Skip to content

Connect or replace the fallback AI provider

PUT
/v1/settings/ai/fallback
curl --request PUT \
--url https://beta-api.plune.ai/v1/settings/ai/fallback \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{ "provider": "openrouter", "apiKey": "sk-example-not-a-real-key", "model": "gpt-5", "acceptedDisclaimer": true }'

#829 (ADR 0052): owner only. An optional second provider with its own key; the code goes to it only when the primary fails. The primary must be connected first (409). The fallback cannot be the same as the primary: the same provider, address and model (400). A custom address goes through the same guard as the primary’s (https, public address) before anything is saved. The key is write-only.

Media type application/json

#829 (ADR 0052): PUT /v1/settings/ai/fallback - the same fields as AiSettingsConnect without language (the primary’s language governs). customAddress is required exactly when provider is custom.

object
provider
required

ADR-0004 — the three ways to connect a provider on this feature; each later addition is its own task.

string
Allowed values: anthropic openrouter custom
customAddress

Same rules as AiSettingsConnect.customAddress.

string format: uri
<= 2048 characters /^[Hh][Tt][Tt][Pp][Ss]:///
apiKey
required

Write-only - never returned by any read (AC-12).

string
>= 1 characters
model
required

Same rules as AiSettingsConnect.model.

string
>= 1 characters <= 200 characters /^[A-Za-z0-9@][A-Za-z0-9._:/@+-]*$/
acceptedDisclaimer
required

The fallback’s own acknowledgement: the code goes to this provider only when the primary fails; the owner answers for its terms of data handling.

boolean
Examples
Example openrouter

a second hosted provider

{
"provider": "openrouter",
"apiKey": "sk-example-not-a-real-key",
"model": "gpt-5",
"acceptedDisclaimer": true
}

Connected - the owner view, with the fallback slot

Media type application/json

What an owner reads — AiSettingsStatus plus AC-05/AC-09/AC-15. Never includes the credentials themselves (AC-12).

object
configured
required

Whether a provider has ever been connected (an ai_settings row exists).

boolean
enabled
required
boolean
provider

Present exactly when configured is true.

string
Allowed values: anthropic openrouter custom
model

Present exactly when configured is true.

string
fallback

#829: the fallback slot - provider and model only (AC-05). Present when one is set.

object
provider
required

ADR-0004 — the three ways to connect a provider on this feature; each later addition is its own task.

string
Allowed values: anthropic openrouter custom
model
required
string
>= 1 characters
fallback

#829: the owner’s view of the fallback slot; present when one is set.

object
provider
required

ADR-0004 — the three ways to connect a provider on this feature; each later addition is its own task.

string
Allowed values: anthropic openrouter custom
model
required
string
>= 1 characters
customAddress

Present when provider is custom.

string
credentialsSavedAt
required
string format: date-time
credentialsSavedBy
required

userId of the owner who last connected or replaced the fallback’s credentials.

string
lastIssueKind

The fallback’s own most recent provider issue, apart from the primary’s: a primary that fell back stays in the primary’s.

string
Allowed values: key_rejected provider_rate_limited provider_unavailable invalid_response address_rejected
lastIssueAt
string format: date-time
nullable
customAddress

Present when provider is custom.

string
monthlyCapUsd

#1286: the owner’s monthly limit in USD; null = none. At 80% usage.cap.state is warning, at 100% new draft requests are refused.

number
nullable
language

Draft language, default en (AC-05) — a project setting, not the reader’s interface language.

string
credentialsSavedAt

Present exactly when configured is true (AC-05, AC-12 — “коли й ким”, never the value).

string format: date-time
credentialsSavedBy

userId of the owner who last connected or replaced credentials.

string
lastIssueKind

AC-09 — the most recent provider issue, if any; null otherwise. Never aborted-shaped: a timeout is not a provider failure.

string
Allowed values: key_rejected provider_rate_limited provider_unavailable invalid_response address_rejected
lastIssueAt
string format: date-time
nullable
usage

Present exactly when configured is true.

object
month
required

Calendar month, UTC.

string
/^\d{4}-\d{2}$/
calls
required
object
succeeded
required
integer
failed
required
integer
aborted
required
integer
fallbackAttempts

#829: how many of the attempts above the fallback made, so an owner sees it really works (ADR 0052 §3).

integer
cost
required
object
knownUsd
required

Sum of costUsd over calls whose model had a known price at call time.

number
unknownPriceCalls
required

Count of calls whose tokens the provider reported but whose model has no price — “ціна невідома” — never folded into knownUsd as zero (SAD §8 rule 6). A call that reported no tokens (a timeout, a rejected key) has nothing to price and is counted in calls, not here.

integer
byActor

#1286: the same two numbers split by who asked. agent is a model acting for a person (a bearer token with X-Plune-Session, an agent actor in the audit log); human is everyone else. The halves add up to the totals.

object
human
required
object
knownUsd
required
number
unknownPriceCalls
required
integer
agent
required
object
knownUsd
required
number
unknownPriceCalls
required
integer
cap

#1286: where the month stands against the owner’s limit (AiSettingsFull.monthlyCapUsd); absent when no limit is set. warning from 80% of it, spent at 100%. Only calls with a known price count.

object
usd
required
number
usedPercent
required
integer
state
required
string
Allowed values: ok warning spent
recommendedModels

GET, owner only (#896): the model the platform recommends for each provider it lists — none for custom, whose owner names the model. The connect form offers it first; always a model with a known price (AC-15).

object
anthropic
string
openrouter
string
models

GET, owner only (#898): the models the connect form lets the owner choose from for each provider the platform lists — the ones it has a price for, the recommended first, each with that price. None for custom. A model that is not listed is still allowed (model is free text): the list is a help, not a gate.

object
anthropic
Array<object>

One model the connect form lists (#898): the id as a provider writes it and what the platform’s own price table charges for it, per million tokens (AC-15).

object
id
required
string
inputUsdPerMillion
required

USD for a million input tokens.

number
outputUsdPerMillion
required

USD for a million output tokens.

number
openrouter
Array<object>

One model the connect form lists (#898): the id as a provider writes it and what the platform’s own price table charges for it, per million tokens (AC-15).

object
id
required
string
inputUsdPerMillion
required

USD for a million input tokens.

number
outputUsdPerMillion
required

USD for a million output tokens.

number
Example
{
"provider": "anthropic",
"fallback": {
"provider": "anthropic",
"lastIssueKind": "key_rejected"
},
"lastIssueKind": "key_rejected",
"usage": {
"cap": {
"state": "ok"
}
},
"models": {
"anthropic": [
{
"id": "claude-sonnet-5-5",
"inputUsdPerMillion": 2,
"outputUsdPerMillion": 10
}
],
"openrouter": [
{
"id": "claude-sonnet-5-5",
"inputUsdPerMillion": 2,
"outputUsdPerMillion": 10
}
]
}
}

Invalid body, a rejected address, or the same as the primary

Media type application/json
object
error
required
string
Examples
{
"error": "validation failed — acceptedDisclaimer: must be true"
}

No / invalid token or session

A member or a reader asked

Media type application/json
object
error
required
string
Example
{
"error": "project owner only"
}

The primary is not connected yet - PUT /v1/settings/ai first

Media type application/json
object
error
required
string
Example
{
"error": "AI is not connected yet — connect a provider first"
}

Rate-limited: the per-route throttle, or the per-minute request limit of the plan an API token counts by (#1442; the body names the plan, #2092). Retry-After carries the seconds until the window rolls over.

Media type application/json
object
error
required
string
Example generated
{
"error": "example"
}
Retry-After
integer

Seconds until the window rolls over

No server encryption key; nothing is saved

Media type application/json
object
error
required
string
Example
{
"error": "AI platform not configured — contact your operator"
}