{"openapi":"3.1.0","info":{"title":"Modelglass Read API","version":"1.0.0","description":"Pricing-intelligence and comparison data for AI image-generation models. Public read API over the compiled registry + ontology artifact. Paid tiers (Starter/Pro) unlock full pricing history; the free tier sees the active price only.","license":{"name":"All rights reserved","url":"https://modelglass.com.au/terms"},"contact":{"name":"Modelglass","url":"https://modelglass.com.au"}},"servers":[{"url":"https://modelglass-api.vercel.app","description":"Production"},{"url":"http://localhost:8787","description":"Local dev"}],"tags":[{"name":"meta","description":"Service + artifact metadata"},{"name":"models","description":"Models, pricing, and capability data"},{"name":"providers","description":"Hosting providers"},{"name":"benchmarks","description":"Benchmark definitions"},{"name":"news","description":"Structured AI news feed (Pro tier)"},{"name":"billing","description":"Checkout, key lifecycle, portal, and webhooks"},{"name":"account","description":"Self-service account access via the magic-link flow"},{"name":"admin","description":"Internal ops diagnostics — ADMIN_SECRET-gated, not part of the public API"},{"name":"mcp","description":"Model Context Protocol transport"}],"security":[{"bearerAuth":[]}],"paths":{"/v1/health":{"get":{"tags":["meta"],"summary":"Health check","description":"Liveness probe. Public — no authentication required.","security":[],"responses":{"200":{"description":"Service is healthy.","content":{"application/json":{"schema":{"type":"object","required":["ok","data","meta"],"properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"status":{"type":"string","example":"ok"}},"additionalProperties":true},"meta":{"$ref":"#/components/schemas/ResponseMeta"}}}}}}}}},"/v1/openapi.json":{"get":{"tags":["meta"],"summary":"This OpenAPI document","description":"The machine-readable OpenAPI 3.1 spec for this API. Public.","security":[],"responses":{"200":{"description":"The OpenAPI document.","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/v1/meta":{"get":{"tags":["meta"],"summary":"Artifact metadata","description":"Artifact version, build timestamp, content hash, and entity counts.","responses":{"200":{"description":"Artifact metadata.","content":{"application/json":{"schema":{"type":"object","required":["ok","data","meta"],"properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"object","additionalProperties":true,"properties":{"artifact_version":{"type":"integer"},"built_at":{"type":"string","format":"date-time"},"content_hash":{"type":"string"},"counts":{"type":"object","additionalProperties":{"type":"integer"}}}},"meta":{"$ref":"#/components/schemas/ResponseMeta"}}}}}},"401":{"description":"Missing, unknown, or revoked API key (UNAUTHORIZED / NOT_FOUND).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Over the plan's daily rate limit (RATE_LIMITED).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error (INTERNAL).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/models":{"get":{"tags":["models"],"summary":"List models","description":"All tracked models with current pricing and technical profile. Free-tier keys see the active price only; Starter+ see full history.","parameters":[{"name":"provider","in":"query","description":"Filter to a provider slug, e.g. 'replicate'.","schema":{"type":"string"}},{"name":"unit","in":"query","description":"Filter to a billing unit. Billing unit. Current values: `per_image`, `per_megapixel`, `per_second`, `per_step`, `per_clip`, `per_1m_tokens_input`, `per_1m_tokens_output`, `per_1m_tokens`, `per_1k_tokens_input`, `per_1k_tokens_output`, `per_request`, `per_month`, `per_credit`, `per_1m_characters`, `per_minute`, `per_generation`, `per_1k_requests`, `per_1m_tokens_citation`, `per_1m_tokens_reasoning`, `per_1m_tokens_cache_read`, `per_1m_tokens_cache_write`, `per_gpu_second`. New units are added without a schema_version bump, so treat an unknown unit as 'not comparable' rather than an error. `per_1m_tokens_cache_read` and `per_1m_tokens_cache_write` are prompt-cache rates: real prices, never a model's headline price.","schema":{"type":"string"}},{"name":"status","in":"query","description":"Filter to a lifecycle status.","schema":{"type":"string","enum":["ga","preview","deprecated","retired"]}},{"name":"arch","in":"query","description":"Filter to an architecture vocabulary id, e.g. 'flow-matching'.","schema":{"type":"string"}}],"responses":{"200":{"description":"Matching models.","content":{"application/json":{"schema":{"type":"object","required":["ok","data","meta"],"properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"array","items":{"$ref":"#/components/schemas/ModelEntry"}},"meta":{"$ref":"#/components/schemas/ResponseMeta"}}}}}},"401":{"description":"Missing, unknown, or revoked API key (UNAUTHORIZED / NOT_FOUND).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Over the plan's daily rate limit (RATE_LIMITED).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error (INTERNAL).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/models/{modelId}":{"get":{"tags":["models"],"summary":"Get a model","description":"One model by its cross-host model id (may contain a slash).","parameters":[{"name":"modelId","in":"path","required":true,"description":"Cross-host model id, e.g. 'bfl/flux-1-schnell'.","schema":{"type":"string"}}],"responses":{"200":{"description":"The model.","content":{"application/json":{"schema":{"type":"object","required":["ok","data","meta"],"properties":{"ok":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/ModelEntry"},"meta":{"$ref":"#/components/schemas/ResponseMeta"}}}}}},"401":{"description":"Missing, unknown, or revoked API key (UNAUTHORIZED / NOT_FOUND).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No model with that id (NOT_FOUND).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Over the plan's daily rate limit (RATE_LIMITED).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error (INTERNAL).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/models/{modelId}/competitors":{"get":{"tags":["models"],"summary":"Get closest competitors","description":"Returns closest competitors for a model, enriched with live pricing data. Each competitor entry includes name, provider, current_price, last_price and price_delta_ratio. current_price is null when the competitor has no current price (retired or withdrawn); last_price then carries the last price it charged and its last day (effective_to), and is never a live price. price_delta_ratio (competitor/source current price) is null when either side has no current price or the units differ.","parameters":[{"name":"modelId","in":"path","required":true,"description":"Cross-host model id, e.g. 'bfl/flux-1-schnell'.","schema":{"type":"string"}}],"responses":{"200":{"description":"Enriched competitor list (empty array if none defined for this model).","content":{"application/json":{"schema":{"type":"object","required":["ok","data","meta"],"properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"model_id":{"type":"string"},"source_slug":{"type":"string","nullable":true},"competitors":{"type":"array","items":{"type":"object","properties":{"slug":{"type":"string"},"step_distance":{"type":"integer","minimum":1,"maximum":3},"notes":{"type":"string","nullable":true},"model_id":{"type":"string","nullable":true},"model_name":{"type":"string","nullable":true},"provider":{"type":"string","nullable":true},"current_price":{"nullable":true,"type":"object","properties":{"amount":{"type":"number"},"currency":{"type":"string"},"unit":{"type":"string"}}},"last_price":{"nullable":true,"type":"object","description":"Only when current_price is null (a retired or withdrawn competitor): the last price it charged and its last day (effective_to, inclusive; null if open-ended). Not a live price.","properties":{"amount":{"type":"number"},"currency":{"type":"string"},"unit":{"type":"string"},"effective_to":{"type":"string","format":"date","nullable":true}}},"price_delta_ratio":{"type":"number","nullable":true}}}}}},"meta":{"$ref":"#/components/schemas/ResponseMeta"}}}}}},"401":{"description":"Missing, unknown, or revoked API key (UNAUTHORIZED / NOT_FOUND).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No model with that id (NOT_FOUND).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Over the plan's daily rate limit (RATE_LIMITED).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error (INTERNAL).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/providers":{"get":{"tags":["providers"],"summary":"List providers","responses":{"200":{"description":"All providers.","content":{"application/json":{"schema":{"type":"object","required":["ok","data","meta"],"properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"array","items":{"$ref":"#/components/schemas/Provider"}},"meta":{"$ref":"#/components/schemas/ResponseMeta"}}}}}},"401":{"description":"Missing, unknown, or revoked API key (UNAUTHORIZED / NOT_FOUND).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Over the plan's daily rate limit (RATE_LIMITED).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error (INTERNAL).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/providers/{slug}":{"get":{"tags":["providers"],"summary":"Get a provider","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The provider.","content":{"application/json":{"schema":{"type":"object","required":["ok","data","meta"],"properties":{"ok":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/Provider"},"meta":{"$ref":"#/components/schemas/ResponseMeta"}}}}}},"401":{"description":"Missing, unknown, or revoked API key (UNAUTHORIZED / NOT_FOUND).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No provider with that slug (NOT_FOUND).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Over the plan's daily rate limit (RATE_LIMITED).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error (INTERNAL).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/benchmarks":{"get":{"tags":["benchmarks"],"summary":"List benchmark definitions","responses":{"200":{"description":"All benchmark definitions.","content":{"application/json":{"schema":{"type":"object","required":["ok","data","meta"],"properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"array","items":{"$ref":"#/components/schemas/Benchmark"}},"meta":{"$ref":"#/components/schemas/ResponseMeta"}}}}}},"401":{"description":"Missing, unknown, or revoked API key (UNAUTHORIZED / NOT_FOUND).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Over the plan's daily rate limit (RATE_LIMITED).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error (INTERNAL).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/news":{"get":{"tags":["news"],"summary":"List structured AI news entries","description":"Returns the curated news feed of AI model events (launches, price changes, deprecations, spec updates). Requires a Pro, Internal, or App plan key (the App plan is the ModelglassPro iOS app's own auto-provisioned, non-paid tier). Results are sorted reverse-chronologically.","parameters":[{"name":"modality","in":"query","schema":{"type":"string","enum":["image","llm","video","audio"]},"description":"Filter by model modality."},{"name":"type","in":"query","schema":{"type":"string","enum":["new_model","price_change","spec_update","deprecation","other"]},"description":"Filter by entry type."},{"name":"since","in":"query","schema":{"type":"string","format":"date"},"description":"Return entries on or after this date (YYYY-MM-DD)."},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":250,"default":50},"description":"Maximum number of entries to return."}],"responses":{"200":{"description":"Structured news feed entries.","content":{"application/json":{"schema":{"type":"object","required":["ok","data","meta"],"properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"array","items":{"$ref":"#/components/schemas/NewsEntry"}},"meta":{"$ref":"#/components/schemas/ResponseMeta"}}}}}},"401":{"description":"Missing, unknown, or revoked API key (UNAUTHORIZED / NOT_FOUND).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Caller does not have a Pro, Internal, or App plan (FORBIDDEN).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Over the plan's daily rate limit (RATE_LIMITED).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error (INTERNAL).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/checkout/session":{"post":{"tags":["billing"],"summary":"Create a Stripe Checkout session","description":"Create a hosted Stripe Checkout session for a paid plan and return its URL. Public.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["plan"],"properties":{"plan":{"type":"string","enum":["starter","pro"]},"successUrl":{"type":"string","format":"uri"},"cancelUrl":{"type":"string","format":"uri"}}}}}},"responses":{"200":{"description":"Checkout session created.","content":{"application/json":{"schema":{"type":"object","required":["ok","data","meta"],"properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"url":{"type":"string","format":"uri"}}},"meta":{"$ref":"#/components/schemas/ResponseMeta"}}}}}},"400":{"description":"Invalid plan or missing fields (BAD_REQUEST).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Stripe error or misconfiguration (INTERNAL).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/keys/session/{sessionId}":{"get":{"tags":["billing"],"summary":"Retrieve the key for a completed checkout","description":"One-time retrieval of the API key issued for a completed Stripe checkout session. The session id is the credential, so this route is public; the pending key is consumed on read (a second call 404s).","security":[],"parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The issued key.","content":{"application/json":{"schema":{"type":"object","required":["ok","data","meta"],"properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"apiKey":{"type":"string","example":"mg_starter_…"}}},"meta":{"$ref":"#/components/schemas/ResponseMeta"}}}}}},"404":{"description":"No key for this session — the webhook may still be processing (NOT_FOUND).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"410":{"description":"This session's key has already been revealed and cannot be shown again (GONE).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/keys/provision":{"post":{"tags":["billing"],"summary":"Provision an API key","description":"Public bootstrap: `{ \"plan\": \"free\" }` issues an mg_free_… key with no payment; `{ \"plan\": \"app\" }` issues an mg_app_… key with no payment (auto-provisioned by the ModelglassPro iOS app — wider price-history window and /v1/news access, but not a paid tier). Any other plan falls through to an internal-only flow (requires an `internal` Bearer key).","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["plan"],"properties":{"plan":{"type":"string","enum":["free","app","starter","pro"]},"label":{"type":"string","description":"Required for internal (non-free/app) provisioning."}}}}}},"responses":{"200":{"description":"Free or app-plan key issued (bare shape, not the data envelope).","content":{"application/json":{"schema":{"type":"object","properties":{"key":{"type":"string","example":"mg_free_…"},"plan":{"type":"string","enum":["free","app"]},"rate_limit":{"type":"integer","example":100}}}}}},"201":{"description":"Key issued via the internal flow.","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"object","additionalProperties":true}}}}}},"400":{"description":"Invalid plan/label (BAD_REQUEST).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Non-free/app plan without a valid key (UNAUTHORIZED).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Valid key but not `internal` (FORBIDDEN).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Free/app bootstrap limited to 20/day per origin (shared budget), or (internal flow) over the caller's plan rate limit (RATE_LIMITED).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/keys/validate":{"post":{"tags":["billing"],"summary":"Validate a raw API key","description":"Public — the key under test is presented in the body, not as a Bearer token (primary consumer: the iOS unlock screen, before the user has a Bearer credential to use). Always returns 200 and never reveals *why* a key is invalid (revoked / not found / malformed), to prevent key enumeration.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["key"],"properties":{"key":{"type":"string"}}}}}},"responses":{"200":{"description":"Validity result.","content":{"application/json":{"schema":{"type":"object","required":["ok","data","meta"],"properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"object","required":["valid"],"properties":{"valid":{"type":"boolean"},"tier":{"type":"string","enum":["free","starter","pro","internal","app"]}}},"meta":{"$ref":"#/components/schemas/ResponseMeta"}}}}}}}}},"/v1/keys/list":{"get":{"tags":["billing"],"summary":"List all keys (internal only)","description":"Admin-style listing of every non-revoked key across all accounts. Requires an `internal`-plan Bearer key. Raw key strings are never returned.","responses":{"200":{"description":"All key records (masked).","content":{"application/json":{"schema":{"type":"object","required":["ok","data","meta"],"properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"array","items":{"$ref":"#/components/schemas/KeySummary"}},"meta":{"$ref":"#/components/schemas/ResponseMeta"}}}}}},"401":{"description":"Missing, unknown, or revoked API key (UNAUTHORIZED / NOT_FOUND).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Valid key but not `internal` (FORBIDDEN).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Over the plan's daily rate limit (RATE_LIMITED).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error (INTERNAL).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/keys":{"get":{"tags":["billing"],"summary":"List the caller's own keys","description":"Self-service dashboard: lists every key on the same account as the caller's own Bearer key. Available to any authenticated plan.","responses":{"200":{"description":"The caller's key records (masked).","content":{"application/json":{"schema":{"type":"object","required":["ok","data","meta"],"properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"array","items":{"$ref":"#/components/schemas/KeySummary"}},"meta":{"$ref":"#/components/schemas/ResponseMeta"}}}}}},"401":{"description":"Missing, unknown, or revoked API key (UNAUTHORIZED / NOT_FOUND).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Caller's key record not found (NOT_FOUND).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Over the plan's daily rate limit (RATE_LIMITED).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error (INTERNAL).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"post":{"tags":["billing"],"summary":"Create an additional key on the caller's own account","description":"Self-service: adds another key to the caller's account at the same tier, subject to the tier's max-active-keys limit.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"label":{"type":"string"}}}}}},"responses":{"201":{"description":"Key created (raw key shown once).","content":{"application/json":{"schema":{"type":"object","required":["ok","data","meta"],"properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"apiKey":{"type":"string"},"keyId":{"type":"string"},"display":{"type":"string"},"tier":{"type":"string","enum":["free","starter","pro","internal","app"]},"label":{"type":"string","nullable":true},"createdAt":{"type":"string","format":"date-time"}}},"meta":{"$ref":"#/components/schemas/ResponseMeta"}}}}}},"401":{"description":"Missing, unknown, or revoked API key (UNAUTHORIZED / NOT_FOUND).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Caller's key record not found (NOT_FOUND).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Account is at its tier's max-active-keys limit (KEY_LIMIT_EXCEEDED).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Over the plan's daily rate limit (RATE_LIMITED).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error (INTERNAL).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/keys/{keyId}/roll":{"post":{"tags":["billing"],"summary":"Roll (rotate) a key","description":"Self-service: issues a new key and transitions the target key out of service — free plan revokes it instantly, starter/pro give it a grace period (default 24h; pro can configure up to 7 days via `gracePeriodHours`). The target key must belong to the caller's own account (a key may roll itself, or another key on the same account); otherwise 404 — this is exactly the 'I just leaked my key, roll it myself, one last time' path, and it never reveals whether a keyId belonging to a different account exists.","parameters":[{"name":"keyId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"gracePeriodHours":{"type":"number","description":"Pro/internal only; 400 on other tiers."}}}}}},"responses":{"201":{"description":"New key issued; the target key is rolled.","content":{"application/json":{"schema":{"type":"object","required":["ok","data","meta"],"properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"apiKey":{"type":"string"},"keyId":{"type":"string"},"display":{"type":"string"},"tier":{"type":"string","enum":["free","starter","pro","internal","app"]},"createdAt":{"type":"string","format":"date-time"},"rolledKeyId":{"type":"string"},"rolledKeyStatus":{"type":"string","enum":["revoked","grace"]}}},"meta":{"$ref":"#/components/schemas/ResponseMeta"}}}}}},"400":{"description":"Missing keyId, target key not active, or gracePeriodHours set on a non-pro tier (BAD_REQUEST).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing, unknown, or revoked API key (UNAUTHORIZED / NOT_FOUND).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Caller's key record not found, or the target key doesn't exist / belongs to a different account (NOT_FOUND).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Over the plan's daily rate limit (RATE_LIMITED).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error (INTERNAL).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/keys/{keyId}":{"delete":{"tags":["billing"],"summary":"Revoke (delete) a key","description":"Self-service: hard-revokes a key belonging to the caller's own account (a key may delete itself, or another key on the same account); 404 if it belongs to a different account or doesn't exist.","parameters":[{"name":"keyId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Key revoked.","content":{"application/json":{"schema":{"type":"object","required":["ok","data","meta"],"properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"revoked":{"type":"boolean","enum":[true]},"keyId":{"type":"string"}}},"meta":{"$ref":"#/components/schemas/ResponseMeta"}}}}}},"400":{"description":"Key is already revoked (BAD_REQUEST).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing, unknown, or revoked API key (UNAUTHORIZED / NOT_FOUND).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Caller's key record not found, or the target key doesn't exist / belongs to a different account (NOT_FOUND).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Over the plan's daily rate limit (RATE_LIMITED).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error (INTERNAL).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/billing/portal":{"post":{"tags":["billing"],"summary":"Create a Stripe billing portal session","description":"Public — the magic `token` (from the emailed account link, see `/v1/auth/magic-link`) is the credential, not a Bearer key. Requires the Stripe Customer Portal to be configured in the Stripe Dashboard.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["token"],"properties":{"token":{"type":"string"}}}}}},"responses":{"200":{"description":"Billing portal session URL.","content":{"application/json":{"schema":{"type":"object","required":["ok","data","meta"],"properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"url":{"type":"string","format":"uri"}}},"meta":{"$ref":"#/components/schemas/ResponseMeta"}}}}}},"400":{"description":"Missing `token` (BAD_REQUEST).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Token expired, already used, or unknown (NOT_FOUND).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Stripe not configured, or a Stripe error (CONFIGURATION_ERROR / INTERNAL).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/auth/magic-link":{"post":{"tags":["account"],"summary":"Request a magic sign-in link","description":"Public. Looks up the Stripe customer by email and emails a 15-minute magic link to `/account`. Always returns `ok:true` regardless of whether the email has an account, to prevent email enumeration. Rate-limited per IP.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email"}}}}}},"responses":{"200":{"description":"Request accepted, regardless of whether the email is known.","content":{"application/json":{"schema":{"type":"object","required":["ok","data","meta"],"properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"sent":{"type":"boolean","enum":[true]}}},"meta":{"$ref":"#/components/schemas/ResponseMeta"}}}}}},"400":{"description":"Missing or invalid `email` (BAD_REQUEST).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too many magic-link requests from this IP this hour (RATE_LIMITED).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/auth/account":{"get":{"tags":["account"],"summary":"View account key info via magic token","description":"Public — the magic `token` query param (from the emailed link) is the credential, not a Bearer key.","security":[],"parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Masked key info for the account.","content":{"application/json":{"schema":{"type":"object","required":["ok","data","meta"],"properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"email":{"type":"string"},"keys":{"type":"array","items":{"type":"object","additionalProperties":true}}}},"meta":{"$ref":"#/components/schemas/ResponseMeta"}}}}}},"400":{"description":"Missing `token` query param (BAD_REQUEST).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Link expired, already used, or unknown (NOT_FOUND).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/auth/regenerate":{"post":{"tags":["account"],"summary":"Roll a key via magic token","description":"Public — consumes the magic token (single-use for this action) and rolls the specified key, which must belong to the token's account.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["token","keyId"],"properties":{"token":{"type":"string"},"keyId":{"type":"string"}}}}}},"responses":{"201":{"description":"New key issued.","content":{"application/json":{"schema":{"type":"object","required":["ok","data","meta"],"properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"apiKey":{"type":"string"},"keyId":{"type":"string"},"display":{"type":"string"},"tier":{"type":"string","enum":["free","starter","pro","internal","app"]},"planLabel":{"type":"string"},"createdAt":{"type":"string","format":"date-time"}}},"meta":{"$ref":"#/components/schemas/ResponseMeta"}}}}}},"400":{"description":"Missing token/keyId, or the key isn't active (BAD_REQUEST).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Token expired/unknown, or key not found on this account (NOT_FOUND).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Free-plan regeneration limit (5/day) reached (RATE_LIMITED).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/webhooks/stripe":{"post":{"tags":["billing"],"summary":"Stripe webhook receiver","description":"Receives Stripe events (checkout.session.completed, customer.subscription.deleted). Authenticates via the `Stripe-Signature` header over the raw request body — not an API key.","security":[],"parameters":[{"name":"Stripe-Signature","in":"header","required":true,"description":"Stripe webhook signature (HMAC over the raw body).","schema":{"type":"string"}}],"requestBody":{"required":true,"description":"The raw Stripe event payload (verified byte-for-byte).","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"200":{"description":"Event received and acknowledged.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Missing or invalid Stripe signature (BAD_REQUEST).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/admin/kv-check":{"get":{"tags":["admin"],"summary":"KV connectivity diagnostic","description":"Internal ops diagnostic — confirms the Upstash KV env vars are set and a live read/write round-trip works. Not part of the public API; documented here only so the spec matches the router exactly.","security":[{"adminSecret":[]}],"responses":{"200":{"description":"KV diagnostic result.","content":{"application/json":{"schema":{"type":"object","required":["ok","data","meta"],"properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"kv_url_set":{"type":"boolean"},"kv_token_set":{"type":"boolean"},"kv_ping":{"type":"string","enum":["ok","failed","not_configured"]},"test_write":{"type":"string","enum":["ok","failed","not_configured"]}}},"meta":{"$ref":"#/components/schemas/ResponseMeta"}}}}}},"401":{"description":"Invalid or missing admin secret (UNAUTHORIZED).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/admin/keys/{keyId}":{"delete":{"tags":["admin"],"summary":"Hard-revoke any key (admin)","description":"Internal ops-only revoke by keyId, bypassing account-ownership checks. Idempotent — revoking an already-revoked key still returns 200. Not part of the public API.","security":[{"adminSecret":[]}],"parameters":[{"name":"keyId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Key revoked.","content":{"application/json":{"schema":{"type":"object","required":["ok","data","meta"],"properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"revoked":{"type":"boolean","enum":[true]},"keyId":{"type":"string"}}},"meta":{"$ref":"#/components/schemas/ResponseMeta"}}}}}},"401":{"description":"Invalid or missing admin secret (UNAUTHORIZED).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No key with that keyId (NOT_FOUND).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/mcp":{"post":{"tags":["mcp"],"summary":"MCP transport (JSON-RPC)","description":"Stateless Model Context Protocol endpoint (JSON-RPC 2.0). Methods: initialize, ping, tools/list, tools/call. Gated by the same Bearer-key auth as /v1; tools read data as the caller's plan.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["jsonrpc","method"],"properties":{"jsonrpc":{"type":"string","enum":["2.0"]},"id":{"type":["string","number","null"]},"method":{"type":"string","example":"tools/list"},"params":{"type":"object","additionalProperties":true}}}}}},"responses":{"200":{"description":"JSON-RPC result or error envelope.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"202":{"description":"Notification acknowledged (no id)."},"401":{"description":"Missing or invalid API key (UNAUTHORIZED).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Over the plan's daily rate limit (RATE_LIMITED).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"API key as a Bearer token: `Authorization: Bearer <key>`. Get a free key at https://modelglass.com.au/signup."},"adminSecret":{"type":"http","scheme":"bearer","description":"The `ADMIN_SECRET` env var as a Bearer token — NOT an API key, and not resolved via the key store. Internal ops use only."}},"schemas":{"ResponseMeta":{"type":"object","required":["artifact_version","built_at"],"properties":{"artifact_version":{"type":"integer"},"built_at":{"type":"string","format":"date-time"},"count":{"type":"integer","description":"Item count when `data` is a collection."}}},"ErrorResponse":{"type":"object","required":["ok","error"],"properties":{"ok":{"type":"boolean","enum":[false]},"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["NOT_FOUND","BAD_REQUEST","UNAUTHORIZED","FORBIDDEN","RATE_LIMITED","CONFIGURATION_ERROR","GONE","KEY_LIMIT_EXCEEDED","INTERNAL"]},"message":{"type":"string"}}}}},"Price":{"type":"object","required":["amount","currency","unit","effective_from","source"],"properties":{"amount":{"type":"number"},"currency":{"type":"string","example":"USD"},"unit":{"type":"string","description":"Billing unit. Current values: `per_image`, `per_megapixel`, `per_second`, `per_step`, `per_clip`, `per_1m_tokens_input`, `per_1m_tokens_output`, `per_1m_tokens`, `per_1k_tokens_input`, `per_1k_tokens_output`, `per_request`, `per_month`, `per_credit`, `per_1m_characters`, `per_minute`, `per_generation`, `per_1k_requests`, `per_1m_tokens_citation`, `per_1m_tokens_reasoning`, `per_1m_tokens_cache_read`, `per_1m_tokens_cache_write`, `per_gpu_second`. New units are added without a schema_version bump, so treat an unknown unit as 'not comparable' rather than an error. `per_1m_tokens_cache_read` and `per_1m_tokens_cache_write` are prompt-cache rates: real prices, never a model's headline price.","example":"per_1m_tokens_input"},"region":{"type":"string"},"hardware":{"type":"string"},"effective_from":{"type":"string","format":"date"},"effective_to":{"type":"string","format":"date","description":"Absent while current."},"source":{"type":"object","required":["url","verified_at","method"],"properties":{"url":{"type":"string","format":"uri"},"verified_at":{"type":"string","format":"date"},"method":{"type":"string","enum":["manual","automated"]}},"additionalProperties":false},"notes":{"type":"string","description":"Public commentary for a reader using this data (e.g. a sale price or regional caveat). May be absent."},"recorded_at":{"type":"string","format":"date","description":"Date Modelglass first recorded this row (set on correction replacement rows)."},"corrected":{"type":"object","description":"Present when this row replaces a price Modelglass recorded wrongly for these dates. The wrong row itself is never served.","required":["at","note"],"properties":{"at":{"type":"string","format":"date","description":"When the wrong row was corrected."},"note":{"type":"string","description":"A fixed public sentence saying this row replaces a wrongly recorded price."}},"additionalProperties":false}},"additionalProperties":false},"Tier":{"type":"object","required":["id","label","pricing"],"properties":{"id":{"type":"string"},"label":{"type":"string"},"attributes":{"type":"object","additionalProperties":true},"pricing":{"type":"array","items":{"$ref":"#/components/schemas/Price"}}},"additionalProperties":false},"Offering":{"type":"object","required":["slug","name","provider","model","tiers"],"properties":{"schema_version":{"type":"integer"},"slug":{"type":"string"},"name":{"type":"string"},"provider":{"type":"string","description":"Provider slug."},"provider_model_id":{"type":"string","description":"The model name in the provider's own API."},"modality":{"type":"string","description":"Coarse modality group, e.g. `image`, `llm`, `video`, `tts`, `stt`, `music`, `3d-generation`. `model.modality` is the finer task type (e.g. `text-to-video`)."},"quality_tier":{"type":"string","enum":["fast","standard","premium"]},"category":{"type":"string","enum":["llm","search-grounded"]},"model":{"type":"object","required":["id","creator","modality","status"],"properties":{"id":{"type":"string","example":"bfl/flux-1-schnell"},"creator":{"type":"string"},"family":{"type":"string"},"modality":{"type":"string"},"status":{"type":"string","enum":["ga","preview","deprecated","retired"]},"released":{"type":"string"},"generation":{"type":"string","enum":["current","previous"]},"derived_from":{"type":"array","items":{"type":"object","properties":{"model_id":{"type":"string"},"relationship":{"type":"string"}},"additionalProperties":false}},"input_types":{"type":"array","items":{"type":"string"}},"output_formats":{"type":"array","items":{"type":"string"}},"open_weights":{"type":"boolean"}},"additionalProperties":false},"capabilities":{"type":"object","additionalProperties":true},"normalization":{"type":"object","additionalProperties":true},"closest_competitors":{"type":"array","items":{"type":"object","required":["slug","step_distance"],"properties":{"slug":{"type":"string"},"step_distance":{"type":"integer"},"notes":{"type":"string","description":"Public commentary for a reader using this data (e.g. a sale price or regional caveat). May be absent."}},"additionalProperties":false}},"search_context_pricing":{"type":"object","properties":{"low":{"$ref":"#/components/schemas/Price"},"medium":{"$ref":"#/components/schemas/Price"},"high":{"$ref":"#/components/schemas/Price"}},"additionalProperties":false},"tiers":{"type":"array","items":{"$ref":"#/components/schemas/Tier"}},"notes":{"type":"string","description":"Public commentary for a reader using this data (e.g. a sale price or regional caveat). May be absent."}},"additionalProperties":false},"ModelEntry":{"type":"object","description":"A model grouped across providers: the join of its pricing offerings and ontology knowledge (capability profile, benchmarks, architecture).","required":["model_id","name","offerings"],"properties":{"model_id":{"type":"string","example":"bfl/flux-1-schnell"},"name":{"type":"string"},"join_status":{"type":"string","enum":["joined","pricing_only","ontology_only"]},"limitations":{"type":"array","items":{"type":"string"}},"knowledge":{"oneOf":[{"$ref":"#/components/schemas/Knowledge"},{"type":"null"}]},"offerings":{"type":"array","items":{"$ref":"#/components/schemas/Offering"}}},"additionalProperties":false},"Knowledge":{"type":"object","description":"Technical identity and comparison facts (ontology). Modality-specific facts (video, audio, 3D) appear only on models of that modality.","properties":{"schema_version":{"type":"integer"},"model_id":{"type":"string"},"name":{"type":"string"},"creator":{"type":"string"},"context_window":{"type":"integer"},"architecture":{"type":"object","properties":{"type":{"type":"string"},"notes":{"type":"string"}},"additionalProperties":false},"training":{"type":"object","properties":{"paradigms":{"type":"array","items":{"type":"string"}},"notes":{"type":"string"}},"additionalProperties":false},"capability_profile":{"type":"array","items":{"type":"object","properties":{"dimension":{"type":"string"},"rating":{"type":"string","enum":["strong","moderate","weak","variable","unknown"]},"notes":{"type":"string","description":"Public commentary for a reader using this data (e.g. a sale price or regional caveat). May be absent."}},"additionalProperties":false}},"benchmarks":{"type":"array","items":{"$ref":"#/components/schemas/BenchmarkResult"}},"benchmark_notes":{"type":"string"},"use_cases":{"type":"array","items":{"type":"string"}},"routing_guidance":{"type":"string"},"limitations":{"type":"array","items":{"type":"string"}},"avoid_use":{"type":"array","items":{"type":"object","properties":{"context":{"type":"string"},"description":{"type":"string"},"severity":{"type":"string"},"source_type":{"type":"string"},"source_url":{"type":"string","format":"uri"}},"additionalProperties":false}},"citations":{"type":"array","items":{"type":"object","properties":{"title":{"type":"string"},"url":{"type":"string","format":"uri"},"note":{"type":"string"}},"additionalProperties":false}},"origin":{"type":"object","properties":{"lab":{"type":"string"},"country":{"type":"string"},"founded":{"type":["string","integer"]}},"additionalProperties":false},"license":{"type":"object","properties":{"type":{"type":"string"},"commercial_use":{"type":"boolean"},"url":{"type":"string"}},"additionalProperties":false},"ethical_notes":{"type":"array","items":{"type":"string"}},"training_data_notes":{"type":"string"},"background":{"type":"array","items":{"type":"string"}},"capability_confidence":{"type":"string","enum":["high","medium","low"]},"notes":{"type":"string","description":"Public commentary for a reader using this data (e.g. a sale price or regional caveat). May be absent."},"sub_modality":{"type":"string"},"api_availability":{"type":"string"},"features_and_differences":{"type":"object","additionalProperties":true},"generation_modes":{"type":"array","items":{"type":"string"}},"supported_resolutions":{"type":"array","items":{"type":"string"}},"fps_options":{"type":"array","items":{"type":"integer"}},"max_clip_duration":{"type":"number"},"native_audio":{"type":"boolean"},"watermark":{"type":"boolean"},"languages_supported":{"type":"integer"},"voice_count":{"type":"integer"},"voice_cloning":{"type":"boolean"},"ssml_support":{"type":"boolean"},"streaming":{"type":"boolean"},"max_polygons":{"type":"integer"}},"additionalProperties":false},"BenchmarkResult":{"type":"object","description":"One benchmark score. Ontology rows use benchmark/score/config/source (or, for audio, name/value/unit/source_url); capability-vertical rows add harness, variant and score_date.","properties":{"benchmark":{"type":"string"},"score":{"type":["number","string"]},"config":{"type":"string"},"source":{"type":"object","properties":{"url":{"type":"string","format":"uri"},"type":{"type":"string","enum":["paper","independent","leaderboard","vendor"]},"verified_at":{"type":"string","format":"date"}},"additionalProperties":false},"name":{"type":"string"},"value":{"type":"number"},"unit":{"type":"string"},"source_url":{"type":"string","format":"uri"},"measured_at":{"type":"string","format":"date"},"harness":{"type":"string"},"harness_version":{"type":"string"},"variant":{"type":"string"},"score_date":{"type":"string"},"baseline":{"type":"number"},"recorded_at":{"type":"string","format":"date"},"notes":{"type":"string","description":"Public commentary for a reader using this data (e.g. a sale price or regional caveat). May be absent."}},"additionalProperties":false},"Provider":{"type":"object","required":["slug","name","url","pricing_page","status"],"properties":{"slug":{"type":"string"},"name":{"type":"string"},"url":{"type":"string","format":"uri"},"pricing_page":{"type":"string","format":"uri"},"status":{"type":"string","enum":["active","deprecated","retired"]},"host_type":{"type":"string"},"modalities":{"type":"array","items":{"type":"string"}},"notes":{"type":"string","description":"Public commentary for a reader using this data (e.g. a sale price or regional caveat). May be absent."}},"additionalProperties":false},"Benchmark":{"type":"object","required":["id","name","summary","measures","score_range","limitations","url"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"version":{"type":"string"},"summary":{"type":"string"},"measures":{"type":"array","items":{"type":"string"}},"category":{"type":"string"},"score_range":{"type":"object","required":["direction","description"],"properties":{"direction":{"type":"string","enum":["higher_is_better","lower_is_better"]},"min":{"type":"number"},"max":{"type":"number"},"unbounded":{"type":"boolean"},"unit":{"type":"string"},"description":{"type":"string"}},"additionalProperties":false},"scale_anchor":{"type":"string","description":"What a score on an Elo-shaped scale is pinned to."},"caveat":{"type":"string","description":"How to read or compare scores on this benchmark."},"limitations":{"type":"array","items":{"type":"string"}},"url":{"type":"string","format":"uri"},"superseded_by":{"type":"string","description":"The benchmark id that replaced this one."},"notes":{"type":"string","description":"Public commentary for a reader using this data (e.g. a sale price or regional caveat). May be absent."}},"additionalProperties":false},"KeySummary":{"type":"object","description":"A masked key record — the raw key string is never returned. `GET /v1/keys/list` (internal) additionally exposes `accountId`; `GET /v1/keys` (self-service) additionally exposes `maxKeys`.","required":["keyId","display","tier","status"],"properties":{"keyId":{"type":"string"},"display":{"type":"string","example":"mg_pro_…ab12"},"tier":{"type":"string","enum":["free","starter","pro","internal","app"]},"label":{"type":"string","nullable":true},"status":{"type":"string","enum":["active","grace","revoked","suspended"]},"formatVersion":{"type":"integer"},"isLegacyFormat":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time"},"expiresAt":{"type":"string","format":"date-time","nullable":true},"lastUsedAt":{"type":"string","format":"date-time","nullable":true}},"additionalProperties":true},"NewsEntry":{"type":"object","required":["date","title","type","modality","provider","summary","source_url","impact"],"properties":{"date":{"type":"string","format":"date"},"title":{"type":"string"},"type":{"type":"string","enum":["new_model","price_change","spec_update","deprecation","other"]},"modality":{"type":"string","enum":["image","llm","video","audio"]},"provider":{"type":"string"},"model_slug":{"type":["string","null"]},"summary":{"type":"string"},"source_url":{"type":"string","format":"uri"},"impact":{"type":"string","enum":["low","medium","high"]}}}}}}