{"components":{"headers":{"RetryAfterMs":{"description":"The same wait as Retry-After, in milliseconds. On 503 overload responses it is jittered between 2000 and 2500. On a per-key 429 `rate_limit_exceeded` it is the exact time until one request is allowed again.","schema":{"minimum":1,"type":"integer"}},"XDeeprelayCost":{"description":"Non-streaming responses only: the amount charged for this call in USD, equal to `usage.cost`. Streaming responses carry the cost in the final usage chunk instead.","schema":{"type":"string"}},"XDeeprelayErrorCode":{"description":"Present on every error response; equal to `error.code` in the body. Lets a client branch on the error without parsing the body. The governance codes are `api_key_budget_exceeded` (402: the API key's monthly budget would be exceeded), `member_spend_limit_reached` (402: the member's monthly spend limit is reached), `model_not_allowed_for_key` (403: the key's model allowlist does not include this model, or the model does not exist) and `keys_manage_required` (403: a key management write by a key without `keys:manage`). The same value is stored as `error_code` on the request-log row.","schema":{"type":"string"}},"XDeeprelayFallbackAttempted":{"description":"Present only when backup models were tried and none served the request: how many models were dialled, the requested one included (skipped backups are not counted). The status, error code and retry headers are the requested model's own, as without fallback, and the error message says fallback was attempted and quotes the request id.","schema":{"example":3,"minimum":1,"type":"integer"}},"XDeeprelayFallbackReason":{"description":"Present only when a backup model served the request: why the requested model could not, as a public error code from the X-Deeprelay-Error-Code table (for example `upstream_timeout`, `upstream_unavailable`, `upstream_rate_limited` or `context_length_exceeded`).","schema":{"example":"upstream_timeout","type":"string"}},"XDeeprelayIgnoredParams":{"description":"Comma-separated list of request inputs the relay did not honour as sent. Absent when everything was honoured. Tokens: a bare hint name (`reasoning_effort`, `presence_penalty`, `frequency_penalty`, `seed`, `parallel_tool_calls`) when the serving upstream did not accept it and it was dropped; `response_format:downgraded` when a non-strict json_schema was served as json_object; `parallel_tool_calls:enforced` when `parallel_tool_calls: false` was enforced by the relay trimming the reply to its first tool call; `image_url.detail` when an image part carried `detail`, which is never forwarded. Reflects the upstream that served the response, never a failed fallback attempt.","schema":{"type":"string"}},"XDeeprelayQuotaInputRemaining":{"description":"Subscribers only. Monthly input quota left, in weighted tokens (tokens × the model's usage factor, the units of `usage.cost_details.quota_units_used`), as of the start of this request; it does not include this call. Clamped at 0. Omitted when the cap is disabled and on pay-as-you-go keys.","schema":{"minimum":0,"type":"integer"}},"XDeeprelayQuotaOutputRemaining":{"description":"Subscribers only. Monthly output quota left, in weighted tokens, as of the start of this request. Same rules as x-deeprelay-quota-input-remaining.","schema":{"minimum":0,"type":"integer"}},"XDeeprelayQuotaReset":{"description":"Subscribers only. When the monthly input/output meters reset (the end of the current billing period), RFC 3339.","schema":{"format":"date-time","type":"string"}},"XDeeprelayQuotaSearchLimit":{"description":"Subscribers only. The plan's monthly web-search allowance. Resets with x-deeprelay-quota-reset.","schema":{"minimum":0,"type":"integer"}},"XDeeprelayQuotaSearchRemaining":{"description":"Subscribers only. Server-side web searches left in this billing period's plan allowance, as of the start of this request. Clamped at 0. Sent alongside the other quota headers, not only on calls that search. Omitted when the plan includes no searches and on pay-as-you-go keys.","schema":{"minimum":0,"type":"integer"}},"XDeeprelayQuotaWeeklyInputLimit":{"description":"Subscribers only. The rolling 7-day input cap in weighted input tokens.","schema":{"minimum":0,"type":"integer"}},"XDeeprelayQuotaWeeklyInputRemaining":{"description":"Subscribers only. Weighted input tokens left in the rolling 7-day input cap, as of the start of this request. The window rolls, so there is no weekly reset header. Clamped at 0. Omitted when the weekly input cap is disabled and on pay-as-you-go keys.","schema":{"minimum":0,"type":"integer"}},"XDeeprelayQuotaWeeklyOutputLimit":{"description":"Subscribers only. The rolling 7-day output cap in weighted output tokens. Reasoning tokens are output tokens.","schema":{"minimum":0,"type":"integer"}},"XDeeprelayQuotaWeeklyOutputRemaining":{"description":"Subscribers only. Weighted output tokens left in the rolling 7-day output cap, as of the start of this request. Independent of the input cap; same rules as x-deeprelay-quota-weekly-input-remaining.","schema":{"minimum":0,"type":"integer"}},"XDeeprelayRequestedModel":{"description":"The model the request asked for, in canonical form. Present once the model id is resolved, on successful responses and on errors.","schema":{"example":"deeprelay/kimi-k3","type":"string"}},"XDeeprelayServedModel":{"description":"The model that produced this response. Equal to X-Deeprelay-Requested-Model unless a backup model served the request. It matches the body `model`, the request-log row and the bill. Sent before the first byte, so it is accurate on streamed responses too.","schema":{"example":"deeprelay/glm-5.3","type":"string"}},"XRateLimitLimitRequests":{"description":"Requests this API key may send per one-second window (its requests per second plus its burst allowance). Sent on every response that passed the per-key limiter, success or 429; omitted when the limiter could not be consulted. There are no token-based rate-limit headers.","schema":{"minimum":1,"type":"integer"}},"XRateLimitRemainingRequests":{"description":"Requests left in the current one-second window, this one counted. Clamped at 0.","schema":{"minimum":0,"type":"integer"}},"XRateLimitResetRequests":{"description":"Time until the current window ends and the full limit is available again, in OpenAI's duration format (for example `300ms` or `1s`).","schema":{"example":"300ms","type":"string"}},"XShouldRetry":{"description":"`false` on quota and spend denials (429 `subscription_quota_exhausted`, 402 `insufficient_balance` / `daily_limit_reached` / `spending_limit_reached` / `api_key_budget_exceeded` / `member_spend_limit_reached`): retrying the same request will not succeed until the quota resets or the wallet or limit changes. Official OpenAI SDKs honour it and make no retry.","schema":{"enum":["false"],"type":"string"}}},"parameters":{"ClientRequestIdHeader":{"description":"Your own correlation id for this call, up to 128 characters. It is stored on the request-log row as `client_request_id` and can be filtered on with `GET /requests?client_request_id=`. When it is absent, an inbound `X-Request-Id` is kept in its place; the response's own `X-Request-Id` is always minted by deeprelay. Surrounding whitespace and control characters are removed; a longer value is dropped, never truncated. It is never used as a lookup key, so it does not need to be unique.","in":"header","name":"X-Client-Request-Id","required":false,"schema":{"maxLength":128,"type":"string"}},"CursorParam":{"in":"query","name":"cursor","schema":{"type":"string"}},"FallbackModelsHeader":{"description":"Backup models for this request only, in priority order: a comma-separated list of up to 3 model ids, for example `deeprelay/glm-5.3,deeprelay/kimi-k3`. It replaces the API key's `fallback_models` for this request; the two lists are never merged. `none` (or an empty value) turns model fallback off for this request. If every provider for the requested model fails before the first token with a temporary error, deeprelay tries the next listed model. Each backup must still pass the key's model allowlist, the request's billing mode and the request's capability needs; one that does not is skipped, so this header can never widen what the key may call. More than 3 models, a malformed id, `none` mixed with ids, or this header together with a `models` body field is a 400 `invalid_request`. Not accepted by Liquid, which always serves exactly the requested model.","in":"header","name":"X-Deeprelay-Fallback-Models","required":false,"schema":{"example":"deeprelay/glm-5.3,deeprelay/kimi-k3","maxLength":512,"type":"string"}},"IdempotencyKeyHeader":{"in":"header","name":"Idempotency-Key","schema":{"type":"string"}},"LimitParam":{"in":"query","name":"limit","schema":{"default":50,"maximum":200,"minimum":1,"type":"integer"}},"RequestLogClientRequestIdParam":{"description":"Only rows whose `X-Client-Request-Id` equals this value.","in":"query","name":"client_request_id","schema":{"maxLength":128,"type":"string"}},"RequestLogErrorCodeParam":{"description":"Only rows that ended with this `X-Deeprelay-Error-Code`.","in":"query","name":"error_code","schema":{"maxLength":64,"pattern":"^[a-z0-9_]+$","type":"string"}},"RequestLogFallbackParam":{"description":"`true` returns only rows a backup model served or where backups were tried (`fallback_reason` is set); `false` or absent applies no filter. Any other value is a 400.","in":"query","name":"fallback","schema":{"type":"boolean"}},"RequestLogFromParam":{"description":"Inclusive RFC 3339 range start. Defaults to the start of the 90-day window; an older value is clamped to it.","in":"query","name":"from","schema":{"format":"date-time","type":"string"}},"RequestLogKeyParam":{"description":"Only rows sent with this API key (its `api_key_id`).","in":"query","name":"key","schema":{"format":"uuid","type":"string"}},"RequestLogModelParam":{"description":"Only rows for this model id.","in":"query","name":"model","schema":{"maxLength":256,"type":"string"}},"RequestLogStatusParam":{"description":"Only rows with this outcome. `rejected` is a call refused before it reached a model (a budget, limit, allowlist or quota gate).","in":"query","name":"status","schema":{"enum":["ok","error","cancelled","rejected"],"type":"string"}},"RequestLogSurfaceParam":{"description":"Only rows from this endpoint family.","in":"query","name":"surface","schema":{"enum":["chat","messages","responses","embeddings","image","video"],"type":"string"}},"RequestLogToParam":{"description":"Exclusive RFC 3339 range end. Defaults to now; must be after `from`.","in":"query","name":"to","schema":{"format":"date-time","maxLength":255,"type":"string"}}},"responses":{"OpenAIBadGateway":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OpenAIErrorEnvelope"}}},"description":"The upstream answered but its response could not be used (OpenAI error envelope): code upstream_response_too_large when the vector payload exceeded the per-request size limit, or upstream_error for a malformed / misaligned response. Not retryable as-is for upstream_response_too_large — send fewer inputs.","headers":{"X-Deeprelay-Error-Code":{"$ref":"#/components/headers/XDeeprelayErrorCode"},"X-Deeprelay-Fallback-Attempted":{"$ref":"#/components/headers/XDeeprelayFallbackAttempted"},"X-Deeprelay-Requested-Model":{"$ref":"#/components/headers/XDeeprelayRequestedModel"}}},"OpenAIBadRequest":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OpenAIErrorEnvelope"}}},"description":"Invalid request (OpenAI error envelope).","headers":{"X-Deeprelay-Error-Code":{"$ref":"#/components/headers/XDeeprelayErrorCode"},"X-Deeprelay-Fallback-Attempted":{"$ref":"#/components/headers/XDeeprelayFallbackAttempted"},"X-Deeprelay-Requested-Model":{"$ref":"#/components/headers/XDeeprelayRequestedModel"}}},"OpenAIForbidden":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OpenAIErrorEnvelope"}}},"description":"API key lacks the required scope (`insufficient_scope`), or the key has a model allowlist that does not include the requested model (`model_not_allowed_for_key`; also the answer for a model that does not exist, so a restricted key cannot probe the catalog). OpenAI error envelope.","headers":{"X-Deeprelay-Error-Code":{"$ref":"#/components/headers/XDeeprelayErrorCode"}}},"OpenAIGone":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OpenAIErrorEnvelope"}}},"description":"The requested artifact has expired and is no longer available (OpenAI error envelope).","headers":{"X-Deeprelay-Error-Code":{"$ref":"#/components/headers/XDeeprelayErrorCode"}}},"OpenAIImages":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImagesResponse"}}},"description":"OpenAI-shaped image generation response (b64_json only)."},"OpenAIInternalError":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OpenAIErrorEnvelope"}}},"description":"Internal server error (OpenAI error envelope).","headers":{"X-Deeprelay-Error-Code":{"$ref":"#/components/headers/XDeeprelayErrorCode"}}},"OpenAIModel":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Model"}}},"description":"OpenAI-shaped single model."},"OpenAINotFound":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OpenAIErrorEnvelope"}}},"description":"Model or resource not found (OpenAI error envelope).","headers":{"X-Deeprelay-Error-Code":{"$ref":"#/components/headers/XDeeprelayErrorCode"}}},"OpenAIPaymentRequired":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OpenAIErrorEnvelope"}}},"description":"Insufficient balance or a spend limit reached (OpenAI error envelope, SERV-06): `insufficient_balance`, `daily_limit_reached`, `spending_limit_reached`, `api_key_budget_exceeded` (this API key's monthly budget) or `member_spend_limit_reached` (the member's monthly spend limit). Carries `x-should-retry: false`: the same request will not succeed until the wallet or the limit changes.","headers":{"X-Deeprelay-Error-Code":{"$ref":"#/components/headers/XDeeprelayErrorCode"},"x-should-retry":{"$ref":"#/components/headers/XShouldRetry"}}},"OpenAIRateLimited":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OpenAIErrorEnvelope"}}},"description":"Rate limit or subscription quota exceeded (OpenAI error envelope). A per-key or per-IP rate limit (`rate_limit_exceeded`) carries Retry-After, the whole seconds to wait, rounded up (minimum 1), and retry-after-ms, the exact wait; both are the time until one request is allowed, not a full refill. A per-key denial also carries the x-ratelimit-*-requests headers. A subscription quota denial (`subscription_quota_exhausted`, raised only when the wallet cannot cover the pay-as-you-go fallback) carries `x-should-retry: false` and no Retry-After. When the tripped gate is a rolling 7-day cap (`param: weekly_input_tokens` or `param: weekly_output_tokens`), the error object also carries `quota_frees_at`, the RFC 3339 time the window frees enough capacity — the earlier of the two windows. `param` names the tripped gate: monthly_input_tokens, monthly_output_tokens, weekly_input_tokens, weekly_output_tokens, max_usage, input_overshoot (this request's estimated input alone exceeds what is left in the tighter of the monthly and rolling 7-day input allowances), or output_overshoot (the same for the estimated output — the completion bound). On an overshoot the quota is not exhausted — send a smaller request (or a smaller max_tokens) or add credits.","headers":{"Retry-After":{"description":"Seconds the client should wait before retrying.","schema":{"minimum":1,"type":"integer"}},"X-Deeprelay-Error-Code":{"$ref":"#/components/headers/XDeeprelayErrorCode"},"X-Deeprelay-Fallback-Attempted":{"$ref":"#/components/headers/XDeeprelayFallbackAttempted"},"X-Deeprelay-Requested-Model":{"$ref":"#/components/headers/XDeeprelayRequestedModel"},"retry-after-ms":{"$ref":"#/components/headers/RetryAfterMs"},"x-ratelimit-limit-requests":{"$ref":"#/components/headers/XRateLimitLimitRequests"},"x-ratelimit-remaining-requests":{"$ref":"#/components/headers/XRateLimitRemainingRequests"},"x-ratelimit-reset-requests":{"$ref":"#/components/headers/XRateLimitResetRequests"},"x-should-retry":{"$ref":"#/components/headers/XShouldRetry"}}},"OpenAIRequestTooLarge":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OpenAIErrorEnvelope"}}},"description":"Request body too large (OpenAI error envelope, code request_too_large).","headers":{"X-Deeprelay-Error-Code":{"$ref":"#/components/headers/XDeeprelayErrorCode"}}},"OpenAIUnauthorized":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OpenAIErrorEnvelope"}}},"description":"Missing or invalid API key (OpenAI error envelope).","headers":{"X-Deeprelay-Error-Code":{"$ref":"#/components/headers/XDeeprelayErrorCode"}}},"OpenAIUnprocessableEntity":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OpenAIErrorEnvelope"}}},"description":"Request validation failed (OpenAI error envelope).","headers":{"X-Deeprelay-Error-Code":{"$ref":"#/components/headers/XDeeprelayErrorCode"}}},"OpenAIUpstreamTimeout":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OpenAIErrorEnvelope"}}},"description":"Upstream provider exceeded the synchronous per-attempt deadline (OpenAI\nerror envelope). 504 semantic — distinct from 503 OpenAIUpstreamUnavailable\nwhich signals a connection or routing failure rather than a timeout.\n","headers":{"X-Deeprelay-Error-Code":{"$ref":"#/components/headers/XDeeprelayErrorCode"},"X-Deeprelay-Fallback-Attempted":{"$ref":"#/components/headers/XDeeprelayFallbackAttempted"},"X-Deeprelay-Requested-Model":{"$ref":"#/components/headers/XDeeprelayRequestedModel"}}},"OpenAIUpstreamUnavailable":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OpenAIErrorEnvelope"}}},"description":"No upstream could serve the request (OpenAI error envelope, `upstream_unavailable`): every capable upstream is down, shedding load, or none of the model's routable upstreams supports a capability the request needs. Always carries Retry-After (whole seconds, rounded up) and retry-after-ms; on an overload the wait is jittered between 2 and 2.5 seconds.","headers":{"Retry-After":{"description":"Seconds the client should wait before retrying.","schema":{"minimum":1,"type":"integer"}},"X-Deeprelay-Error-Code":{"$ref":"#/components/headers/XDeeprelayErrorCode"},"X-Deeprelay-Fallback-Attempted":{"$ref":"#/components/headers/XDeeprelayFallbackAttempted"},"X-Deeprelay-Requested-Model":{"$ref":"#/components/headers/XDeeprelayRequestedModel"},"retry-after-ms":{"$ref":"#/components/headers/RetryAfterMs"}}},"OpenAIVideo":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VideoJob"}}},"description":"A video generation job object."},"OpenAIVideoList":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VideoList"}}},"description":"A page of video generation jobs."},"Problem":{"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}},"description":"Error response (RFC 7807)"},"RateLimited":{"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}},"description":"Rate limit exceeded (RFC 7807). Retry-After header indicates seconds to wait.","headers":{"Retry-After":{"description":"Seconds the client should wait before retrying.","schema":{"minimum":1,"type":"integer"}}}},"UsagePage":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsagePage"}}},"description":"OK"},"WebhookEndpointPage":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointPage"}}},"description":"OK"}},"schemas":{"AlertEmailToggles":{"properties":{"api_key_budget":{"type":"boolean"},"balance_low":{"type":"boolean"},"member_spend":{"type":"boolean"},"subscription_quota":{"type":"boolean"}},"required":["balance_low","subscription_quota","api_key_budget","member_spend"],"type":"object"},"AlertSettings":{"properties":{"balance_threshold_cents":{"description":"The wallet balance, in cents, below which a low-balance alert fires.","format":"int64","type":"integer"},"email":{"$ref":"#/components/schemas/AlertEmailToggles"},"note":{"description":"A fixed note that the member always receives their own limit emails.","type":"string"},"updated_at":{"description":"Null until the organization saves its settings once.","format":"date-time","type":["string","null"]}},"required":["balance_threshold_cents","email","updated_at","note"],"type":"object"},"AlertSettingsUpdate":{"properties":{"balance_threshold_cents":{"format":"int64","maximum":100000000,"minimum":1,"type":"integer"},"email":{"$ref":"#/components/schemas/AlertEmailToggles"}},"required":["balance_threshold_cents","email"],"type":"object"},"ApiKey":{"description":"An API key's metadata and limits. Never carries the secret.","properties":{"allowed_models":{"description":"The models this key may call. Null means every model. A call for any other model is refused with 403 `model_not_allowed_for_key`, and `GET /models` lists only these.","items":{"type":"string"},"type":["array","null"]},"api_key_id":{"format":"uuid","type":"string"},"budget_cents":{"description":"Monthly spend budget in cents, reset at the start of each UTC month. Null means no budget. A call that would take the key past it is refused with 402 `api_key_budget_exceeded`.","format":"int64","type":["integer","null"]},"budget_resets_at":{"description":"When the budget resets. Present when the key has a budget.","format":"date-time","type":"string"},"budget_used_micro_cents":{"description":"Spend so far this month, in micro-cents. Present when the key has a budget.","format":"int64","type":"integer"},"created_at":{"format":"date-time","type":"string"},"expires_at":{"format":"date-time","type":["string","null"]},"fallback_entries":{"description":"Present when `fallback_models` is set: one entry per backup, in order, explaining it as of now.","items":{"$ref":"#/components/schemas/ApiKeyFallbackEntry"},"type":"array"},"fallback_models":{"description":"Backup models in priority order, applied to every model this key calls. Null means model fallback is off (the default). If every provider for the requested model fails before the first token with a temporary error, the next entry is tried. Entries are kept when a model leaves the plan roster or the catalog and are skipped at request time; see `fallback_entries`. Not applied to Liquid.","items":{"type":"string"},"maxItems":3,"type":["array","null"]},"key_prefix":{"type":"string"},"last4":{"type":"string"},"last_used_at":{"format":"date-time","type":["string","null"]},"name":{"type":"string"},"primary_model":{"description":"The model this key requested most over the last 30 days, which `above_primary` is measured against. Present only for a key with `fallback_models` and recent usage.","type":"string"},"rate_limit_burst":{"type":"integer"},"rate_limit_rps":{"type":"integer"},"revoked_at":{"format":"date-time","type":"string"},"scopes":{"$ref":"#/components/schemas/ApiKeyScopes"},"stale_models":{"description":"Entries of `allowed_models` that are no longer in the catalog.","items":{"type":"string"},"type":"array"}},"required":["api_key_id","name","key_prefix","last4","scopes","budget_cents","allowed_models","fallback_models","rate_limit_rps","rate_limit_burst","expires_at","created_at","last_used_at"],"type":"object"},"ApiKeyCreate":{"properties":{"allowed_models":{"items":{"type":"string"},"maxItems":256,"type":"array"},"budget_cents":{"format":"int64","minimum":1,"type":"integer"},"expires_at":{"format":"date-time","type":"string"},"fallback_models":{"description":"Backup models in priority order. Omit it to leave model fallback off. Each entry must be a chat model in the catalog and, when the key has `allowed_models`, on that list. Duplicates are removed. An empty array, more than 3 models or an invalid entry is a 422 `validation_failed` naming the `fallback_models` field.","items":{"type":"string"},"maxItems":3,"minItems":1,"type":"array"},"name":{"maxLength":128,"minLength":1,"type":"string"},"rate_limit_burst":{"minimum":1,"type":"integer"},"rate_limit_rps":{"minimum":1,"type":"integer"},"scopes":{"additionalProperties":true,"description":"One of `{\"full_access\": true}`, `{\"standard\": true}`, `{\"read_only\": true}`, or a per-resource map such as `{\"serverless\": \"read\", \"keys\": \"write\"}`. A shortcut cannot be combined with per-resource entries.","type":"object"}},"required":["name","scopes"],"type":"object"},"ApiKeyCreated":{"description":"The new key's metadata plus its secret (`key`), returned exactly once. The same fields as `ApiKey`, plus `key`.","properties":{"allowed_models":{"description":"The models this key may call. Null means every model. A call for any other model is refused with 403 `model_not_allowed_for_key`, and `GET /models` lists only these.","items":{"type":"string"},"type":["array","null"]},"api_key_id":{"format":"uuid","type":"string"},"budget_cents":{"description":"Monthly spend budget in cents, reset at the start of each UTC month. Null means no budget. A call that would take the key past it is refused with 402 `api_key_budget_exceeded`.","format":"int64","type":["integer","null"]},"budget_resets_at":{"description":"When the budget resets. Present when the key has a budget.","format":"date-time","type":"string"},"budget_used_micro_cents":{"description":"Spend so far this month, in micro-cents. Present when the key has a budget.","format":"int64","type":"integer"},"created_at":{"format":"date-time","type":"string"},"expires_at":{"format":"date-time","type":["string","null"]},"fallback_entries":{"description":"Present when `fallback_models` is set: one entry per backup, in order, explaining it as of now.","items":{"$ref":"#/components/schemas/ApiKeyFallbackEntry"},"type":"array"},"fallback_models":{"description":"Backup models in priority order, applied to every model this key calls. Null means model fallback is off (the default). If every provider for the requested model fails before the first token with a temporary error, the next entry is tried. Entries are kept when a model leaves the plan roster or the catalog and are skipped at request time; see `fallback_entries`. Not applied to Liquid.","items":{"type":"string"},"maxItems":3,"type":["array","null"]},"key":{"description":"The secret. Shown once; it cannot be read again.","type":"string"},"key_prefix":{"type":"string"},"last4":{"type":"string"},"last_used_at":{"format":"date-time","type":["string","null"]},"name":{"type":"string"},"primary_model":{"description":"The model this key requested most over the last 30 days, which `above_primary` is measured against. Present only for a key with `fallback_models` and recent usage.","type":"string"},"rate_limit_burst":{"type":"integer"},"rate_limit_rps":{"type":"integer"},"revoked_at":{"format":"date-time","type":"string"},"scopes":{"$ref":"#/components/schemas/ApiKeyScopes"},"stale_models":{"description":"Entries of `allowed_models` that are no longer in the catalog.","items":{"type":"string"},"type":"array"}},"required":["api_key_id","name","key_prefix","last4","scopes","budget_cents","allowed_models","fallback_models","rate_limit_rps","rate_limit_burst","expires_at","created_at","last_used_at","key"],"type":"object"},"ApiKeyFallbackEntry":{"description":"One backup model on a key, as it stands now.","properties":{"above_primary":{"description":"True when this backup's input plus output price is above `primary_model`'s. Pricier backups are allowed; a request served by one is billed at its own price, and the key budget is checked at that price.","type":"boolean"},"input_per_1m_tokens_microcents":{"description":"Current input price per 1M tokens, in micro-cents. 0 when unknown or unavailable.","format":"int64","type":"integer"},"model":{"type":"string"},"output_per_1m_tokens_microcents":{"description":"Current output price per 1M tokens, in micro-cents. 0 when unknown or unavailable.","format":"int64","type":"integer"},"state":{"description":"`ok`: usable as a backup. `wallet_only`: the model is not on the subscription plan roster, so it backs up only requests billed to the wallet; a plan-covered request skips it. `unavailable`: the model is no longer in the catalog and is always skipped.","enum":["ok","wallet_only","unavailable"],"type":"string"}},"required":["model","state","input_per_1m_tokens_microcents","output_per_1m_tokens_microcents","above_primary"],"type":"object"},"ApiKeyList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/ApiKey"},"type":"array"}},"required":["data"],"type":"object"},"ApiKeyScopes":{"properties":{"keys_manage":{"description":"Whether the key can create, update and revoke keys: true for a `full_access` key and for a granular key granted `{\"keys\": \"write\"}`.","type":"boolean"},"resources":{"additionalProperties":{"enum":["none","read","write"],"type":"string"},"description":"Per-resource levels, present for a granular key.","type":"object"},"shape":{"enum":["full_access","standard","read_only","granular"],"type":"string"}},"required":["shape","keys_manage"],"type":"object"},"ApiKeyUpdate":{"additionalProperties":false,"description":"Send only the fields to change. `null` clears `budget_cents`, `allowed_models`, `fallback_models` or `expires_at`. `scopes` cannot be changed.","properties":{"allowed_models":{"items":{"type":"string"},"maxItems":256,"type":["array","null"]},"budget_cents":{"format":"int64","minimum":1,"type":["integer","null"]},"expires_at":{"format":"date-time","type":["string","null"]},"fallback_models":{"description":"Replace the backup list (priority order), or `null` to turn model fallback off. Same rules as on create, judged against the key's allowlist after this update. An empty array is a 422 `validation_failed`; send `null` instead.","items":{"type":"string"},"maxItems":3,"minItems":1,"type":["array","null"]},"name":{"maxLength":128,"minLength":1,"type":"string"},"rate_limit_burst":{"minimum":1,"type":"integer"},"rate_limit_rps":{"minimum":1,"type":"integer"}},"type":"object"},"Balance":{"description":"The organization's credit balance with its auto-pay configuration and live burn rate. Mirrors the dashboard's balance contract field for field. No payment-instrument detail appears here — saved cards live behind separate admin-only routes.\n","properties":{"auto_pay_amount_cents":{"description":"Amount auto-pay charges when it triggers. Meaningful only when auto_pay_enabled.","format":"int64","type":"integer"},"auto_pay_enabled":{"type":"boolean"},"auto_pay_threshold_cents":{"description":"Auto-pay triggers when the balance falls below this. Meaningful only when auto_pay_enabled.","format":"int64","type":"integer"},"balance_cents":{"description":"Remaining credit in USD cents. May be negative when usage has outrun the balance.","format":"int64","type":"integer"},"balance_dollars":{"description":"The same figure in dollars, for display.","type":"number"},"burn_cents_per_hour":{"description":"Live burn rate in USD cents per hour: the committed hourly price of every instance that is live, counted from launch — so instances still creating, provisioning or booting are included, not only those that have reached running. 0 when nothing is live.\n\n`null` means the burn rate could not be determined. Treat that as unknown, never as 0: reading an unavailable burn rate as \"nothing running\" reports unbounded runway to an organization that may be minutes from empty.","format":"int64","type":["integer","null"]}},"required":["balance_cents","balance_dollars","auto_pay_enabled","auto_pay_threshold_cents","auto_pay_amount_cents","burn_cents_per_hour"],"type":"object"},"ChatChoice":{"properties":{"finish_reason":{"description":"tool_calls appears only when the request carried tools and the model made a call, which requires a model that lists \"tools\" in its supported_parameters.\n","enum":["stop","length","content_filter","tool_calls"],"type":"string"},"index":{"type":"integer"},"logprobs":{"additionalProperties":true,"description":"Per-token log probabilities (OpenAI shape, `content` array) when the request set logprobs: true, passed through from the serving upstream. Not comparable across upstreams or numeric precisions.\n","nullable":true,"type":"object"},"message":{"$ref":"#/components/schemas/ChatMessage"}},"type":"object"},"ChatCompletionRequest":{"properties":{"frequency_penalty":{"description":"Hint. Forwarded when the serving upstream accepts it, otherwise dropped and reported in x-deeprelay-ignored-params.","maximum":2,"minimum":-2,"nullable":true,"type":"number"},"logprobs":{"description":"Return per-token log probabilities in choices[].logprobs. Gated per model (\"logprobs\" in supported_parameters). Values come from the serving upstream and are not comparable across upstreams or numeric precisions.\n","nullable":true,"type":"boolean"},"max_completion_tokens":{"description":"OpenAI's newer name for max_tokens, accepted for client compatibility. When both are sent, max_tokens wins.\n","minimum":1,"nullable":true,"type":"integer"},"max_tokens":{"minimum":1,"nullable":true,"type":"integer"},"messages":{"items":{"$ref":"#/components/schemas/ChatMessage"},"type":"array"},"model":{"description":"Canonical model id, e.g. \"deeprelay/llama-3.3-70b-instruct\".","type":"string"},"models":{"description":"OpenRouter-compatible backup models for this request. `model` is the requested model and `models` lists the backups in priority order; when `model` is empty, the first entry of `models` is the requested model. An entry naming the requested model is ignored. At most 3 backups. The list replaces the API key's `fallback_models` for this request, and an empty array turns model fallback off for this request. Each backup must still pass the key's model allowlist, the request's billing mode and its capability needs, or it is skipped. Sending both `models` and the X-Deeprelay-Fallback-Models header is a 400 `invalid_request`. Chat completions only; on `/messages` and `/responses` use the header.","items":{"maxLength":256,"type":"string"},"maxItems":4,"type":["array","null"]},"parallel_tool_calls":{"description":"Whether the model may emit several tool calls in one turn. When false and the serving upstream cannot honour it, the relay returns only the first tool call and reports `parallel_tool_calls:enforced` in x-deeprelay-ignored-params.\n","nullable":true,"type":"boolean"},"presence_penalty":{"description":"Hint. Forwarded when the serving upstream accepts it, otherwise dropped and reported in x-deeprelay-ignored-params.","maximum":2,"minimum":-2,"nullable":true,"type":"number"},"reasoning_effort":{"description":"Hint: how much a reasoning model should think. Values in use are none, minimal, low, medium, high and xhigh; which ones a model accepts varies. Forwarded to an upstream that accepts the value, otherwise dropped and reported in x-deeprelay-ignored-params. Never rejected.\n","type":"string"},"response_format":{"description":"Constrain the reply format. Gated per model: `json_object` needs \"response_format\" in the model's supported_parameters, and a strict `json_schema` needs \"structured_outputs\". A model without the capability returns 400 unsupported_parameter naming the models that have it. A non-strict json_schema on a model with only \"response_format\" is served as json_object and reported as `response_format:downgraded` in x-deeprelay-ignored-params. The relay never emulates JSON mode by prompting.\n","discriminator":{"mapping":{"json_object":"#/components/schemas/ResponseFormatJSONObject","json_schema":"#/components/schemas/ResponseFormatJSONSchema","text":"#/components/schemas/ResponseFormatText"},"propertyName":"type"},"oneOf":[{"$ref":"#/components/schemas/ResponseFormatText"},{"$ref":"#/components/schemas/ResponseFormatJSONObject"},{"$ref":"#/components/schemas/ResponseFormatJSONSchema"}]},"seed":{"description":"Hint for best-effort determinism. Forwarded when the serving upstream accepts it, otherwise dropped and reported. Identical seeds are not guaranteed to give identical output across upstreams.\n","nullable":true,"type":"integer"},"stream":{"default":false,"type":"boolean"},"stream_options":{"$ref":"#/components/schemas/StreamOptions"},"temperature":{"nullable":true,"type":"number"},"tool_choice":{"description":"How the model should pick a tool: the strings \"none\", \"auto\" or \"required\", or an object naming one function. Requires a non-empty tools array unless it is \"none\".\n","oneOf":[{"enum":["none","auto","required"],"type":"string"},{"$ref":"#/components/schemas/ToolChoiceFunction"}]},"tools":{"description":"Functions the model may call. Support is per-model — check for \"tools\" in the model's supported_parameters before sending it. Omit the field entirely rather than sending an empty array.\n","items":{"$ref":"#/components/schemas/Tool"},"type":"array"},"top_logprobs":{"description":"Number of alternatives per token (requires logprobs). Gated per model; each model has its own maximum, and a larger value returns 400 unsupported_parameter.\n","maximum":20,"minimum":0,"nullable":true,"type":"integer"}},"required":["model","messages"],"type":"object"},"ChatCompletionResponse":{"properties":{"choices":{"items":{"$ref":"#/components/schemas/ChatChoice"},"type":"array"},"created":{"description":"Unix timestamp (seconds).","type":"integer"},"id":{"type":"string"},"model":{"description":"Canonical (deeprelay/...) model id, NEVER the upstream HF path.","type":"string"},"object":{"enum":["chat.completion"],"type":"string"},"usage":{"$ref":"#/components/schemas/Usage"}},"required":["id","object","created","model","choices","usage"],"type":"object"},"ChatMessage":{"properties":{"content":{"description":"The message text. Null on an assistant turn that is purely a tool call — branch on that to tell a tool call from a reply. Responses always carry a string (or null).\n\nRequests may instead send an array of content parts, as OpenAI clients do (typed here as a string so SDK callers can keep passing plain text). Text parts ({\"type\":\"text\",\"text\":\"...\"}) are joined with newlines. Image parts ({\"type\":\"image_url\",\"image_url\":{\"url\":\"...\"}}) are accepted on models whose supported_parameters list \"vision\"; any other model returns 400 unsupported_parameter (param messages) naming the models that accept images. `url` must be an https URL or a data:image/{png,jpeg,webp,gif};base64 URI, and the relay never fetches it itself. Limits: 16 images per request, 20 MiB per image, 8 KiB per URL, inside the 32 MiB body cap (413 request_too_large). Each image counts as 1,600 tokens in the spend preflight. `image_url.detail` is accepted but not forwarded, and is reported in x-deeprelay-ignored-params. Audio and file parts are rejected with 400 unsupported_content_part.\n","nullable":true,"type":"string"},"role":{"description":"\"developer\" is OpenAI's newer spelling of \"system\" and is treated as system.\n","enum":["system","developer","user","assistant","tool"],"type":"string"},"tool_call_id":{"description":"Which call this message answers. REQUIRED on a role \"tool\" message — without it the upstream cannot match the result to its call.\n","type":"string"},"tool_calls":{"description":"Calls the model wants made. Assistant messages only.","items":{"$ref":"#/components/schemas/ToolCall"},"type":"array"}},"required":["role"],"type":"object"},"CostDetails":{"description":"How this call was priced, in USD (exact decimals, up to 8 places). list_price_equivalent = plan_covered_amount + charged_amount. A call is either fully covered by the subscription or fully charged: there is no partial coverage.\n","properties":{"cache_savings":{"description":"What the prompt cache saved: the list price without the cache discount minus list_price_equivalent. Null when the upstream did not report cache usage. Can be negative when a model's cached rate is not a discount.\n","type":["number","null"]},"charged_amount":{"description":"The part charged to the wallet; equal to usage.cost.","type":"number"},"list_price_equivalent":{"description":"What the call costs at the model's pay-as-you-go list rates (peak rates inside a peak window).","type":"number"},"plan_covered_amount":{"description":"The part the flat subscription covered (the full list price, or 0).","type":"number"},"quota_units_used":{"description":"Weighted subscription-quota units this call consumed: (prompt + completion tokens) × the model's usage factor. 0 for a subscriber call billed pay-as-you-go (a model the plan does not cover, or a quota fallback); null for a pay-as-you-go organization.\n","type":["integer","null"]}},"required":["list_price_equivalent","plan_covered_amount","charged_amount","quota_units_used","cache_savings"],"type":"object"},"CryptoDeposit":{"description":"A stablecoin deposit. Mirrors the Go DTO field for field. Like that struct, this schema deliberately has NO payment-processor property — the processor is an implementation detail and is structurally absent from the customer contract, not merely filtered out.\n","properties":{"amount_usd_cents":{"description":"The amount requested, in USD cents. Never the settled or credited value.","type":"integer"},"asset":{"enum":["usdc","usdt"],"type":"string"},"chain":{"enum":["ethereum","base","arbitrum","polygon","solana","tron","bsc"],"type":"string"},"created_at":{"format":"date-time","type":"string"},"credited_cents":{"description":"USD cents actually credited to the balance. Absent until credited.","type":"integer"},"deposit_id":{"type":"string"},"expires_at":{"description":"RFC3339 UTC. After this, send nothing to pay_address.","format":"date-time","type":"string"},"pay_address":{"description":"The address to send funds to. Unique to this deposit.","type":"string"},"pay_amount":{"description":"The exact token amount to send, as a decimal string (never a number — the value must not round-trip through a float).\n","type":"string"},"status":{"description":"Lifecycle state: created, detected, confirming, settled, screening, held_screening, credited, credited_underpaid, credited_overpaid, expired, expired_received, failed, failed_screening.\n","type":"string"},"tx_hash":{"type":"string"}},"required":["deposit_id","status","chain","asset","amount_usd_cents","created_at"],"type":"object"},"Embedding":{"properties":{"embedding":{"description":"The embedding vector as 32-bit floats.","items":{"format":"float","type":"number"},"type":"array"},"index":{"description":"Position of this vector's input in the request.","type":"integer"},"object":{"enum":["embedding"],"type":"string"}},"required":["object","embedding","index"],"type":"object"},"EmbeddingsRequest":{"properties":{"dimensions":{"description":"Requested output dimensionality, for models that support truncated (Matryoshka) embeddings. Passed through to the model when set.","minimum":1,"type":"integer"},"encoding_format":{"default":"float","description":"Only \"float\" is supported. A value of \"base64\" is rejected with invalid_request_error / unsupported_parameter.","enum":["float"],"type":"string"},"input":{"description":"Text to embed: either a single string or an array of up to 2048 strings. Token-id arrays are not supported. The per-request ceiling is bounded so the response stays under 64 MiB — about 680 inputs at 4096 dimensions, fewer at a larger `dimensions` value; an over-limit batch is rejected with 400 invalid_request_error whose message states the limit, before any processing.","oneOf":[{"type":"string"},{"items":{"type":"string"},"maxItems":2048,"type":"array"}]},"model":{"description":"Canonical embedding model id, e.g. \"deeprelay/qwen3-embedding-8b\".","type":"string"},"user":{"description":"Opaque end-user identifier for abuse tracing. Optional.","type":"string"}},"required":["model","input"],"type":"object"},"EmbeddingsResponse":{"properties":{"data":{"items":{"$ref":"#/components/schemas/Embedding"},"type":"array"},"model":{"description":"Canonical (deeprelay/...) model id, NEVER the upstream identifier.","type":"string"},"object":{"enum":["list"],"type":"string"},"usage":{"$ref":"#/components/schemas/EmbeddingsUsage"}},"required":["object","data","model","usage"],"type":"object"},"EmbeddingsUsage":{"description":"Embeddings are billed on input tokens only, so this block carries no completion_tokens field — an embeddings call emits none, and total_tokens always equals prompt_tokens.","properties":{"prompt_tokens":{"type":"integer"},"total_tokens":{"type":"integer"}},"required":["prompt_tokens","total_tokens"],"type":"object"},"ImagesGenerationsRequest":{"properties":{"model":{"description":"Customer-facing image model id (e.g. deeprelay/seedream-4.0) or alias.","type":"string"},"n":{"default":1,"description":"Number of images to generate. Defaults to 1.","minimum":1,"type":"integer"},"prompt":{"description":"Text description of the image(s) to generate.","type":"string"},"response_format":{"default":"b64_json","description":"Only b64_json is supported. A value of \"url\" is rejected with invalid_request_error until S3-backed URL delivery lands.","enum":["b64_json"],"type":"string"},"size":{"description":"Output image dimensions as WxH (e.g. 1024x1024).","type":"string"}},"required":["model","prompt"],"type":"object"},"ImagesResponse":{"properties":{"created":{"description":"Unix timestamp (seconds) when the response was created.","type":"integer"},"data":{"items":{"properties":{"b64_json":{"description":"Base64-encoded image bytes.","type":"string"}},"required":["b64_json"],"type":"object"},"type":"array"},"usage":{"properties":{"image_count":{"description":"Number of images generated (the billable unit).","type":"integer"}},"required":["image_count"],"type":"object"}},"required":["created","data","usage"],"type":"object"},"InferencePreflight":{"description":"The verdict for one (organization, model) pair: what would happen if the model were invoked right now.\n","properties":{"funded":{"description":"Whether a pay-as-you-go request could be paid for right now: a positive credit balance AND no self-set spending cap already at its limit. A boolean and never a figure — this route is on the inference read scope and must not disclose the balance.","type":"boolean"},"message":{"description":"A ready-to-print sentence, phrased for this exact plan/credit combination. Prefer it over composing one from verdict and reason: it improves without a client release.","type":"string"},"model":{"description":"The canonical catalog id the request resolved to.","type":"string"},"plan_covered":{"description":"Whether the subscription tier covers this model. A property of the PLATFORM, not of the caller — it stays true for an organization that has no subscription, which is what makes the \"this is included in the plan\" upsell honest.","type":"boolean"},"reason":{"description":"Stable code explaining the verdict, for clients that need their own wording. Absent on a plain `ok` that needs no explanation. `api_key_budget_exceeded`, `member_spend_limit_reached` and `model_not_allowed_for_key` come with `verdict: block` and only for a key that has a budget or a model allowlist, or a member that has a spend limit.","enum":["plan_covered","payg","not_plan_covered","subscription_quota_exhausted","insufficient_balance","daily_limit_reached","monthly_limit_reached","api_key_budget_exceeded","member_spend_limit_reached","model_not_allowed_for_key","preflight_unavailable"],"type":"string"},"subscribed":{"description":"Whether the organization is entitled to the plan right now.","type":"boolean"},"verdict":{"description":"`ok` — the request will be served (covered by the plan, or payable). `warn` — it will be served AND charged in a way the caller may not expect. `block` — it will be refused (402 or 429).","enum":["ok","warn","block"],"type":"string"}},"required":["model","verdict","message","plan_covered","subscribed","funded"],"type":"object"},"JSONSchemaFormat":{"description":"The schema is forwarded byte-for-byte. It may be at most 64 KiB and nested at most 32 levels deep; larger or deeper schemas return 400 unsupported_parameter (param response_format).\n","properties":{"description":{"type":"string"},"name":{"type":"string"},"schema":{"additionalProperties":true,"description":"A JSON Schema object describing the reply.","type":"object"},"strict":{"description":"When true, the reply is guaranteed to match the schema; only models listing \"structured_outputs\" accept it.\n","nullable":true,"type":"boolean"}},"required":["name"],"type":"object"},"Model":{"properties":{"aliases":{"items":{"type":"string"},"type":"array"},"author":{"description":"Model creator/organization (e.g. \"Meta\", \"Qwen\", \"Black Forest Labs\").","type":"string"},"category":{"description":"Browse-facing category; mirrors modality (chat/image/video/embedding).","type":"string"},"coding_agent_rank":{"description":"The model's position in the coding-agent list, 1 first. Present exactly when `recommended_for` contains `coding-agents`; a setup command makes the rank-1 model the default.","minimum":1,"type":"integer"},"context_length":{"type":"integer"},"created":{"description":"Always present for OpenAI-client compatibility. Currently 0 for every model (the catalog carries no creation date); do not sort on it.","type":"integer"},"fine_tunable":{"description":"Whether this model can be used as the base of a managed fine-tuning job. The tunable allowlist is operator-curated and much narrower than the serving catalog, so this is false for almost every model. Absent means false.","type":"boolean"},"fine_tune_base_model":{"description":"The exact id to send as `model` when creating a fine-tuning job for this entry. Present only when `fine_tunable` is true, and deliberately distinct from `id`: the allowlist matches the customer-facing base id (e.g. `qwen2.5-7b-instruct`), which a served model normally carries as an alias rather than as its namespaced catalog id.","type":"string"},"id":{"type":"string"},"max_completion_tokens":{"description":"The model's maximum output, in tokens, from the catalog. Omitted when the catalog lists none.","type":"integer"},"metrics":{"$ref":"#/components/schemas/ModelMetrics"},"modality":{"enum":["chat","image","video","embedding"],"type":"string"},"object":{"enum":["model"],"type":"string"},"owned_by":{"description":"The model's author (the same value as `author`), or \"deeprelay\" when the author is unknown.","type":"string"},"parameters":{"description":"Typed parameter descriptors for media (image/video) models — name, type, enum/bounds/default. Playgrounds render input forms from these.","items":{"properties":{"default":{"description":"Default value for the field (any JSON scalar)."},"enum":{"items":{"type":"string"},"type":"array"},"max":{"type":"integer"},"min":{"type":"integer"},"name":{"type":"string"},"type":{"enum":["enum","int","string","bool"],"type":"string"}},"required":["name","type"],"type":"object"},"type":"array"},"plan_covered":{"description":"\"Included in the flat subscription\": whether the subscription tier covers this model. A model-level fact, the same for every caller. A covered model rides the plan's quota while the subscription is entitled and has quota left; everything else bills pay-as-you-go at list rates, as does a covered model once the quota is spent. Absent means false.\n\nCoverage is a property of the platform, not of the caller: the flag reads the same for every organization, subscribed or not. Use `/billing/subscription` for what the caller's own plan has left.","type":"boolean"},"pricing":{"properties":{"cached_input_per_1m_tokens_cents":{"description":"cached_input_per_1m_tokens_microcents rounded to whole cents; omitted when the exact rate rounds below half a cent.","type":"integer"},"cached_input_per_1m_tokens_microcents":{"description":"Exact per-1M-token rate in micro-cents (1e-6 cent) for the cache-HIT part of a chat prompt (usage.prompt_tokens_details.cached_tokens). Present only on models with a published cached-input rate; absent means no discount — every prompt token bills at input_per_1m_tokens_cents. Treat this field as the presence signal: cached rates are routinely sub-cent, so the rounded cents field below is often omitted.","type":"integer"},"currency":{"type":"string"},"input_per_1m_tokens_cents":{"type":"integer"},"off_peak":{"$ref":"#/components/schemas/ModelRate"},"output_per_1m_tokens_cents":{"type":"integer"},"peak":{"$ref":"#/components/schemas/ModelRate"},"peak_windows_utc":{"description":"The daily UTC ranges during which the `peak` rates apply, start inclusive and end exclusive (\"24:00\" is a valid end; a range whose end sorts before its start wraps midnight). Present only on a model priced by time of day.","items":{"$ref":"#/components/schemas/PeakWindow"},"type":"array"},"per_image_cents":{"type":"integer"},"per_image_microcents":{"description":"Exact per-image rate in micro-cents (1e-6 cent). Prefer this for image models — per_image_cents rounds sub-cent prices to 0.","type":"integer"},"per_mpxl_microcents":{"description":"Price per output megapixel in micro-cents (1e-6 cent) for image models metered per megapixel. Mutually exclusive with the per-image fields.","type":"integer"},"per_search_microcents":{"description":"Pay-as-you-go price of one server-side web search, in micro-cents (1e-6 cent); 200000 is $2.00 per 1,000 searches. Present only on models with `web_search: true`. Charged once per search that returns results, on top of the token cost; a failed search is free. The search results the model reads are ordinary prompt tokens at the input rate. Searches inside a subscriber's monthly allowance are covered by the plan, and searches past it (when the organization opted in on `/search/settings`) bill at the separate overflow price shown there, not at this rate.","format":"int64","type":"integer"},"per_video_microcents":{"description":"Representative per-clip price in micro-cents (1e-6 cent) for video models — a \"from\" price taken from the provider's published example rate.","type":"integer"},"per_video_second_cents":{"type":"integer"},"per_video_second_microcents":{"description":"Exact per-second-of-output-video rate in micro-cents (1e-6 cent). Prefer this for video models.","type":"integer"},"period":{"description":"Present only on a model priced by time of day. Which side of the peak schedule the instant this response was rendered falls on; the flat rate fields above are the rates in effect at that instant. A request is billed at the rate in effect when it ARRIVES (UTC), so a caller planning a call for later should price it from `peak` / `off_peak` and `peak_windows_utc`.","enum":["peak","off_peak"],"type":"string"}},"type":"object"},"reasoning_efforts":{"description":"The `reasoning_effort` values this model accepts (for example `none`, `low`, `medium`, `high`), as the catalog lists them. Omitted when the catalog lists none.","items":{"type":"string"},"type":"array"},"recommended_for":{"description":"The audiences deeprelay recommends this model for. The only value today is `coding-agents`: models tested with coding agents, which `deeprelay setup \u003cagent\u003e` configures. More values may be added. Omitted on every model that is not recommended.","items":{"type":"string"},"type":"array"},"status":{"enum":["active","coming_soon","deprecated"],"type":"string"},"supported_parameters":{"items":{"type":"string"},"type":"array"},"tier":{"description":"The serving tier this entry prices: `serverless` or `economy` today, and more values may be added. A model served on both tiers appears twice: under its bare id for `serverless`, and under the `:economy`-suffixed id for `economy`. Send the id of the tier you want. Omitted on `coming_soon` placeholder entries.","type":"string"},"usage_factor_milli":{"description":"How heavily this model draws on the subscription quota, ×1000: each token counts as usage_factor_milli / 1000 weighted tokens toward the quota (and in cost_details.quota_units_used). Omitted at the default 1000 (1.0×).","type":"integer"},"web_search":{"description":"Whether the model can run the server-side `web_search` tool on `/messages` and `/responses`: deeprelay runs the search itself and feeds the results back to the model. True exactly where the model supports tool calling. Always present. A request that sends the tool to a model with `web_search: false` is rejected with 400 `web_search_unsupported_model`.","type":"boolean"},"weights_verification":{"$ref":"#/components/schemas/WeightsVerification"}},"required":["id","object","created","owned_by","modality","status","pricing"],"type":"object"},"ModelList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/Model"},"type":"array"},"object":{"enum":["list"],"type":"string"}},"required":["object","data"],"type":"object"},"ModelMetrics":{"description":"Measured performance over a rolling window, from deeprelay's own customer traffic only. A figure is omitted (never zero) when it has too few samples behind it; the counts and minimums are always present so a reader can tell why.","properties":{"as_of":{"description":"The hour the figures were computed for.","format":"date-time","type":"string"},"insufficient_data":{"description":"True when `sample_count` is below `min_samples`.","type":"boolean"},"min_samples":{"description":"Samples needed before `success_rate` is published.","type":"integer"},"min_stream_samples":{"description":"Streamed samples needed before the TTFT and tokens/s figures are published.","type":"integer"},"precision":{"description":"What deeprelay can state about the numeric precision this model is served at: `full` (always full precision), `mixed` (some serving capacity is quantized) or `unverified` (not verified; the default).","enum":["full","mixed","unverified"],"type":"string"},"sample_count":{"format":"int64","type":"integer"},"stream_sample_count":{"format":"int64","type":"integer"},"success_rate":{"maximum":1,"minimum":0,"type":"number"},"tokens_per_second_p50":{"description":"Median decode rate after the first token, in tokens per second.","type":"number"},"ttft_p50_ms":{"description":"Median time to first token, in milliseconds.","type":"number"},"window_days":{"type":"integer"}},"required":["window_days","as_of","sample_count","stream_sample_count","min_samples","min_stream_samples","insufficient_data","precision"],"type":"object"},"ModelRate":{"description":"One per-1M-token rate triple, in rounded cents and exact micro-cents (1e-6 cent). Prefer the micro-cent fields for arithmetic; cached rates are routinely sub-cent and their cents field is then omitted.","properties":{"cached_input_per_1m_tokens_cents":{"type":"integer"},"cached_input_per_1m_tokens_microcents":{"type":"integer"},"input_per_1m_tokens_cents":{"type":"integer"},"input_per_1m_tokens_microcents":{"type":"integer"},"output_per_1m_tokens_cents":{"type":"integer"},"output_per_1m_tokens_microcents":{"type":"integer"}},"type":"object"},"OpenAIErrorEnvelope":{"properties":{"error":{"properties":{"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"type":"string"},"param":{"description":"On a 429 subscription_quota_exhausted this is the tripped gate: monthly_input_tokens, monthly_output_tokens, weekly_input_tokens, weekly_output_tokens, max_usage, input_overshoot (this request's estimated input alone exceeds what is left in the tighter of the monthly and rolling 7-day input allowances), or output_overshoot (the same for the estimated output — the completion bound, max_tokens or the model's declared maximum). On an overshoot the quota is not exhausted — send a smaller request (or a smaller max_tokens) or add credits.","nullable":true,"type":"string"},"quota_frees_at":{"description":"Only on a 429 `subscription_quota_exhausted` whose `param` is `weekly_input_tokens` or `weekly_output_tokens` (the rolling 7-day caps): the RFC 3339 time at which enough plan-covered usage ages out of the rolling window for requests to ride the plan again — the earlier of the two windows' free times, each computed from its own dimension. The message names the same time. Omitted on every other error, and omitted when it cannot be computed. There is no Retry-After on this 429 and `x-should-retry` is `false`: wait until this time or add credits, rather than auto-retrying.","format":"date-time","type":"string"},"type":{"description":"OpenAI error class (e.g. invalid_request_error, authentication_error, billing_error).","type":"string"}},"required":["message","type","code"],"type":"object"}},"required":["error"],"type":"object"},"PeakWindow":{"description":"One daily UTC time range during which a model's peak rates apply.","properties":{"end":{"description":"\"HH:MM\" UTC, exclusive (\"24:00\" means until midnight).","type":"string"},"start":{"description":"\"HH:MM\" UTC, inclusive.","type":"string"}},"required":["start","end"],"type":"object"},"Problem":{"properties":{"code":{"enum":["unauthenticated","invalid_api_key","insufficient_scope","not_found","idempotency_conflict","idempotency_mismatch","validation_failed","invalid_gpu_type","disk_unavailable","disk_exceeds_offering","model_too_large","gpu_vram_too_small","price_changed","insufficient_balance","quota_exceeded","rate_limited","forbidden","invalid_request","keys_manage_required","scope_exceeds_caller","export_too_large","api_key_budget_exceeded","member_spend_limit_reached","model_not_allowed_for_key","creator_check_unavailable","switch_renewal_unpaid","checkout_paused","internal_error","operation_failed"],"type":"string"},"detail":{"type":"string"},"field":{"description":"On a 422 `validation_failed`, the request field that failed.","type":"string"},"max_rows":{"description":"On a 422 `export_too_large`, the export row cap.","type":"integer"},"request_id":{"type":"string"},"status":{"type":"integer"},"title":{"type":"string"},"type":{"format":"uri","type":"string"}},"required":["type","title","status","detail","code","request_id"],"type":"object"},"RefereeStatus":{"description":"Present only when the organization signed up through someone else's invite link: where that referral sits and how far the organization is from unlocking its own reward.\n","properties":{"gate":{"description":"What qualifies this referral. `deposit` (net real deposits) for current referrals; `spend` (inference usage) for referrals made under the earlier program, which keep their original terms.","enum":["deposit","spend"],"type":"string"},"goal_cents":{"description":"The amount that qualifies the referral.","format":"int64","type":"integer"},"hold_until":{"description":"When the hold ends and both halves pay out. Absent while pending.","format":"date-time","type":"string"},"progress_cents":{"description":"Progress toward the gate so far — net deposits for `deposit`, inference spend for `spend`.","format":"int64","type":"integer"},"qualified_at":{"description":"When the gate was met. Absent while pending.","format":"date-time","type":"string"},"qualify_spend_cents":{"deprecated":true,"description":"Deprecated alias of `goal_cents`.","format":"int64","type":"integer"},"reward_cents":{"description":"What this organization receives on settlement.","format":"int64","type":"integer"},"settled_at":{"description":"When the reward was paid. Absent until settled.","format":"date-time","type":"string"},"spent_cents":{"deprecated":true,"description":"Deprecated alias of `progress_cents`.","format":"int64","type":"integer"},"status":{"description":"Lifecycle state. `pending` until the gate is met; `qualified` inside the hold; `settled` once paid; `reversed` when the qualifying deposit was refunded after payout and both rewards were taken back.","enum":["pending","qualified","settled","rejected","reversed"],"type":"string"}},"required":["status","spent_cents","qualify_spend_cents","reward_cents"],"type":"object"},"Referral":{"description":"The organization's referral-program view: its invite link, the program terms, its referrer stats, and (only when it was itself referred) its referee progress. Amounts are USD cents.\n","properties":{"code":{"description":"The organization's shareable referral code, e.g. `DR-7KQ2-M4XP`.","example":"DR-7KQ2-M4XP","type":"string"},"commission_earned_cents":{"description":"Total deposit commission credited to this organization as a referrer (separate from the flat bonus in `earned_cents`).","format":"int64","type":"integer"},"commission_pct":{"description":"Percentage of each real deposit a referred friend makes that is credited to the referrer as spend-only promotional credit. 0 means the commission is off.","type":"integer"},"earned_cents":{"description":"Total credit paid to this organization as a referrer.","format":"int64","type":"integer"},"hold_days":{"description":"Days between qualification and payout (chargeback hold).","type":"integer"},"invite_url":{"description":"The ready-to-share invite link, `{site}/r/{code}`.","example":"https://deeprelay.ai/r/DR-7KQ2-M4XP","format":"uri","type":"string"},"pending":{"description":"Friends who signed up through the link but have not yet reached the qualifying deposit.","type":"integer"},"qualified":{"description":"Friends past the qualifying deposit whose reward is inside the chargeback hold.","type":"integer"},"qualify_deposit_cents":{"description":"The friend's cumulative net real deposits (card, auto-pay and crypto, less refunds) that qualify a referral.","format":"int64","type":"integer"},"qualify_spend_cents":{"deprecated":true,"description":"Deprecated — referrals now qualify on deposits. Mirrors `qualify_deposit_cents`.","format":"int64","type":"integer"},"referee_reward_cents":{"description":"Credit paid to the referred friend per settled referral.","format":"int64","type":"integer"},"referred":{"$ref":"#/components/schemas/RefereeStatus"},"reward_cents":{"description":"Credit paid to the referrer per settled referral.","format":"int64","type":"integer"},"settled":{"description":"Referrals that have paid out.","type":"integer"}},"required":["code","invite_url","pending","qualified","settled","earned_cents","reward_cents","referee_reward_cents","qualify_spend_cents","hold_days","commission_pct","commission_earned_cents"],"type":"object"},"RequestAttempt":{"description":"One model in a request's backup-model trail.","properties":{"duration_ms":{"description":"How long this model's attempt took, in milliseconds.","format":"int64","type":"integer"},"model":{"type":"string"},"outcome":{"description":"`ok`: this model served. `failed`: it was dialled and every provider failed before the first token. `skipped`: it was passed over without being dialled.","enum":["ok","failed","skipped"],"type":"string"},"reason":{"description":"Why the attempt failed (a public error code such as `upstream_timeout`) or was skipped: `model_not_allowed_for_key`, `subscription_quota_exhausted`, `api_key_budget_exceeded`, `member_spend_limit_reached`, `insufficient_balance`, `daily_limit_reached`, `spending_limit_reached`, `wallet_only` (not on the plan roster, for a plan-covered request), `unavailable`, `capability_mismatch`, `context_window_too_small`, `breaker_open` or `deadline_exhausted` (too little time was left to start another model).","type":"string"},"tries":{"description":"How many upstream attempts were made for this model. 0 for a skipped model.","type":"integer"},"ttft_ms":{"description":"Time to first token, for the model that served a streamed call.","format":"int64","type":"integer"}},"required":["model","outcome","duration_ms","tries"],"type":"object"},"RequestLogEntry":{"description":"One inference call as the request log records it. Never carries prompt or completion content. Optional numeric fields are omitted when they do not apply (for example token counts on a rejected call).","properties":{"attempts":{"description":"Present when backup models were tried: every model the request went through, in order, including skipped backups. Upstream providers are never named.","items":{"$ref":"#/components/schemas/RequestAttempt"},"maxItems":4,"type":"array"},"cached_tokens":{"format":"int64","type":"integer"},"client_request_id":{"description":"The caller's `X-Client-Request-Id`, when one was sent.","type":"string"},"completion_tokens":{"format":"int64","type":"integer"},"cost":{"description":"The call's cost in USD: an exact decimal with up to 8 places, emitted as a JSON number. On a pay-as-you-go row (`plan_covered: false`) it is the amount billed to the wallet, the same figure as `usage.cost`. On a plan-covered row it is the list-price equivalent, not a charge: tokens plus every web search the allowance covered, at the per-search list price; the subscription paid for it. A paid web-search overflow fee is not included; it is in `search_overflow_fee_micro_cents`. `0` for a rejected or failed call.","type":"number"},"cost_micro_cents":{"description":"The same amount as an integer number of micro-cents.","format":"int64","type":"integer"},"duration_ms":{"format":"int64","type":"integer"},"error_code":{"description":"The `X-Deeprelay-Error-Code` the caller received, on a failed or rejected call.","type":"string"},"fallback_reason":{"description":"Set when backup models were tried: the requested model's public error code (for example `upstream_timeout`). Rows with it set are the ones `fallback=true` returns.","type":"string"},"finish_reason":{"type":"string"},"had_tool_calls":{"type":"boolean"},"http_status":{"type":"integer"},"ignored_params":{"description":"Request parameters that were accepted and ignored.","items":{"type":"string"},"type":"array"},"image_count":{"type":"integer"},"key_id":{"description":"The `api_key_id` of the key that sent the call. Absent for dashboard playground calls.","type":"string"},"key_name":{"type":"string"},"key_prefix":{"description":"The key's public prefix. The secret is never stored or returned.","type":"string"},"model":{"type":"string"},"occurred_at":{"format":"date-time","type":"string"},"plan_covered":{"description":"Whether the subscription plan paid for the call.","type":"boolean"},"prompt_tokens":{"format":"int64","type":"integer"},"quota_units":{"description":"Weighted tokens drawn from the subscription quota.","format":"int64","type":"integer"},"reasoning_tokens":{"format":"int64","type":"integer"},"request_id":{"description":"The `X-Request-Id` the call was answered with.","type":"string"},"requested_model":{"description":"The model the request asked for. Always present; equal to `model` unless a backup model was tried.","type":"string"},"search_count":{"description":"Server-side web searches billed on this call (failed searches are not counted). Omitted when the call ran none.","format":"int64","type":"integer"},"search_overflow_count":{"description":"The subset of `search_count` that ran past the plan's monthly search allowance and was billed to the wallet (the organization opted in on `/search/settings`). Never greater than `search_count`. Omitted when none did.","format":"int64","type":"integer"},"search_overflow_fee_micro_cents":{"description":"The wallet charge, in micro-cents, for this call's `search_overflow_count` searches at the overflow price. It is a field of this request, not a separate request: the log never lists the fee as its own row. Omitted when there was no overflow fee.","format":"int64","type":"integer"},"search_result_tokens":{"description":"The subset of `prompt_tokens` that were web-search results fed back to the model. Already counted in `prompt_tokens`; do not add them again. Omitted when the call ran no search.","format":"int64","type":"integer"},"served_model":{"description":"The model that produced the response. Omitted when no model served (a failed or rejected call). When a backup served, `model` is this model and the row is billed at its price.","type":"string"},"status":{"enum":["ok","error","cancelled","rejected"],"type":"string"},"suppressed_count":{"description":"For a rejected row: how many further identical rejections in the same minute were folded into this one.","type":"integer"},"surface":{"enum":["chat","messages","responses","embeddings","image","video"],"type":"string"},"ttft_ms":{"description":"Time to first token, for a streamed call.","format":"int64","type":"integer"},"video_seconds":{"type":"integer"}},"required":["request_id","occurred_at","surface","model","status","cost","cost_micro_cents","plan_covered"],"type":"object"},"RequestLogPage":{"properties":{"data":{"items":{"$ref":"#/components/schemas/RequestLogEntry"},"type":"array"},"next_cursor":{"description":"Opaque cursor for the next page, or null when there are no more rows.","type":["string","null"]},"window_days":{"description":"How many days back the request log reaches (90).","type":"integer"}},"required":["data","next_cursor","window_days"],"type":"object"},"ResponseFormatJSONObject":{"description":"The reply is a single valid JSON object.","properties":{"type":{"enum":["json_object"],"type":"string"}},"required":["type"],"type":"object"},"ResponseFormatJSONSchema":{"properties":{"json_schema":{"$ref":"#/components/schemas/JSONSchemaFormat"},"type":{"enum":["json_schema"],"type":"string"}},"required":["type","json_schema"],"type":"object"},"ResponseFormatText":{"properties":{"type":{"enum":["text"],"type":"string"}},"required":["type"],"type":"object"},"SpendingLimit":{"properties":{"auto_terminate_hours":{"nullable":true,"type":"integer"},"billing_cycle_start":{"format":"date-time","type":"string"},"current_day_spend_cents":{"type":"integer"},"current_month_spend_cents":{"type":"integer"},"current_month_spend_dollars":{"type":"number"},"daily_limit_cents":{"nullable":true,"type":"integer"},"daily_limit_dollars":{"nullable":true,"type":"number"},"enforcement":{"enum":["stop_running","block_launch","notify_only"],"type":"string"},"monthly_limit_cents":{"type":"integer"},"monthly_limit_dollars":{"type":"number"},"percent_used":{"type":"number"}},"required":["monthly_limit_cents","monthly_limit_dollars","current_month_spend_cents","current_month_spend_dollars","percent_used","billing_cycle_start","enforcement","current_day_spend_cents"],"type":"object"},"StreamOptions":{"properties":{"include_usage":{"default":true,"description":"When true, the final stream chunk carries usage totals (CHAT-03).","type":"boolean"}},"type":"object"},"Subscription":{"description":"The organization's subscription: entitlement, billing period, the plan it is on with that plan's configured quota, consumption against it, and the plans this deployment offers. The console and this endpoint assemble it from the same source, so the two surfaces cannot report different numbers.\n","properties":{"cancel_at_period_end":{"description":"Whether the subscription stops at the end of the current period instead of renewing.","type":"boolean"},"current_period_end":{"description":"End of the current billing period. Absent under the same condition as current_period_start.","format":"date-time","type":"string"},"current_period_start":{"description":"Start of the current billing period. Absent until the billing provider has reported a period for this subscription.","format":"date-time","type":"string"},"plan":{"$ref":"#/components/schemas/SubscriptionPlan"},"plans":{"description":"Every plan this deployment offers, smallest first (Base, then Plus when it is configured), each with its price and quota. The keys are the accepted `plan_key` values for checkout and upgrade. Empty when nothing can be bought here.","items":{"$ref":"#/components/schemas/SubscriptionTier"},"type":"array"},"quota":{"$ref":"#/components/schemas/SubscriptionQuota"},"status":{"description":"Raw subscription status — \"active\", \"trialing\", \"past_due\", \"canceled\" and so on — or \"none\" when the organization never subscribed.","type":"string"},"subscribed":{"description":"Whether the organization is entitled to the plan right now. Only an `active` or `trialing` subscription entitles, including a short grace window past the period end that absorbs renewal webhook lag; a failed payment (`past_due`) cuts access immediately. Render from this; do not re-derive entitlement from `status` and the period bounds.","type":"boolean"},"usage":{"$ref":"#/components/schemas/SubscriptionUsage"}},"required":["subscribed","status","cancel_at_period_end","plan","quota","plans"],"type":"object"},"SubscriptionCheckoutRequest":{"description":"Optional overrides for a checkout mint. Every field has a deployment default, so an empty body is the normal call.\n","properties":{"cancel_url":{"description":"Where the provider returns if checkout is abandoned. Defaults to the deployment's configured URL. Must be on the deployment's dashboard origin, else 422 `validation_failed`.","type":"string"},"method":{"description":"`card` (dollars, card or Link; the default) or `upi` (rupees, UPI AutoPay). On a subscribed organization, the other currency's method switches the existing plan's billing.","enum":["card","upi"],"type":"string"},"plan_key":{"description":"The plan to buy — `flat` (Base, the default) or `plus`. Pins the plan the client displayed: an unknown or un-offered key is a 400 rather than a silent purchase of a different tier.","type":"string"},"success_url":{"description":"Where the provider returns after a completed payment. Defaults to the deployment's configured URL. Must be on the deployment's dashboard origin, else 422 `validation_failed`.","type":"string"}},"type":"object"},"SubscriptionCheckoutSession":{"properties":{"checkout_url":{"description":"The hosted page the customer must open to pay.","type":"string"},"first_charge_at":{"description":"For a switch, when the new currency is first charged (the current renewal date).","format":"date-time","type":"string"},"plan":{"$ref":"#/components/schemas/SubscriptionPlan"},"session_id":{"description":"Checkout session id, for support correlation.","type":"string"},"switch_to":{"description":"Set when the checkout switches an existing plan's billing currency.","enum":["card","upi"],"type":"string"}},"required":["checkout_url","session_id","plan"],"type":"object"},"SubscriptionModelUsage":{"description":"One model's plan-covered usage in a window. Cached input is part of `input_tokens` and counts toward the allowance in full; it is broken out only so you can see how much of your input was cache reads.","properties":{"cached_input_tokens":{"description":"The part of `input_tokens` served from a prompt cache. A subset, never an addition.","format":"int64","type":"integer"},"input_tokens":{"description":"Raw input tokens served, cached input included.","format":"int64","type":"integer"},"model":{"description":"Catalog model id, e.g. `deeprelay/kimi-k3`.","type":"string"},"output_tokens":{"description":"Raw output tokens served, reasoning included.","format":"int64","type":"integer"},"usage_factor_milli":{"description":"The model's usage factor in thousandths (1000 = ×1, 24000 = ×24).","format":"int64","type":"integer"},"weighted_input_tokens":{"description":"`input_tokens` × the usage factor: what this model drew from the input allowance.","format":"int64","type":"integer"},"weighted_output_tokens":{"description":"`output_tokens` × the usage factor: what this model drew from the output allowance.","format":"int64","type":"integer"}},"required":["model","input_tokens","cached_input_tokens","output_tokens","usage_factor_milli","weighted_input_tokens","weighted_output_tokens"],"type":"object"},"SubscriptionPaygEstimate":{"description":"What this period's plan-covered traffic would have cost on pay-as-you-go, read off the request ledger. `list_micro_cents` is exact: every covered request records its list price with the model's cached-input rate honored. `uncached_micro_cents` is the same traffic with every cache read billed at the full input rate (today's off-peak rates, so approximate); the gap is the cached-input discount, which the plan allowance does not give (cached input counts in full). The token counts are the raw input and its cached subset over the period. Omitted from `usage` until the period has covered usage.","properties":{"cached_input_tokens":{"description":"The cache-read subset of `input_tokens`.","format":"int64","type":"integer"},"input_tokens":{"format":"int64","type":"integer"},"list_micro_cents":{"description":"The period's covered traffic at pay-as-you-go list rates, in micro-cents.","format":"int64","type":"integer"},"period_end":{"format":"date-time","type":"string"},"period_start":{"format":"date-time","type":"string"},"uncached_micro_cents":{"description":"The same traffic with cache reads at the full input rate, in micro-cents.","format":"int64","type":"integer"}},"required":["period_start","period_end","list_micro_cents","uncached_micro_cents","input_tokens","cached_input_tokens"],"type":"object"},"SubscriptionPlan":{"description":"The plan the organization is on — or, for an organization that never subscribed, the Base plan it would get. Always present: it is what a point of purchase renders before sending anyone to pay.\n","properties":{"key":{"description":"Stable identifier for the plan: `flat` (Base) or `plus`. Send it as `plan_key` on /billing/subscription/checkout to pin the plan you displayed, or on /billing/subscription/upgrade to name the target.","type":"string"},"name":{"description":"Human-facing plan name (\"Base\", \"Plus\").","type":"string"},"price":{"$ref":"#/components/schemas/SubscriptionPrice"}},"required":["key","name"],"type":"object"},"SubscriptionPortalRequest":{"description":"Optional overrides for a billing-portal mint.","properties":{"return_url":{"description":"Where the provider returns when the customer leaves the portal. Defaults to the deployment's configured URL.","type":"string"}},"type":"object"},"SubscriptionPortalSession":{"properties":{"portal_url":{"description":"The hosted billing portal — cancel, resume, change payment method, download invoices.","type":"string"}},"required":["portal_url"],"type":"object"},"SubscriptionPrice":{"description":"The plan's recurring charge, read live from the billing provider — the single source of truth, never a number embedded in code.\n\nABSENT means \"price unavailable right now\" (billing unconfigured, or the provider could not be reached). It NEVER means free. Render the plan without a figure rather than substituting one; the hosted checkout page always shows the real amount.\n","properties":{"amount_cents":{"description":"Amount in the currency's minor unit (299 = $2.99).","format":"int64","type":"integer"},"currency":{"description":"ISO 4217 currency code, lower-case (e.g. \"usd\").","type":"string"},"interval":{"description":"Recurrence unit — \"day\", \"week\", \"month\" or \"year\".","type":"string"},"interval_count":{"description":"How many intervals between charges (\"month\", 1 = monthly).","format":"int64","type":"integer"}},"required":["amount_cents","currency","interval","interval_count"],"type":"object"},"SubscriptionQuota":{"description":"A plan's configured allowances — the denominators of the usage meter. On `/billing/subscription` it is the quota of the plan the organization is on; always present, including for an organization that has not subscribed, where it describes what the Base plan would provide. Each entry of `plans` carries its own.\n\nToken figures are WEIGHTED tokens: every model carries a usage factor, so a model at factor 2 spends two of these per token it serves.\n","properties":{"input_tokens_monthly":{"description":"Weighted input tokens included per billing period.","format":"int64","type":"integer"},"max_usage_micro_cents":{"description":"Per-period backstop on list-price metered cost, in micro-cents (1e-6 cent). 0 means no backstop is configured on this deployment.","format":"int64","type":"integer"},"output_tokens_monthly":{"description":"Weighted output tokens included per billing period. Reasoning tokens are output tokens.","format":"int64","type":"integer"},"payg_discount_bp":{"deprecated":true,"description":"Deprecated and always 0. There is no subscriber discount on pay-as-you-go spend: a request that exhausts the plan quota bills at list rates, the same rates an org without a subscription pays. The field is retained so existing deserializers keep working and will be removed in the next breaking revision. Do not display it.","format":"int64","type":"integer"},"searches_monthly":{"description":"Server-side web searches included per billing period. When they are used up, further searches return an in-band \"allowance exhausted\" result to the model and the request still succeeds on the plan, unless the organization opted into overflow on `/search/settings`. 0 means the plan includes no searches.","format":"int64","type":"integer"},"weekly_input_tokens":{"description":"Weighted input tokens allowed over a ROLLING 7-day window. This cap slides with the clock; it does not reset with the billing period. 0 means no weekly input cap is configured on this deployment.","format":"int64","type":"integer"},"weekly_output_tokens":{"description":"Weighted output tokens allowed over a ROLLING 7-day window, independent of the weekly input cap. Slides with the clock; it does not reset with the billing period. Reasoning tokens are output tokens. 0 means no weekly output cap is configured.","format":"int64","type":"integer"}},"required":["input_tokens_monthly","output_tokens_monthly","weekly_input_tokens","weekly_output_tokens","max_usage_micro_cents","payg_discount_bp"],"type":"object"},"SubscriptionTier":{"description":"One plan as the plan catalogue lists it: identity, price and the allowance it buys. Every offered plan covers the same models at the same usage factors — Plus is Base with a larger allowance.\n","properties":{"key":{"description":"Stable plan identifier — `flat` (Base) or `plus`.","type":"string"},"name":{"description":"Human-facing plan name.","type":"string"},"price":{"$ref":"#/components/schemas/SubscriptionPrice"},"quota":{"$ref":"#/components/schemas/SubscriptionQuota"}},"required":["key","name","quota"],"type":"object"},"SubscriptionUpgradeRequest":{"properties":{"plan_key":{"description":"The plan to move to (`plus`). Must be bigger than the plan the organization is on.","type":"string"}},"required":["plan_key"],"type":"object"},"SubscriptionUsage":{"description":"Consumption against the quota, from the same factor-weighted read the request-time gate evaluates — so this meter and enforcement cannot disagree.\n\nPresent only for an entitled subscription whose meter could be read. Its absence is never a statement that nothing was used.\n","properties":{"by_model":{"description":"This period's plan-covered usage split by model: the raw tokens each model served, its usage factor, and the weighted tokens they drew from the allowance. Heaviest weighted input first. Each model's weighted figures round down on their own, so they can sum to slightly less than `weighted_input_tokens` / `weighted_output_tokens`, which stay authoritative. Omitted when the breakdown could not be read or there is no covered usage.","items":{"$ref":"#/components/schemas/SubscriptionModelUsage"},"type":"array"},"cost_micro_cents":{"description":"List-price metered cost of this period's plan traffic in micro-cents (1e-6 cent), against `quota.max_usage_micro_cents`.","format":"int64","type":"integer"},"payg_estimate":{"$ref":"#/components/schemas/SubscriptionPaygEstimate"},"period_end":{"description":"End of that window.","format":"date-time","type":"string"},"period_start":{"description":"Start of the window the period figures were summed over: the subscription's current period, or the UTC calendar month when the billing provider has not reported one yet.","format":"date-time","type":"string"},"recent_payg_calls_24h":{"description":"Wallet-billed calls while subscribed, e.g. input-overshoot or quota-gate pay-as-you-go fallbacks and models the plan does not cover. Recent means the last 24 hours, clamped to the current period. Omitted when the count could not be read; its absence means unknown, not zero.","format":"int64","type":"integer"},"searches":{"description":"Plan-covered web searches run this period, against `quota.searches_monthly`. Overflow searches billed to the wallet are not counted here.","format":"int64","type":"integer"},"weekly_by_model":{"description":"The same breakdown over the trailing 7 days, against the weekly caps. Omitted under the same conditions as `by_model`.","items":{"$ref":"#/components/schemas/SubscriptionModelUsage"},"type":"array"},"weekly_weighted_input_tokens":{"description":"Weighted input tokens over the trailing 7 days, against `quota.weekly_input_tokens`. A rolling figure, not a period one — it can be non-zero moments after a period rolls over.","format":"int64","type":"integer"},"weekly_weighted_output_tokens":{"description":"Weighted output tokens over the trailing 7 days, against `quota.weekly_output_tokens`. A rolling figure, like `weekly_weighted_input_tokens`.","format":"int64","type":"integer"},"weighted_input_tokens":{"description":"Weighted input tokens spent this period, against `quota.input_tokens_monthly`.","format":"int64","type":"integer"},"weighted_output_tokens":{"description":"Weighted output tokens spent this period, against `quota.output_tokens_monthly`.","format":"int64","type":"integer"}},"required":["period_start","period_end","weighted_input_tokens","weighted_output_tokens","weekly_weighted_input_tokens","weekly_weighted_output_tokens","cost_micro_cents"],"type":"object"},"Tool":{"properties":{"function":{"$ref":"#/components/schemas/ToolFunction"},"type":{"enum":["function"],"type":"string"}},"required":["type","function"],"type":"object"},"ToolCall":{"description":"A function call the model emitted.","properties":{"function":{"$ref":"#/components/schemas/ToolCallFunction"},"id":{"description":"Identifier for this call. Echo it back as tool_call_id on the message carrying the result.\n","type":"string"},"index":{"description":"Position among parallel calls. Present on streaming deltas, which key their fragments by it; absent on assembled messages.\n","type":"integer"},"type":{"enum":["function"],"type":"string"}},"required":["function"],"type":"object"},"ToolCallFunction":{"properties":{"arguments":{"description":"The arguments as a JSON-encoded STRING, not an object — this is OpenAI's shape, and it is what lets a model stream partial arguments. Parse it before use, and do not assume it is valid JSON: a model can emit malformed arguments.\n","type":"string"},"name":{"type":"string"}},"required":["name","arguments"],"type":"object"},"ToolChoiceFunction":{"properties":{"function":{"properties":{"name":{"type":"string"}},"required":["name"],"type":"object"},"type":{"enum":["function"],"type":"string"}},"required":["type","function"],"type":"object"},"ToolFunction":{"properties":{"description":{"description":"What the function does. The model reads this to decide when to call it.","type":"string"},"name":{"description":"The function name the model will emit when it calls this tool.","type":"string"},"parameters":{"additionalProperties":true,"description":"JSON Schema for the function's arguments. Forwarded to the model untouched; deeprelay does not interpret it.\n","type":"object"}},"required":["name"],"type":"object"},"Usage":{"properties":{"completion_tokens":{"type":"integer"},"completion_tokens_details":{"properties":{"reasoning_tokens":{"type":"integer"}},"type":"object"},"cost":{"description":"The amount charged for this call in USD: equal to cost_details.charged_amount, so 0 for a call covered by the flat subscription. An exact decimal with up to 8 places, emitted as a JSON number. On a stream it appears only in the final usage chunk. Your invoice is computed from the billing ledger, which prices the call from the same breakdown.\n","type":"number"},"cost_details":{"$ref":"#/components/schemas/CostDetails"},"prompt_tokens":{"type":"integer"},"prompt_tokens_details":{"description":"Present whenever the upstream reported prompt-cache usage, including a reported 0. Absent means the upstream did not report it.\n","properties":{"cached_tokens":{"description":"Prompt tokens served from the upstream's prompt cache.","nullable":true,"type":"integer"}},"type":"object"},"total_tokens":{"type":"integer"}},"required":["prompt_tokens","completion_tokens","total_tokens"],"type":"object"},"UsageBucket":{"description":"One `/usage` row. Every row carries `bucket_start`, `gpu_seconds` and `cost_cents`. When the request set `modality` or `model` the row is an inference-usage row: it also carries `modality`, `model`, `prompt_tokens`, `completion_tokens` and `image_count` (aggregating every serverless inference call in the bucket for that modality and model), and `gpu_seconds` is always 0. Otherwise it is an instance-usage row, carrying `instance_id` / `gpu_type` when `group_by` asked for them.\n","properties":{"bucket_start":{"description":"Start of the time bucket (truncated to `bucket`).","format":"date-time","type":"string"},"completion_tokens":{"description":"Inference-usage rows only. Sum of output tokens; 0 for modalities not billed per token.","type":"integer"},"cost_cents":{"description":"Total cost of the row, in US cents.","type":"integer"},"gpu_seconds":{"description":"GPU instance seconds billed in the bucket. Always 0 on inference-usage rows.","type":"integer"},"gpu_type":{"description":"Instance-usage rows with `group_by=gpu_type` only.","nullable":true,"type":"string"},"image_count":{"description":"Inference-usage rows only. Sum of generated images; 0 for non-image modalities.","type":"integer"},"instance_id":{"description":"Instance-usage rows with `group_by=instance_id` only.","nullable":true,"type":"string"},"modality":{"description":"Inference-usage rows only: the modality of the calls in this row (chat, image, video or embedding).\n","type":"string"},"model":{"description":"Inference-usage rows only: the model `id` the calls in this row were made against.\n","type":"string"},"prompt_tokens":{"description":"Inference-usage rows only. Sum of input tokens; 0 for modalities not billed per token.","type":"integer"}},"required":["bucket_start","gpu_seconds","cost_cents"],"type":"object"},"UsagePage":{"properties":{"data":{"description":"Inference-usage rows when the request set `modality` or `model`; instance-usage rows otherwise. One page never mixes the two.\n","items":{"$ref":"#/components/schemas/UsageBucket"},"type":"array"},"next_cursor":{"type":["string","null"]}},"required":["data","next_cursor"],"type":"object"},"VideoCreateRequest":{"properties":{"model":{"description":"Customer-facing video model id (e.g. deeprelay/wan-2.2-t2v) or alias.","type":"string"},"prompt":{"description":"Text description of the video to generate.","type":"string"},"seconds":{"description":"Output video duration in seconds (the billable unit).","minimum":1,"type":"integer"},"size":{"description":"Output video dimensions as WxH (e.g. 1280x720).","type":"string"}},"required":["model","prompt"],"type":"object"},"VideoJob":{"properties":{"completed_at":{"description":"Unix timestamp (seconds) when the job completed.","nullable":true,"type":"integer"},"cost_cents":{"description":"Final billed cost in cents; present once the job completes (failed/cancelled jobs are never billed).","nullable":true,"type":"integer"},"created_at":{"description":"Unix timestamp (seconds) when the job was created.","type":"integer"},"error":{"description":"Canonical failure code when status is failed.","nullable":true,"type":"string"},"error_message":{"description":"Human-readable reason behind `error`, when the upstream supplied one. `error` classifies the failure, `error_message` explains it (for example a requested duration outside the model's supported range). Sanitized before storage; only ever present alongside `error`.","nullable":true,"type":"string"},"expires_at":{"description":"Unix timestamp (seconds) when the artifact expires (24h after completion).","nullable":true,"type":"integer"},"id":{"type":"string"},"model":{"type":"string"},"object":{"enum":["video"],"type":"string"},"progress":{"description":"Generation progress 0-100.","type":"integer"},"seconds":{"type":"integer"},"size":{"type":"string"},"status":{"enum":["queued","in_progress","completed","failed","cancelled","expired"],"type":"string"}},"required":["id","object","model","status","progress","created_at"],"type":"object"},"VideoList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/VideoJob"},"type":"array"},"has_more":{"type":"boolean"},"object":{"enum":["list"],"type":"string"}},"required":["object","data","has_more"],"type":"object"},"WebhookEndpoint":{"properties":{"created_at":{"format":"date-time","type":"string"},"enabled":{"type":"boolean"},"event_types":{"items":{"enum":["instance.creating","instance.running","instance.terminated","instance.failed","video.completed","video.failed","balance.low","subscription_quota.threshold","api_key_budget.threshold","member_spend.threshold","plan_roster.model_removed"],"type":"string"},"type":"array"},"id":{"format":"uuid","type":"string"},"secret":{"description":"Returned ONLY at creation; subsequent reads omit.","type":"string"},"url":{"format":"uri","type":"string"}},"required":["id","url","event_types","enabled","created_at"],"type":"object"},"WebhookEndpointPage":{"properties":{"data":{"items":{"$ref":"#/components/schemas/WebhookEndpoint"},"type":"array"},"next_cursor":{"type":["string","null"]}},"required":["data","next_cursor"],"type":"object"},"WeightsVerification":{"description":"Present only for an open-weight chat model whose latest public fingerprint run passed: the run compared the relay's output distributions against independent deployments of the same weights and found them the same model. Derived from the newest run the public harness published (see `method_url`), through a short cache, and dropped once that run is older than 14 days. There is no negative value: a model the run did not verify, for any reason, simply has no `weights_verification` key.","properties":{"harness_version":{"description":"The version of the measuring code that run used.","type":"string"},"method_url":{"description":"The public harness repository that defines the method.","format":"uri","type":"string"},"references":{"description":"How many independent deployments of the same weights the relay was compared against.","minimum":0,"type":"integer"},"run_id":{"description":"The public GitHub Actions run id.","type":"string"},"run_url":{"description":"The public run, where the report and job log live.","format":"uri","type":"string"},"status":{"description":"Always `verified`; the object is absent otherwise.","enum":["verified"],"type":"string"},"verified_at":{"description":"When the run that made the finding finished.","format":"date-time","type":"string"},"weights":{"description":"The Hugging Face repo the served weights were verified against.","type":"string"}},"required":["status","weights","verified_at","run_id","run_url","harness_version","references","method_url"],"type":"object"}},"securitySchemes":{"bearerAuth":{"bearerFormat":"deeprelay_live_\u003c24-base62\u003e","description":"An API key. Its scopes bound what it may do: a shortcut (`full_access`, `standard`, `read_only`) or a per-resource map of `none` / `read` / `write` levels over `serverless`, `billing`, `webhooks`, `keys` and the other resources. Key management needs the `keys:manage` capability: `keys:write` grants keys:manage, `full_access` implies it, and `standard` does not. Each route's description names the scope it requires.","scheme":"bearer","type":"http"}}},"info":{"contact":{"email":"support@deeprelay.ai","name":"deeprelay Support"},"description":"Programmatic API for deeprelay. Authenticate with API keys minted from\nthe dashboard. See https://deeprelay.ai/docs for details.\n\nKeys minted from the dashboard or `deeprelay login` belong to the\nProduction server, https://api.deeprelay.ai/v1. The Demo server is an\ninternal staging environment with its own key store.\n\nVersioning: `/v1` is the API version. `info.version` is the revision of\nthis `/v1` document, not a separate API.\n\nOpenAI compatibility: chat completions follow the OpenAI request and\nresponse shapes, but not every OpenAI parameter is carried. Some are\nrejected with 400 `unsupported_parameter` and any parameter not declared\nin this spec is accepted and ignored. The full table is at\nhttps://deeprelay.ai/docs/api/inference#openai-compatibility.\n","license":{"identifier":"Apache-2.0","name":"Apache-2.0"},"title":"deeprelay Public Developer API","version":"1.0.0"},"openapi":"3.1.0","paths":{"/alerts/settings":{"get":{"description":"Returns the low-balance threshold and the four org-level alert email toggles. An organization that has never saved settings gets the defaults with `updated_at: null`. Requires the `billing:read` scope.\n","operationId":"getAlertSettings","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlertSettings"}}},"description":"OK"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/Problem"}},"summary":"Get the organization's alert settings","tags":["Alerts"]},"put":{"description":"Replaces every setting at once: send `balance_threshold_cents` and all four `email` toggles (a missing field is 422 `validation_failed` naming the `field`). Requires the `billing:write` scope AND an org admin's key or a `full_access` key (403 `forbidden` otherwise). The toggles only control the org-level emails; a member always receives their own spend-limit emails. Webhook delivery of the same events is configured on the webhook endpoint, not here.\n","operationId":"putAlertSettings","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlertSettingsUpdate"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlertSettings"}}},"description":"OK"},"400":{"$ref":"#/components/responses/Problem"},"403":{"$ref":"#/components/responses/Problem"},"422":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/Problem"}},"summary":"Replace the organization's alert settings","tags":["Alerts"]}},"/api-keys":{"get":{"description":"Lists the organization's API keys with their limits and current budget use. An org admin's key and a `full_access` key see every key in the organization; any other key sees only the keys its owner created. The secret is never returned here. Requires the `keys:read` scope.\n","operationId":"listApiKeys","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyList"}}},"description":"OK"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/Problem"}},"summary":"List API keys","tags":["ApiKeys"]},"post":{"description":"Mints a key. The response is the only place the secret (`key`) appears; store it then, because it cannot be read again.\n\nRequires the `keys:manage` capability: a `full_access` key, or a key whose scopes grant `{\"keys\": \"write\"}`. Any other key gets 403 `keys_manage_required`. A key can never mint a key stronger than itself: scopes it does not hold answer 403 `scope_exceeds_caller`, and a minting key that has a budget, a model allowlist, a rate limit or an expiry passes each down as an upper bound (422 `validation_failed` naming the `field`). Only an org admin can mint a `full_access` key.\n","operationId":"createApiKey","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreate"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreated"}}},"description":"Created. The response is sent with `Cache-Control: no-store`."},"400":{"$ref":"#/components/responses/Problem"},"403":{"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}},"description":"`keys_manage_required` (the calling key cannot manage keys), `scope_exceeds_caller` (the requested scopes exceed the calling key's own) or `forbidden` (only an org admin can mint a `full_access` key)."},"422":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/Problem"}},"summary":"Create an API key","tags":["ApiKeys"]}},"/api-keys/{id}":{"delete":{"description":"Revokes the key immediately; it cannot be restored. Requires the `keys:manage` capability (403 `keys_manage_required` otherwise); a member can only revoke keys they created.\n","operationId":"revokeApiKey","parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"responses":{"204":{"description":"Revoked"},"403":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/Problem"}},"summary":"Revoke an API key","tags":["ApiKeys"]},"get":{"description":"Returns one key. A key the caller cannot see answers 404. Requires the `keys:read` scope.\n","operationId":"getApiKey","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKey"}}},"description":"OK"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/Problem"}},"summary":"Get an API key","tags":["ApiKeys"]},"parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"patch":{"description":"Changes a key's name, budget, model allowlist, backup models, rate limit or expiry. An absent field is left unchanged; an explicit `null` clears `budget_cents`, `allowed_models`, `fallback_models` or `expires_at`. `scopes` cannot be changed after minting, and unknown fields are refused (422), so a typo never looks like a successful change. Requires the `keys:manage` capability (403 `keys_manage_required` otherwise); a member can only change keys they created.\n","operationId":"updateApiKey","parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyUpdate"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKey"}}},"description":"OK"},"400":{"$ref":"#/components/responses/Problem"},"403":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"422":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/Problem"}},"summary":"Update an API key's name or limits","tags":["ApiKeys"]}},"/billing/balance":{"get":{"description":"Returns the organization's credit balance, its auto-pay configuration, and the live burn rate. Requires the `billing:read` scope. The organization is taken from the authenticated API key, never from a parameter.\n\nDivide `balance_cents` by `burn_cents_per_hour` for the remaining runway in hours. The quotient is undefined in three cases a caller must keep apart: `burn_cents_per_hour` is 0 (nothing running, so the balance funds unbounded idle time), it is `null` (the burn rate could not be determined — NOT the same as idle), or the balance is already at or below zero.\n\nThis is the balance itself — for spend against a configured cap see `/billing/spending-limit`, and for past consumption see `/usage`.\n","operationId":"getBalance","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Balance"}}},"description":"OK"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/Problem"}},"summary":"Get the org credit balance","tags":["Billing"]}},"/billing/deposits":{"get":{"description":"Returns the organization's stablecoin deposits, newest first, capped at 50. Requires the `billing:read` scope. The organization is taken from the authenticated API key, never from a parameter. Returns 404 when stablecoin deposits are not enabled for this deployment.\n","operationId":"listDeposits","responses":{"200":{"content":{"application/json":{"schema":{"properties":{"deposits":{"items":{"$ref":"#/components/schemas/CryptoDeposit"},"type":"array"},"min_deposit_cents":{"description":"The effective minimum deposit in USD cents (admin override, else the deployment's configured floor). Validate amounts against this live value rather than hardcoding the default.","example":500,"format":"int64","type":"integer"}},"required":["deposits","min_deposit_cents"],"type":"object"}}},"description":"OK"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/Problem"}},"summary":"List stablecoin deposits","tags":["Billing"]}},"/billing/deposits/crypto":{"post":{"description":"Creates a deposit intent and returns the payment address and the exact token amount to send. Requires the `billing:write` scope; any member of the organization may add funds. Send the exact `pay_amount` of `asset` on `chain` and no other network — funds sent on a different network are not detected automatically. Returns 404 when stablecoin deposits are not enabled for this deployment.\n","operationId":"createCryptoDeposit","requestBody":{"content":{"application/json":{"schema":{"properties":{"amount_cents":{"description":"Amount to deposit in USD cents. Must be at least the configured minimum (500 = $5.00 by default).\n","type":"integer"},"asset":{"enum":["usdc","usdt"],"type":"string"},"chain":{"description":"Network to send on. tron and bsc (BNB Chain) carry USDT only (TRC-20 and BEP-20); base carries USDC only.\n","enum":["ethereum","base","arbitrum","polygon","solana","tron","bsc"],"type":"string"}},"required":["amount_cents","chain","asset"],"type":"object"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CryptoDeposit"}}},"description":"Created"},"400":{"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}},"description":"`validation-error` — `amount_cents` is below the configured minimum. `unsupported-chain-asset` — the chain/asset pair is not available. `amount-below-minimum`: the amount is below the network's current minimum, which moves with network fees; `detail` quotes it when known. Raise the amount or choose another chain. `invalid-request` — malformed JSON body.\n"},"403":{"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}},"description":"`org-frozen` — the organization cannot add funds; contact support."},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}},"description":"`payment-source-unavailable` — deposits are temporarily unavailable; retry shortly."},"default":{"$ref":"#/components/responses/Problem"}},"summary":"Create a stablecoin deposit","tags":["Billing"]}},"/billing/deposits/{id}":{"get":{"description":"Returns a single deposit belonging to the caller's organization. Requires the `billing:read` scope. A deposit belonging to another organization returns the same `404 deposit-not-found` as one that does not exist — deliberately, so this endpoint cannot be used to test whether a deposit id is real. Also 404 when stablecoin deposits are not enabled for this deployment.\n","operationId":"getDeposit","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CryptoDeposit"}}},"description":"OK"},"404":{"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}},"description":"`deposit-not-found` — no such deposit, or it belongs to another organization. `not-found` — the feature is not enabled.\n"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/Problem"}},"summary":"Get one stablecoin deposit","tags":["Billing"]},"parameters":[{"description":"The deposit id.","in":"path","name":"id","required":true,"schema":{"type":"string"}}]},"/billing/spending-limit":{"delete":{"description":"Removes the organization's monthly spending limit and daily spend cap entirely. Requires the `billing:write` scope AND org-admin privileges (a non-admin member gets 403). Returns 404 when no limit is configured. This is the only way to remove a limit through the API, because `PUT` floors `monthly_limit_dollars` at 1.00 and so cannot express \"no limit\".\n","operationId":"deleteSpendingLimit","responses":{"204":{"description":"No Content"},"403":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/Problem"}},"summary":"Clear the org spending limit","tags":["Billing"]},"get":{"description":"Returns the organization's monthly spending limit and opt-in daily spend cap, with the current month and day spend. Requires the `billing:read` scope. Returns 404 when no limit is configured.\n","operationId":"getSpendingLimit","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SpendingLimit"}}},"description":"OK"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/Problem"}},"summary":"Get the org spending limit","tags":["Billing"]},"put":{"description":"Sets the monthly spending limit and optionally sets or clears the opt-in daily spend cap. Requires the `billing:write` scope AND org-admin privileges (a non-admin member gets 403). `daily_limit_dollars` uses pointer semantics: omit to leave the cap unchanged, 0 to clear it, a positive value to set it.\n","operationId":"updateSpendingLimit","requestBody":{"content":{"application/json":{"schema":{"properties":{"auto_terminate_hours":{"description":"Hours after the limit is reached before auto-terminate; null = never.","nullable":true,"type":"integer"},"daily_limit_dollars":{"description":"Opt-in daily cap in USD. Omit = unchanged, 0 = clear, \u003e 0 = set.","type":"number"},"enforcement":{"enum":["stop_running","block_launch","notify_only"],"type":"string"},"monthly_limit_dollars":{"description":"Monthly limit in USD (minimum 1.00).","type":"number"}},"required":["monthly_limit_dollars"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SpendingLimit"}}},"description":"OK"},"403":{"$ref":"#/components/responses/Problem"},"422":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/Problem"}},"summary":"Set the org spending limit","tags":["Billing"]}},"/billing/subscription":{"get":{"description":"Returns the organization's subscription: whether it is entitled, the current billing period, the plan it is on and that plan's configured quota, and — for an entitled subscription — consumption against the limits the server enforces on every request. Requires the `billing:read` scope. The organization is taken from the authenticated API key, never from a parameter.\n\n`plan` and `quota` describe the plan the organization IS on (the Base plan for an organization that never subscribed). `plans` lists every plan this deployment offers — Base, then Plus — each with its price and quota, so a client can render an upgrade offer without a second call.\n\nAn organization that never subscribed is NOT an error. It gets 200 with `subscribed: false`, `status: \"none\"` and the quota the Base plan would provide, so a client can answer \"what does the plan include\" without a second endpoint and without an error path.\n\nRead `subscribed`, not `status`, to decide whether the plan applies: `status` carries the billing provider's vocabulary, and the two can disagree — an `active` subscription just past its period end still entitles for a short grace window (renewal-webhook lag), while `past_due` and `canceled` never entitle.\n\n`usage` is present only for an entitled subscription whose meter could be read. An absent `usage` NEVER means \"nothing used\" — reading it as zero would report a full quota to an organization that has none left.\n\nThis is the plan's meter. For pay-as-you-go credit see `/billing/balance`, for spend against a self-set cap see `/billing/spending-limit`, and for past consumption see `/usage`.\n","operationId":"getSubscription","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Subscription"}}},"description":"OK"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/Problem"}},"summary":"Get the org subscription status and quota usage","tags":["Billing"]}},"/billing/subscription/checkout":{"post":{"description":"Opens a hosted checkout session for a subscription plan and returns its URL. Requires the `billing:write` scope AND organization-admin privileges — subscribing spends organization money.\n\nThis endpoint does NOT subscribe anyone. Checkout is a hosted page that needs a browser and a card, so the caller's job is to put the returned URL in front of a human. The subscription becomes active when payment completes, which is not synchronous with this call: poll `/billing/subscription` to confirm.\n\n`success_url` and `cancel_url` are optional and fall back to the deployment's configured redirects, which is what lets a command-line client start a purchase without having any URLs of its own. An empty request body is valid and means \"use every default\". When sent, they must be on the deployment's dashboard origin, else 422 `validation_failed`.\n\n`plan_key` names the plan to buy: `flat` (Base, the default when the field is omitted) or `plus`. It pins the plan the client DISPLAYED — an unknown key, or a plan this deployment does not offer, is a 400 rather than a silent purchase of a different tier. The `plans` array on `/billing/subscription` lists the accepted keys.\n\nOne subscription per organization: a second checkout while an entitling subscription exists is a 409. To move an existing subscription to a bigger plan use `/billing/subscription/upgrade`.\n\n`method` picks how the plan is paid: `card` (dollars, card or Link; the default) or `upi` (rupees, UPI AutoPay, India). On an organization that already subscribes, an explicit `method` in the other currency is a switch, not a second subscription: the link saves the new card or UPI AutoPay mandate (nothing is charged), and once that completes the same plan is moved to the new currency, first charged on the current renewal date (that month is already paid). The answer then carries `switch_to` and `first_charge_at`. A switch is refused (409) within two days of renewal, while the plan is set to cancel, while an upgrade waits on its UPI payment, and in the currency the plan already uses. While new payments are paused on the organization after several declined cards, a new checkout (a switch included) is refused with 429 `checkout_paused` and a Retry-After; renewing a plan billed through the previous payment account is never refused.\n","operationId":"createSubscriptionCheckout","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionCheckoutRequest"}}},"required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionCheckoutSession"}}},"description":"OK"},"403":{"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}},"description":"Not an organization admin"},"409":{"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}},"description":"The organization already has an active subscription"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}},"description":"Subscription billing is not configured on this deployment"},"default":{"$ref":"#/components/responses/Problem"}},"summary":"Start a subscription checkout session","tags":["Billing"]}},"/billing/subscription/portal":{"post":{"description":"Returns a URL for the hosted billing portal: where a customer cancels the subscription, resumes one they cancelled, changes payment method, or downloads invoices. Requires the `billing:write` scope AND organization-admin privileges.\n\nCancellation lives here rather than on its own endpoint because it is one surface with the rest of the billing lifecycle. The common reason a subscription is about to lapse is a declined card, and the fix for that is a new card, not a cancellation — sending a customer somewhere that can only cancel would lose renewals.\n\nCancelling in the portal ends the subscription at the close of the current period; coverage continues until then and `/billing/subscription` reports `cancel_at_period_end: true`.\n\nAn organization that has never paid for anything gets 404: there is no billing account to manage, and this endpoint deliberately does not create one as a side effect of looking.\n","operationId":"createSubscriptionPortal","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionPortalRequest"}}},"required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionPortalSession"}}},"description":"OK"},"403":{"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}},"description":"Not an organization admin"},"404":{"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}},"description":"The organization has no billing account yet"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}},"description":"Subscription billing is not configured on this deployment"},"default":{"$ref":"#/components/responses/Problem"}},"summary":"Open the billing portal to cancel or manage the subscription","tags":["Billing"]}},"/billing/subscription/upgrade":{"post":{"description":"Moves the organization's EXISTING subscription onto a bigger plan. Requires the `billing:write` scope AND organization-admin privileges — an upgrade charges the organization's card on file.\n\nUnlike checkout, no hosted page is involved: the subscription's price is swapped, the new plan's FULL price is charged immediately (whenever in the cycle the upgrade happens), and a new billing period starts now. There is no credit for the unused remainder of the old plan's period, and the usage meters start from zero on the new plan because every quota window is summed over the current period. The response is the post-upgrade subscription, in the same shape as `/billing/subscription`.\n\n`plan_key` is required and names the target plan (`plus`). The target must be bigger than the plan the organization is on: upgrading to the current plan or to a smaller one is a 409 `not_an_upgrade` — downgrades go through the billing portal. An organization with no entitling subscription gets 409 `not_subscribed` and should use checkout instead. A subscription already set to end at the period close is refused with 409 `subscription_cancelling` (the upgrade would start a new period and move that end date); resume it in the portal first.\n\nIf the charge cannot be collected (card declined, no payment method) the call is a 402 `payment_failed` and NOTHING changes — the organization stays on its current plan. Fix the card in the billing portal and retry.\n","operationId":"upgradeSubscription","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionUpgradeRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Subscription"}}},"description":"OK — the subscription after the upgrade"},"400":{"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}},"description":"Unknown plan_key, or a plan this deployment does not offer"},"402":{"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}},"description":"Payment for the new plan failed; the current plan is unchanged"},"403":{"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}},"description":"Not an organization admin"},"409":{"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}},"description":"`not_subscribed` — no active subscription to upgrade (use checkout); `not_an_upgrade` — the target is the current plan or a smaller one; `subscription_cancelling` — the subscription is set to end at the period close (resume it in the portal first)"},"422":{"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}},"description":"plan_key is missing"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}},"description":"Subscription billing is not configured on this deployment"},"default":{"$ref":"#/components/responses/Problem"}},"summary":"Upgrade the subscription to a bigger plan","tags":["Billing"]}},"/chat/completions":{"post":{"description":"OpenAI-compatible chat completion. Parameters fall into three groups:\n\n- **Semantic parameters are gated per model.** `response_format`,\n  `tools`/`tool_choice`, `logprobs`/`top_logprobs` and `image_url`\n  content parts are sent only to a model whose `/v1/models` entry\n  lists the capability in `supported_parameters` (`vision` for\n  images). A model without it returns 400 `unsupported_parameter`\n  whose message names the models that do support it. When the model\n  supports the parameter but none of its currently routable upstreams\n  does, the request fails with 503 `upstream_unavailable` rather than\n  being silently degraded.\n- **Hints are forwarded or dropped, never rejected.**\n  `reasoning_effort`, `presence_penalty`, `frequency_penalty`, `seed`\n  and `parallel_tool_calls` are forwarded to an upstream that accepts\n  them and otherwise dropped. Every drop is reported in the\n  `x-deeprelay-ignored-params` response header.\n- **Rejected everywhere.** `logit_bias` and `function_call` return\n  400 `unsupported_parameter`; `input_audio` and `file` content parts\n  return 400 `unsupported_content_part`.\n\nA non-strict `json_schema` sent to a model that can only guarantee\nJSON is served as `json_object` and reported as\n`response_format:downgraded`; a strict one is rejected. A\n`json_schema` over 64 KiB or nested deeper than 32 levels returns 400\n`unsupported_parameter` (param `response_format`). An upstream\nrejection of a request the relay accepted returns 400\n`upstream_rejected_request`. A non-chat model id returns 404\n`model_not_found`. The request body is capped at 32 MiB (413\n`request_too_large`).\n\nEvery successful response carries `usage.cost` and\n`usage.cost_details` (on a stream, only in the final usage chunk).\nSee https://deeprelay.ai/docs/api/inference#openai-compatibility.\n\n**Backup models.** When the API key has `fallback_models`, or the\nrequest sends `models` or X-Deeprelay-Fallback-Models, and every\nprovider for the requested model fails before the first token with a\ntemporary error (unavailable, timed out, rate limited upstream, or\nstalled), deeprelay tries the next backup model. It never switches\nafter the first byte, and never on a client error, a quota or budget\nrefusal, or a content-filter result. A backup is billed at its own\nprice under the same billing mode as the requested model. The body\n`model` and X-Deeprelay-Served-Model name the model that served;\nX-Deeprelay-Requested-Model names the one asked for, and\nX-Deeprelay-Fallback-Reason says why it was passed over. If no model\nserves, the response is the requested model's own error, with\nX-Deeprelay-Fallback-Attempted.\n","operationId":"createChatCompletion","parameters":[{"$ref":"#/components/parameters/ClientRequestIdHeader"},{"$ref":"#/components/parameters/FallbackModelsHeader"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatCompletionRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatCompletionResponse"}},"text/event-stream":{"schema":{"description":"Server-Sent Events stream of ChatCompletionChunk objects, terminated by `data: [DONE]`.","format":"binary","type":"string"}}},"description":"Successful response. application/json for non-streaming requests;\ntext/event-stream for streaming requests (stream=true).\n","headers":{"X-Deeprelay-Fallback-Reason":{"$ref":"#/components/headers/XDeeprelayFallbackReason"},"X-Deeprelay-Requested-Model":{"$ref":"#/components/headers/XDeeprelayRequestedModel"},"X-Deeprelay-Served-Model":{"$ref":"#/components/headers/XDeeprelayServedModel"},"x-deeprelay-cost":{"$ref":"#/components/headers/XDeeprelayCost"},"x-deeprelay-ignored-params":{"$ref":"#/components/headers/XDeeprelayIgnoredParams"},"x-deeprelay-quota-input-remaining":{"$ref":"#/components/headers/XDeeprelayQuotaInputRemaining"},"x-deeprelay-quota-output-remaining":{"$ref":"#/components/headers/XDeeprelayQuotaOutputRemaining"},"x-deeprelay-quota-reset":{"$ref":"#/components/headers/XDeeprelayQuotaReset"},"x-deeprelay-quota-search-limit":{"$ref":"#/components/headers/XDeeprelayQuotaSearchLimit"},"x-deeprelay-quota-search-remaining":{"$ref":"#/components/headers/XDeeprelayQuotaSearchRemaining"},"x-deeprelay-quota-weekly-input-limit":{"$ref":"#/components/headers/XDeeprelayQuotaWeeklyInputLimit"},"x-deeprelay-quota-weekly-input-remaining":{"$ref":"#/components/headers/XDeeprelayQuotaWeeklyInputRemaining"},"x-deeprelay-quota-weekly-output-limit":{"$ref":"#/components/headers/XDeeprelayQuotaWeeklyOutputLimit"},"x-deeprelay-quota-weekly-output-remaining":{"$ref":"#/components/headers/XDeeprelayQuotaWeeklyOutputRemaining"},"x-ratelimit-limit-requests":{"$ref":"#/components/headers/XRateLimitLimitRequests"},"x-ratelimit-remaining-requests":{"$ref":"#/components/headers/XRateLimitRemainingRequests"},"x-ratelimit-reset-requests":{"$ref":"#/components/headers/XRateLimitResetRequests"}}},"400":{"$ref":"#/components/responses/OpenAIBadRequest"},"401":{"$ref":"#/components/responses/OpenAIUnauthorized"},"402":{"$ref":"#/components/responses/OpenAIPaymentRequired"},"403":{"$ref":"#/components/responses/OpenAIForbidden"},"404":{"$ref":"#/components/responses/OpenAINotFound"},"413":{"$ref":"#/components/responses/OpenAIRequestTooLarge"},"422":{"$ref":"#/components/responses/OpenAIUnprocessableEntity"},"429":{"$ref":"#/components/responses/OpenAIRateLimited"},"500":{"$ref":"#/components/responses/OpenAIInternalError"},"503":{"$ref":"#/components/responses/OpenAIUpstreamUnavailable"}},"security":[{"bearerAuth":[]}],"summary":"Create a chat completion (OpenAI-compatible)","tags":["Inference"]}},"/embeddings":{"post":{"description":"Synchronous text embeddings. Accepts a single string or an array of up to 2048 strings, bounded so the response stays under 64 MiB (about 680 inputs at 4096 dimensions; fewer at a larger `dimensions` value) — over-limit requests are rejected with 400 before any processing. Returns one float vector per input, in request order. Billed on the upstream's reported prompt tokens at the model's listed input rate, rounded up to the next whole cent per request; an embeddings call emits no completion tokens, so `usage` carries `prompt_tokens` and `total_tokens` only. `encoding_format` accepts only `float` — a value of `base64` is rejected with `unsupported_parameter`. A chat model id on this route returns 404 `model_not_found`: the id is valid, but not on this surface.","operationId":"createEmbeddings","parameters":[{"$ref":"#/components/parameters/ClientRequestIdHeader"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmbeddingsRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmbeddingsResponse"}}},"description":"One embedding vector per input, in request order."},"400":{"$ref":"#/components/responses/OpenAIBadRequest"},"401":{"$ref":"#/components/responses/OpenAIUnauthorized"},"402":{"$ref":"#/components/responses/OpenAIPaymentRequired"},"403":{"$ref":"#/components/responses/OpenAIForbidden"},"404":{"$ref":"#/components/responses/OpenAINotFound"},"413":{"$ref":"#/components/responses/OpenAIRequestTooLarge"},"429":{"$ref":"#/components/responses/OpenAIRateLimited"},"500":{"$ref":"#/components/responses/OpenAIInternalError"},"502":{"$ref":"#/components/responses/OpenAIBadGateway"},"503":{"$ref":"#/components/responses/OpenAIUpstreamUnavailable"},"504":{"$ref":"#/components/responses/OpenAIUpstreamTimeout"}},"security":[{"bearerAuth":[]}],"summary":"Create embeddings (OpenAI-compatible)","tags":["Inference"]}},"/health":{"get":{"operationId":"getHealth","responses":{"200":{"content":{"application/json":{"schema":{"properties":{"status":{"const":"ok","type":"string"}},"type":"object"}}},"description":"OK"}},"security":[],"summary":"Liveness probe","tags":["Meta"]}},"/images/generations":{"post":{"description":"Synchronous text-to-image generation. Returns base64-encoded images only (`response_format` is restricted to `b64_json`); a value of `url` is rejected with `invalid_request_error` until S3-backed URL delivery lands. Pass an `Idempotency-Key` header to make a retried request replay the original response without a second charge.","operationId":"createImage","parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"},{"$ref":"#/components/parameters/ClientRequestIdHeader"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImagesGenerationsRequest"}}},"required":true},"responses":{"200":{"$ref":"#/components/responses/OpenAIImages"},"400":{"$ref":"#/components/responses/OpenAIBadRequest"},"401":{"$ref":"#/components/responses/OpenAIUnauthorized"},"402":{"$ref":"#/components/responses/OpenAIPaymentRequired"},"403":{"$ref":"#/components/responses/OpenAIForbidden"},"404":{"$ref":"#/components/responses/OpenAINotFound"},"422":{"$ref":"#/components/responses/OpenAIUnprocessableEntity"},"429":{"$ref":"#/components/responses/OpenAIRateLimited"},"500":{"$ref":"#/components/responses/OpenAIInternalError"},"503":{"$ref":"#/components/responses/OpenAIUpstreamUnavailable"},"504":{"$ref":"#/components/responses/OpenAIUpstreamTimeout"}},"security":[{"bearerAuth":[]}],"summary":"Create image (OpenAI-compatible)","tags":["Inference"]}},"/inference/preflight":{"get":{"description":"Answers \"what happens if I call this model right now?\" before the call is made: whether the plan covers the model, whether the organization is subscribed, and whether there is credit to pay if it is not covered. Requires the `serverless:read` scope.\n\nNothing is sent, counted, charged, or reserved. The verdict is computed from the state of the SAME gates that judge the real request — plan coverage, subscription entitlement, remaining plan quota, credit balance and self-set spending caps — so the advice cannot drift from enforcement. It is deliberately NOT a dry run: no per-request cost estimate is quoted, because that figure changes with every prompt and quoting it would invite clients to cache it.\n\nThe gates consulted depend on the model's MODALITY, because the endpoints do not all meet the same ones. Chat, embeddings and image requests meet the full gate (balance, then the organization's opt-in daily cap, then its monthly cap). Video creation meets only the balance check, so an organization past its own spending cap but holding credit is reported as fundable for video — which is what the video endpoint will in fact do. Predicting the strictest gate rather than the applicable one would make this endpoint refuse requests the API accepts.\n\nThe same is true of the plan: video creation does not run the subscription gate, so `plan_covered` is false for a video model even if an operator has placed it on the plan's covered list. That is a deliberate divergence from the same-named field on `/v1/models`, which reports the platform's configuration. Here it means \"the plan covers this REQUEST\" — describing what will happen is the entire job of a preflight.\n\nOne caveat on \"read-only\": resolving the balance creates the organization's balance row if it has never had one (idempotent, org-scoped, and the same row the first real request would create). Nothing else is written.\n\nThe case this exists for is `warn` / `not_plan_covered`. A subscriber calling a model outside the plan IS served and IS charged pay-as-you-go, and nothing in the response to that request says so — the first signal used to be the invoice.\n\n`funded` is a boolean and never a figure. This route is on the inference read scope, so it must not disclose the organization's balance; use `/billing/balance` for the number.\n\nFailure posture is the opposite of the request gate's: any gate that cannot be read degrades the verdict toward `ok`, never toward `block`. A false `block` would stop a customer whose request would have succeeded, while a false `ok` costs them one honest error from the real call.\n","operationId":"inferencePreflight","parameters":[{"description":"Model id or alias, optionally with a `:economy` tier suffix. It is resolved through the catalog exactly as the inference endpoints resolve it, so an alias and a tier view answer for the model that would actually serve.","in":"query","name":"model","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InferencePreflight"}}},"description":"OK"},"404":{"$ref":"#/components/responses/OpenAINotFound"},"429":{"$ref":"#/components/responses/OpenAIRateLimited"}},"summary":"Check whether a model request would be served, and at whose expense","tags":["Inference"]}},"/models":{"get":{"description":"Works with or without an API key, and returns the same body either\nway. Without a key the response is publicly cacheable\n(`Cache-Control: public, max-age=60`), carries a strong `ETag`\n(send `If-None-Match` to get a 304), and is rate-limited per client\nIP (429 `rate_limit_exceeded` with Retry-After). With a key it is\n`Cache-Control: private` and counts against the key's rate limit;\nan invalid key returns 401, it does not fall back to anonymous\naccess. Error responses are `no-store`.\n\nEach entry's `supported_parameters` is backed by live capability\nprobes of the model's upstreams: it is the list to check before\nsending a gated parameter (`response_format`,\n`structured_outputs`, `logprobs`, `tools`, `vision`, ...).\n\nOne exception to \"the same body\": a key with a model allowlist\n(`allowed_models`) sees only the models on its list.\n\nEach entry may carry a `metrics` object: measured time to first\ntoken, decode rate and success rate over a rolling window of deeprelay\ncustomer traffic. Below the sample minimum `insufficient_data` is true\nand the figure is omitted, never zero. A chat model whose open weights\nthe latest public fingerprint run verified also carries a\n`weights_verification` object after it; every other model omits the\nkey. These two objects are always the last keys, in that order.\n","operationId":"listModels","parameters":[{"in":"query","name":"modality","required":false,"schema":{"enum":["chat","image","video","embedding"],"type":"string"}},{"description":"An ETag from an earlier anonymous response; a match returns 304.","in":"header","name":"If-None-Match","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModelList"}}},"description":"OpenAI-shaped model list.","headers":{"Cache-Control":{"description":"`public, max-age=60` without a key; `private` with one.","schema":{"type":"string"}},"ETag":{"description":"Strong validator for the anonymous listing.","schema":{"type":"string"}}}},"304":{"description":"Not modified; the If-None-Match ETag still matches."},"400":{"$ref":"#/components/responses/OpenAIBadRequest"},"401":{"$ref":"#/components/responses/OpenAIUnauthorized"},"403":{"$ref":"#/components/responses/OpenAIForbidden"},"429":{"$ref":"#/components/responses/OpenAIRateLimited"}},"security":[{},{"bearerAuth":[]}],"summary":"List available models (OpenAI-compatible)","tags":["Inference"]}},"/models/{id}":{"get":{"description":"A key with a model allowlist gets 403 `model_not_allowed_for_key` for a model off its list, and the same 403 for an id that does not exist.","operationId":"getModel","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"$ref":"#/components/responses/OpenAIModel"},"401":{"$ref":"#/components/responses/OpenAIUnauthorized"},"403":{"$ref":"#/components/responses/OpenAIForbidden"},"404":{"$ref":"#/components/responses/OpenAINotFound"},"429":{"$ref":"#/components/responses/OpenAIRateLimited"}},"security":[{"bearerAuth":[]}],"summary":"Get a specific model (OpenAI-compatible)","tags":["Inference"]}},"/openapi.json":{"get":{"description":"The machine-readable specification of the PUBLISHED API surface — the same document the client SDKs are generated from. Endpoints marked `x-gpu-supply-paused` in the authoritative spec are absent from it while GPU supply is paused (D-28), so this response and the SDKs always describe the same surface.","operationId":"getOpenApiSpec","responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"OK"}},"security":[],"summary":"OpenAPI 3.1 specification","tags":["Meta"]}},"/referrals":{"get":{"description":"Returns the organization's shareable referral code and invite link (minting the code on first read), the program terms, the referrer's stats, and — when this organization itself signed up through someone else's link — its own progress toward the referee reward. Requires the `billing:read` scope. The organization is taken from the authenticated API key, never from a parameter.\n\nThe program: share the invite link; once a friend who signed up through it has made `qualify_deposit_cents` of net real deposits (card, auto-pay or crypto, less refunds), the referrer receives `reward_cents` and the friend receives `referee_reward_cents`, both as non-withdrawable credit, after a `hold_days` chargeback hold. Both rewards are reversed if the qualifying deposit is later refunded. The deposit commission is retired and `commission_pct` is 0; when it is non-zero the referrer also earns that percent of every real deposit the friend makes. `commission_earned_cents` still reports commission earned before retirement. Program rewards never apply to the subscription. The terms ride on the wire so a client never hard-codes the amounts.\n\n`referred` is ABSENT (not null) for an organization nobody referred — key on its presence. This is the same contract the dashboard's referral card reads.\n","operationId":"getReferral","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Referral"}}},"description":"OK"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/Problem"}},"summary":"Get the org referral code, invite link, terms and stats","tags":["Billing"]}},"/requests":{"get":{"description":"Returns one newest-first page of the organization's inference request log: one row per call to chat completions, messages, responses, embeddings, image generation or video creation, including calls that were refused before they reached a model (`status: rejected`). Rows are kept for 90 days; `window_days` says how far back the log reaches, and a `from` older than that is clamped rather than rejected.\n\nVisibility follows the caller. An org admin's key and a `full_access` key see the whole organization; any other key sees only its own rows. A `key` filter naming a key the caller cannot see returns an empty page.\n\nA row never carries prompt or completion content. `client_request_id` is the value the caller sent in `X-Client-Request-Id` (or `metadata.client_request_id` on `/responses`), kept for correlation.\n\nRequires the `serverless:read` scope. Reads are rate-limited per key (429 `rate_limited` with Retry-After).\n","operationId":"listRequests","parameters":[{"$ref":"#/components/parameters/RequestLogKeyParam"},{"$ref":"#/components/parameters/RequestLogModelParam"},{"$ref":"#/components/parameters/RequestLogSurfaceParam"},{"$ref":"#/components/parameters/RequestLogStatusParam"},{"$ref":"#/components/parameters/RequestLogErrorCodeParam"},{"$ref":"#/components/parameters/RequestLogClientRequestIdParam"},{"$ref":"#/components/parameters/RequestLogFromParam"},{"$ref":"#/components/parameters/RequestLogToParam"},{"$ref":"#/components/parameters/RequestLogFallbackParam"},{"$ref":"#/components/parameters/CursorParam"},{"description":"Page size. Defaults to 50; values above 100 are clamped to 100.","in":"query","name":"limit","schema":{"default":50,"maximum":100,"minimum":1,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RequestLogPage"}}},"description":"OK"},"400":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/Problem"}},"summary":"List requests (the request log)","tags":["Requests"]}},"/requests/export":{"get":{"description":"Streams every request-log row matching the filters as a CSV attachment, oldest columns first: `request_id, client_request_id, occurred_at, surface, model, key_id, key_name, key_prefix, status, http_status, error_code, finish_reason, prompt_tokens, completion_tokens, cached_tokens, reasoning_tokens, quota_units, cost, plan_covered, ttft_ms, duration_ms, ignored_params, requested_model, served_model, fallback_reason`. Takes the same filters and visibility rules as `GET /requests`, without the cursor and limit.\n\nThe export is counted before anything is written. When the filters match more rows than the export cap, the answer is 422 `export_too_large` and the problem body carries `max_rows`; narrow the time range or add a filter, then export again.\n\nRequires the `serverless:read` scope. Exports have their own, tighter per-key rate limit.\n","operationId":"exportRequests","parameters":[{"$ref":"#/components/parameters/RequestLogKeyParam"},{"$ref":"#/components/parameters/RequestLogModelParam"},{"$ref":"#/components/parameters/RequestLogSurfaceParam"},{"$ref":"#/components/parameters/RequestLogStatusParam"},{"$ref":"#/components/parameters/RequestLogErrorCodeParam"},{"$ref":"#/components/parameters/RequestLogClientRequestIdParam"},{"$ref":"#/components/parameters/RequestLogFromParam"},{"$ref":"#/components/parameters/RequestLogToParam"},{"$ref":"#/components/parameters/RequestLogFallbackParam"}],"responses":{"200":{"content":{"text/csv":{"schema":{"type":"string"}}},"description":"CSV attachment (`Content-Disposition` names the file)."},"400":{"$ref":"#/components/responses/Problem"},"422":{"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}},"description":"`export_too_large`: the filters match more rows than the export cap. `max_rows` in the problem body is the cap."},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/Problem"}},"summary":"Export the request log as CSV","tags":["Requests"]}},"/requests/{id}":{"get":{"description":"Returns one request-log row. An unknown id, an id older than 90 days and an id the caller cannot see all answer the same 404. Requires the `serverless:read` scope.\n","operationId":"getRequest","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RequestLogEntry"}}},"description":"OK"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/Problem"}},"summary":"Get one request","tags":["Requests"]},"parameters":[{"description":"Any id the caller holds for the request: the `X-Request-Id` response header, a chat completion id (`chatcmpl-…`), a message id (`msg_…`), a response id (`resp_…`) or a video id.","in":"path","name":"id","required":true,"schema":{"maxLength":64,"type":"string"}}]},"/usage":{"get":{"description":"Returns spend aggregated into time buckets over `[start, end)` (default: the last 30 days, `bucket=day`), newest bucket first.\n\nThe response has one of two row shapes, selected by the query:\n\n- **Inference usage** — set `modality` and/or `model`. Rows aggregate serverless inference calls, one row per (bucket, modality, model), and carry `modality`, `model`, `prompt_tokens`, `completion_tokens` and `image_count`. This is the usage a serverless-inference customer is billed for.\n- **Instance usage** — neither `modality` nor `model` set. Rows aggregate GPU instance billing sessions, optionally split by `group_by`. With no instance usage this returns an empty `data` array, so to read inference spend always pass `modality` or `model`.\n\nRequires the `billing:read` scope.\n","operationId":"listUsage","parameters":[{"description":"Bucket width. Defaults to `day`.","in":"query","name":"bucket","schema":{"enum":["hour","day","week","month"],"type":"string"}},{"description":"Split instance-usage rows by instance or GPU type. Applies only to the instance-usage shape (neither `modality` nor `model` set); ignored when either is set, since inference rows are always split by modality and model.\n","in":"query","name":"group_by","schema":{"enum":["instance_id","gpu_type"],"type":"string"}},{"description":"Inclusive RFC 3339 range start. Defaults to 30 days ago.","in":"query","name":"start","schema":{"format":"date-time","type":"string"}},{"description":"Exclusive RFC 3339 range end. Defaults to now; must be after `start`.","in":"query","name":"end","schema":{"format":"date-time","type":"string"}},{"$ref":"#/components/parameters/CursorParam"},{"$ref":"#/components/parameters/LimitParam"},{"description":"Return inference-usage rows for this modality only. Setting `modality` or `model` selects the inference-usage shape; omit both for the instance-usage shape.\n","in":"query","name":"modality","schema":{"enum":["chat","image","video","embedding"],"type":"string"}},{"description":"Return inference-usage rows for this model only: an exact match on the model `id` as listed by `GET /models`. May be combined with `modality`. Setting `modality` or `model` selects the inference-usage shape.\n","in":"query","name":"model","schema":{"type":"string"}}],"responses":{"200":{"$ref":"#/components/responses/UsagePage"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/Problem"}},"summary":"Time-bucketed usage","tags":["Usage"]}},"/videos":{"get":{"operationId":"listVideos","parameters":[{"in":"query","name":"after","required":false,"schema":{"type":"string"}},{"in":"query","name":"limit","required":false,"schema":{"maximum":100,"minimum":1,"type":"integer"}}],"responses":{"200":{"$ref":"#/components/responses/OpenAIVideoList"},"401":{"$ref":"#/components/responses/OpenAIUnauthorized"},"403":{"$ref":"#/components/responses/OpenAIForbidden"},"429":{"$ref":"#/components/responses/OpenAIRateLimited"}},"security":[{"bearerAuth":[]}],"summary":"List video generation jobs","tags":["Inference"]},"post":{"description":"Submit an asynchronous text-to-video generation job. Returns a job object with status `queued`; poll GET /videos/{id} until `completed`, then stream the result from GET /videos/{id}/content. Pass an `Idempotency-Key` header to make a retried request replay the original response without a second charge.","operationId":"createVideo","parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"},{"$ref":"#/components/parameters/ClientRequestIdHeader"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VideoCreateRequest"}}},"required":true},"responses":{"200":{"$ref":"#/components/responses/OpenAIVideo"},"400":{"$ref":"#/components/responses/OpenAIBadRequest"},"401":{"$ref":"#/components/responses/OpenAIUnauthorized"},"402":{"$ref":"#/components/responses/OpenAIPaymentRequired"},"403":{"$ref":"#/components/responses/OpenAIForbidden"},"404":{"$ref":"#/components/responses/OpenAINotFound"},"429":{"$ref":"#/components/responses/OpenAIRateLimited"},"500":{"$ref":"#/components/responses/OpenAIInternalError"}},"security":[{"bearerAuth":[]}],"summary":"Create a video generation job (async)","tags":["Inference"]}},"/videos/{id}":{"get":{"operationId":"getVideo","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"$ref":"#/components/responses/OpenAIVideo"},"401":{"$ref":"#/components/responses/OpenAIUnauthorized"},"403":{"$ref":"#/components/responses/OpenAIForbidden"},"404":{"$ref":"#/components/responses/OpenAINotFound"},"429":{"$ref":"#/components/responses/OpenAIRateLimited"}},"security":[{"bearerAuth":[]}],"summary":"Get a video generation job","tags":["Inference"]}},"/videos/{id}/cancel":{"post":{"operationId":"cancelVideo","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"$ref":"#/components/responses/OpenAIVideo"},"401":{"$ref":"#/components/responses/OpenAIUnauthorized"},"403":{"$ref":"#/components/responses/OpenAIForbidden"},"404":{"$ref":"#/components/responses/OpenAINotFound"},"409":{"$ref":"#/components/responses/OpenAIBadRequest"},"429":{"$ref":"#/components/responses/OpenAIRateLimited"}},"security":[{"bearerAuth":[]}],"summary":"Cancel a video generation job","tags":["Inference"]}},"/videos/{id}/content":{"get":{"description":"Streams the generated MP4 for a completed job. Returns 410 Gone once the artifact has expired (24h retention).","operationId":"getVideoContent","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"video/mp4":{"schema":{"format":"binary","type":"string"}}},"description":"The generated video stream."},"401":{"$ref":"#/components/responses/OpenAIUnauthorized"},"403":{"$ref":"#/components/responses/OpenAIForbidden"},"404":{"$ref":"#/components/responses/OpenAINotFound"},"410":{"$ref":"#/components/responses/OpenAIGone"},"429":{"$ref":"#/components/responses/OpenAIRateLimited"}},"security":[{"bearerAuth":[]}],"summary":"Download a completed video artifact","tags":["Inference"]}},"/webhook-endpoints":{"get":{"operationId":"listWebhookEndpoints","parameters":[{"$ref":"#/components/parameters/CursorParam"},{"$ref":"#/components/parameters/LimitParam"}],"responses":{"200":{"$ref":"#/components/responses/WebhookEndpointPage"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/Problem"}},"summary":"List webhook endpoints","tags":["Webhooks"]},"post":{"operationId":"createWebhookEndpoint","parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"requestBody":{"content":{"application/json":{"schema":{"properties":{"event_types":{"items":{"enum":["instance.creating","instance.running","instance.terminated","instance.failed","video.completed","video.failed","balance.low","subscription_quota.threshold","api_key_budget.threshold","member_spend.threshold","plan_roster.model_removed"],"type":"string"},"type":"array"},"url":{"format":"uri","type":"string"}},"required":["url","event_types"],"type":"object"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}},"description":"Created"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/Problem"}},"summary":"Create a webhook endpoint","tags":["Webhooks"]}},"/webhook-endpoints/{id}":{"delete":{"operationId":"deleteWebhookEndpoint","responses":{"204":{"description":"Deleted"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/Problem"}},"summary":"Delete a webhook endpoint","tags":["Webhooks"]},"get":{"operationId":"getWebhookEndpoint","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}},"description":"OK"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/Problem"}},"summary":"Get a webhook endpoint","tags":["Webhooks"]},"parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}]}},"security":[{"bearerAuth":[]}],"servers":[{"description":"Production","url":"https://api.deeprelay.ai/v1"},{"description":"Demo","url":"https://api.demo.deeprelay.ai/v1"}],"tags":[{"description":"OpenAI-compatible inference: chat completions, image generation, video generation, and the model catalog.","name":"Inference"},{"description":"Reusable launch templates that capture an instance configuration for one-call launches.","name":"Templates"},{"description":"OpenAI-compatible fine-tuning: create and manage fine-tuning jobs and upload JSONL training files.","name":"FineTuning"},{"description":"Community Cloud supplier program: enroll as a supplier, register and manage your machines, and track your earnings.","name":"Community"},{"description":"Service metadata: the liveness probe and the machine-readable OpenAPI specification. No authentication required.","name":"Meta"},{"description":"The public inference benchmark: price, time to first token, output throughput and a weights-identity check for deeprelay models against independently operated deployments of the same weights, measured by an open harness on a public CI run whose id and URL are inside every report. No authentication required.","name":"Benchmarks"},{"description":"The GPU hardware catalog — every GPU type available on the platform. No authentication required.","name":"GpuTypes"},{"description":"Live pricing offers per GPU type, GPU count, region, and tier, with current availability. No authentication required.","name":"Pricing"},{"description":"The launch environment catalog: certified frameworks, selectable versions, \"What's included\" software lists, and the raw-VM OS images accepted by instance launch. No authentication required.","name":"Environments"},{"description":"GPU instances: launch, list, inspect, update, and terminate. Mutating calls return an async operation to poll for completion.","name":"Instances"},{"description":"Async operation tracking. Instance launches and terminations return an operation id; poll it here until the operation reaches a terminal state.","name":"Operations"},{"description":"SSH public keys registered with your organization for instance access.","name":"SshKeys"},{"description":"Webhook endpoints for event delivery. The signing secret is returned once at creation; verify every delivery against it.","name":"Webhooks"},{"description":"Watch rules: get notified when a GPU model's price drops below a number you set, or when it comes back in stock. Watch rules only notify — they never launch capacity and never spend.","name":"Watch"},{"description":"Auto-launch rules: standing, pre-approved purchase orders that launch a frozen instance spec automatically when a GPU model hits your target, bounded by a whole-instance hourly ceiling and a maximum runtime you approve up front. A separate resource from watch rules — watch notifies, auto-launch spends, and no field converts one into the other.","name":"Autolaunch"},{"description":"GPU Market history: deeprelay's own price and obtainable capacity for a GPU model over time, sampled every 15 minutes and retained for a year. A null price or count is a bucket we did not sample, which is NOT the same as a zero.","name":"Market"},{"description":"Time-bucketed usage aggregates for your organization, optionally grouped by instance or GPU type.","name":"Usage"},{"description":"Billing controls and funding: the organization spending limit, account deposits, and the referral program.","name":"Billing"},{"description":"The request log: one row per inference call over the last 90 days, with its status, error code, token counts, cost and latency. Never carries prompt or completion content.","name":"Requests"},{"description":"API key management: mint keys with a monthly budget, a model allowlist, a rate limit and an expiry, then update or revoke them. Writes need the `keys:manage` capability.","name":"ApiKeys"},{"description":"Organization alert settings: the low-balance threshold and which governance alerts are emailed to admins.","name":"Alerts"}]}