Skip to main content
BYOK lets you route /v1/chat/completions requests through your own model-provider account using the @provider/model syntax. These seven endpoints are how the credentials behind that syntax are managed. They live under https://dash.square1.dev/api/user/me/byok.
Everything here concerns credentials you registered on your own account. Provider names appear in these responses because you are the source of that information — they say nothing about how the shared model pool is served.

Gates

This router applies three checks in order, ahead of every endpoint below:
  1. Active session or key — an unlinked or revoked account is rejected here even though it can reach the rest of /api/user/*.
  2. No guests — guest accounts expire, and an expired guest would orphan stored ciphertext.
  3. Feature enabled — BYOK is an operator switch, and it additionally requires the server’s credential-encryption secret to be configured.
Failures 2 and 3 both answer 403 byok_disabled:
Guests get the same code with a different message (게스트 계정에서는 사용할 수 없습니다.). Branch on the code, not the text.
All seven endpoints share one rate-limit budget: 15 requests/minute. A model-list fetch and a save compete for the same window.

Field naming

This router is entirely snake_case, request bodies and responses alike — the exact opposite of its /api/user/* siblings, which are mostly camelCase. auth_scheme, is_primary, fallback_model_id, base_url_origin.

List credentials — GET /api/user/me/byok

credentials[]

The secret is write-only. There is no reveal endpoint, no unmasked field, and no way to read it back. Replace it by saving a new credential.

providers[]

The catalog of provider types you may register, one row per (provider, auth scheme) pair. A provider that supports two schemes appears twice — because the per-provider cap is counted per scheme, so collapsing them would make the count wrong. Each row carries id, label, auth_scheme, required_fields, optional_fields, supports_models_list, and multi_scheme. Drive your UI from this array rather than hardcoding a list — which providers are offered, and which fields each needs, is server-side data. The label values are the human-readable provider names shown in the dashboard.

The three fallback fields

They look redundant and are not: Sending only the last one would make “following the default” and “explicitly chose the same value” indistinguishable, and there would be no way back to null.

Set the fallback toggle — PUT /api/user/me/byok/fallback

enabled accepts true, false, or null. null is a value, not a deletion — it means “follow the operator default again”, which is why there is no DELETE on this path. Omitting enabled entirely is a 400 invalid_request. A 404 with no code is possible but rare — it means the account row vanished between authentication and the write.

Verify without saving — POST /api/user/me/byok/verify

The “test” button. Runs a live probe against the provider and reports the result, storing nothing.
Success:
A failed verification is still HTTP 200. The result is in the body:
Check data.ok, not the status code. This endpoint reserves non-2xx for malformed requests — not for “your credential didn’t work”.
When the credential requires a base_url, a successful response also carries resolved_base_url — the URL the server actually assembled from your input. Show it: pasting a full endpoint path (rather than a base) is a common mistake that otherwise surfaces only as an opaque failure.

Save a credential — POST /api/user/me/byok

Same body as verify. The server re-runs the identical probe first and only stores a credential that passed — there is no unverified state.
The same probe failure that verify reports as 200 {ok:false} is a 400 here. One request, two surfaces, two shapes. Do not write one handler for both — branch on the endpoint you called.
  • label is optional (max 40 characters). Omit it and the server assigns the lowest unused key-N for that provider and scheme.
  • The label is immutable after creation — it is bound into the encryption of the secret.
  • The first credential saved for a provider becomes is_primary.
byok_credential_conflict covers both the cap and a duplicate label. If you need to tell them apart, compare your active count against max_per_provider from the list response.

Update a credential — PATCH /api/user/me/byok/:id

Exactly three fields are accepted: label and secret are not accepted. The label is part of the secret’s encryption, and secrets are replaced by saving a new credential. fallback_model_id must name a model in the shared catalog — a BYOK (@provider/...) id is rejected, because falling back from your key to your key defeats the point. An unknown id is 400 byok_credential_missing. 404 byok_credential_missing if the id isn’t yours. A non-integer id is a 400 with the same code.

Delete a credential — DELETE /api/user/me/byok/:id

Soft-revokes the credential and promotes another to primary if needed. 404 byok_credential_missing if it isn’t yours. The label frees up for reuse.

List a credential’s models — GET /api/user/me/byok/:id/models

A server-side proxy that asks your provider what models your credential can see. Your secret never leaves the server.
  • cached tells you whether the answer came from the short-lived per-credential cache. A cache hit never skips the ownership check.
  • A successful fetch marks the credential verified — a rejected credential returns to active.
The ids returned here are your provider’s ids — use them verbatim after the @provider/ prefix. BYOK models never appear in GET /v1/models.

Calling a BYOK model

Registration is only half of it; the request syntax, the @provider/model forms, and the dispatch-time error surface are covered in BYOK.