Files
OSGAccountServer/docs/openapi.yaml
T
Rocky 9fb947aa7d
CI / verify (push) Has been cancelled
CI / publish (push) Has been cancelled
Add runtime provider controls and searchable AI routing
Manage provider keys at runtime, route current-information questions through server-side search with safe fallback, and scope OOBE usage claims to grants.
2026-08-22 16:33:17 +08:00

2866 lines
108 KiB
YAML

openapi: 3.1.0
info:
title: OSG Account Server API
version: 0.1.0
description: |
Account, credit, referral, integrity, and managed AI APIs for OSGKeyboard.
Local features and user-owned API keys remain independent of this service.
servers:
- url: https://account.osglab.com
security:
- bearerAuth: []
paths:
/health:
get:
security: []
summary: Compatibility health check
responses:
"200": { $ref: "#/components/responses/HealthUp" }
/health/live:
get:
security: []
summary: Process liveness
responses:
"200": { $ref: "#/components/responses/HealthUp" }
/health/ready:
get:
security: []
summary: Database and migration readiness
responses:
"200": { $ref: "#/components/responses/HealthUp" }
"503":
description: Database is unavailable or migrations failed.
/v1/auth/apple:
post:
security: []
summary: Exchange Sign in with Apple credentials for an OSG session
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/AppleSignInRequest" }
responses:
"200":
description: Authenticated session
content:
application/json:
schema: { $ref: "#/components/schemas/SessionTokenEnvelope" }
default: { $ref: "#/components/responses/Error" }
/v1/auth/refresh:
post:
security: []
summary: Rotate a refresh token
requestBody:
required: true
content:
application/json:
schema:
type: object
additionalProperties: false
required: [refreshToken]
properties:
refreshToken: { type: string, minLength: 32 }
responses:
"200":
description: Rotated session
content:
application/json:
schema: { $ref: "#/components/schemas/SessionTokenEnvelope" }
default: { $ref: "#/components/responses/Error" }
/v1/auth/logout:
post:
summary: Revoke the current session family
responses:
"204": { description: Session revoked }
default: { $ref: "#/components/responses/Error" }
/v1/analytics/events:
post:
security:
- {}
- bearerAuth: []
summary: Idempotently accept privacy-minimized product events
description: |
Accepts pre-login or authenticated client events. The random
installation UUID is stored only as a digest. When a valid bearer
session is supplied, the installation is linked to the account and is
deleted with that account. Audio, user text, prompts, transcripts,
model output, credentials and arbitrary properties are never accepted.
New clients must send installationId once at the batch level. During
migration, legacy clients that send the same installationId on every
event remain accepted; conflicting or incomplete identities are rejected.
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/ProductAnalyticsBatchRequest" }
responses:
"200":
description: Atomic batch acceptance and replay counts
content:
application/json:
schema: { $ref: "#/components/schemas/ProductAnalyticsBatchResponse" }
"400": { $ref: "#/components/responses/Error" }
"409": { $ref: "#/components/responses/Error" }
"422": { $ref: "#/components/responses/Error" }
default: { $ref: "#/components/responses/Error" }
/v1/analytics/keyboard-usage:
post:
security:
- {}
- bearerAuth: []
summary: Idempotently accept privacy-minimized daily keyboard usage summaries
description: |
Accepts finalized UTC-day counters for text manually committed by
OSGKeyboard. Language classification happens on-device. Raw text,
keystrokes, surrounding context, host application identifiers, voice
transcripts and AI output are never accepted. The current UTC date is
not accepted because daily summaries are immutable once submitted.
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/KeyboardUsageBatchRequest" }
responses:
"200":
description: Atomic batch acceptance and replay counts
content:
application/json:
schema: { $ref: "#/components/schemas/ProductAnalyticsBatchResponse" }
"400": { $ref: "#/components/responses/Error" }
"409": { $ref: "#/components/responses/Error" }
"422": { $ref: "#/components/responses/Error" }
default: { $ref: "#/components/responses/Error" }
/v1/account:
get:
summary: Return the account profile
responses:
"200":
description: Account profile
content:
application/json:
schema: { $ref: "#/components/schemas/AccountEnvelope" }
default: { $ref: "#/components/responses/Error" }
patch:
summary: Update the current account nickname
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/UpdateAccountProfileRequest" }
responses:
"200":
description: Updated account profile
content:
application/json:
schema: { $ref: "#/components/schemas/AccountEnvelope" }
default: { $ref: "#/components/responses/Error" }
delete:
summary: Reauthenticate with Apple, delete the account, and revoke authorization
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/DeleteAccountRequest" }
responses:
"204": { description: Account deleted }
default: { $ref: "#/components/responses/Error" }
/v1/apple/events:
post:
security: []
summary: Receive a signed Apple server-to-server account event
requestBody:
required: true
content:
application/json:
schema:
type: object
additionalProperties: false
required: [payload]
properties:
payload: { type: string }
responses:
"204": { description: Event accepted }
default: { $ref: "#/components/responses/Error" }
/v1/credits/balance:
get:
summary: Return available and consumed integer credits
responses:
"200":
description: Credit account
content:
application/json:
schema: { $ref: "#/components/schemas/CreditAccount" }
default: { $ref: "#/components/responses/Error" }
/v1/credits/ledger:
get:
summary: Return immutable credit history
parameters:
- $ref: "#/components/parameters/Limit"
responses:
"200":
description: Ledger entries
content:
application/json:
schema:
type: array
items: { $ref: "#/components/schemas/LedgerEntry" }
default: { $ref: "#/components/responses/Error" }
/v1/credits/rates:
get:
summary: Return effective managed-provider rate cards
responses:
"200":
description: Effective rate cards
content:
application/json:
schema:
type: array
items: { type: object, additionalProperties: true }
default: { $ref: "#/components/responses/Error" }
/v1/storekit/products:
get:
summary: Return enabled consumable credit products
description: |
Current catalog: `500tks` grants 500 credits, `1500tks` grants 1,500
credits, and `3000tks` grants 3,000 credits. Localized prices are
supplied by StoreKit.
responses:
"200":
description: StoreKit credit product catalog
content:
application/json:
schema:
type: array
items: { $ref: "#/components/schemas/StoreKitProduct" }
default: { $ref: "#/components/responses/Error" }
/v1/storekit/transactions:
get:
summary: Return credited StoreKit purchase history for the current account
description: |
Returns only App Store transactions that were previously verified and
committed with their immutable credit-ledger entries. Results include
purchases credited before this endpoint was introduced and never call
the App Store at query time. The opaque cursor follows the stable
descending order of `purchasedAt` and `transactionId`.
parameters:
- $ref: "#/components/parameters/Limit"
- name: cursor
in: query
description: Opaque cursor returned by the preceding page.
schema: { type: string, minLength: 1, maxLength: 256 }
responses:
"200":
description: Credited StoreKit purchases for the authenticated account
content:
application/json:
schema: { $ref: "#/components/schemas/StoreKitTransactionHistory" }
"400": { $ref: "#/components/responses/Error" }
default: { $ref: "#/components/responses/Error" }
post:
summary: Verify an App Store transaction and idempotently grant credits
description: |
Submit the StoreKit 2 `VerificationResult.jwsRepresentation` before
finishing the consumable transaction. The purchase must include an
`appAccountToken` equal to the authenticated account UUID. Replaying
the same App Store transaction returns the original grant.
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/StoreKitTransactionRequest" }
responses:
"200":
description: Verified purchase grant or idempotent replay
content:
application/json:
schema: { $ref: "#/components/schemas/StoreKitPurchase" }
"409": { $ref: "#/components/responses/Error" }
"422": { $ref: "#/components/responses/Error" }
default: { $ref: "#/components/responses/Error" }
/v1/referrals:
get:
summary: List invitees without exposing their Apple identity
parameters:
- $ref: "#/components/parameters/Limit"
responses:
"200":
description: Referral bindings
content:
application/json:
schema:
type: array
items: { type: object, additionalProperties: true }
default: { $ref: "#/components/responses/Error" }
/v1/referrals/me:
get:
summary: Return the referral profile and provision its permanent account invite code
responses:
"200":
description: Referral profile
content:
application/json:
schema: { $ref: "#/components/schemas/ReferralProfile" }
default: { $ref: "#/components/responses/Error" }
/v1/referrals/code:
post:
deprecated: true
summary: Compatibility endpoint for the permanent account invite code
responses:
"200":
description: Invite code
content:
application/json:
schema: { $ref: "#/components/schemas/ReferralCode" }
default: { $ref: "#/components/responses/Error" }
/v1/referrals/redeem:
post:
summary: Bind the account to an inviter during the eligibility window
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/RedeemReferralRequest" }
responses:
"200": { description: Referral binding }
default: { $ref: "#/components/responses/Error" }
/v1/referrals/bind:
post:
deprecated: true
summary: Compatibility alias for referral redemption
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/RedeemReferralRequest" }
responses:
"200": { description: Referral binding }
default: { $ref: "#/components/responses/Error" }
/v1/referrals/campaigns:
get:
summary: Return active referral campaigns
responses:
"200": { description: Active campaigns }
default: { $ref: "#/components/responses/Error" }
/v1/integrity/challenges:
post:
security: []
summary: Issue a single-use App Attest challenge
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/AppAttestChallengeRequest" }
responses:
"201":
description: Challenge issued
content:
application/json:
schema: { $ref: "#/components/schemas/AppAttestChallengeResponse" }
default: { $ref: "#/components/responses/Error" }
/v1/integrity/attest:
post:
security: []
summary: Register a validated App Attest key
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/AppAttestationRequest" }
responses:
"204": { description: Key registered }
default: { $ref: "#/components/responses/Error" }
/v1/integrity/assert:
post:
security: []
summary: Validate a challenge-only App Attest assertion
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/AppAssertionRequest" }
responses:
"200": { description: Assertion counter advanced }
default: { $ref: "#/components/responses/Error" }
/v1/oobe/grants:
post:
security: []
summary: Create a short-lived anonymous OOBE gateway grant
description: |
Verifies an App Attest assertion bound to the installation and returns
credentials limited to the four onboarding AI pages. Each feature can
succeed once within this short-lived grant; a later OOBE run receives
a new grant so the guided experience remains repeatable.
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/CreateOobeGrantRequest" }
responses:
"201":
description: OOBE gateway credentials
content:
application/json:
schema: { $ref: "#/components/schemas/OobeGrantTokens" }
default: { $ref: "#/components/responses/GatewayError" }
/v1/oobe/grants/refresh:
post:
security: []
summary: Rotate an anonymous OOBE refresh token
parameters:
- $ref: "#/components/parameters/IdempotencyKey"
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/RefreshOobeGrantRequest" }
responses:
"200":
description: Rotated OOBE gateway credentials
content:
application/json:
schema: { $ref: "#/components/schemas/OobeGrantTokens" }
default: { $ref: "#/components/responses/GatewayError" }
/v1/gateway/catalog:
get:
summary: Return configured managed-provider capabilities
responses:
"200": { description: Provider catalog }
default: { $ref: "#/components/responses/Error" }
/v1/gateway/grants:
post:
summary: Create an idempotent managed-provider grant
parameters:
- $ref: "#/components/parameters/IdempotencyKey"
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/CreateGatewayGrantRequest" }
responses:
"201":
description: Gateway grant and rotating credentials
content:
application/json:
schema: { $ref: "#/components/schemas/GatewayGrantTokens" }
default: { $ref: "#/components/responses/GatewayError" }
/v1/gateway/grants/refresh:
post:
security: []
summary: Rotate a gateway refresh token
parameters:
- $ref: "#/components/parameters/IdempotencyKey"
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/RefreshGatewayGrantRequest" }
responses:
"200":
description: Rotated gateway credentials
content:
application/json:
schema: { $ref: "#/components/schemas/GatewayGrantTokens" }
default: { $ref: "#/components/responses/GatewayError" }
/v1/gateway/grants/{grantId}:
delete:
summary: Revoke a gateway grant
parameters:
- name: grantId
in: path
required: true
schema: { type: string, format: uuid }
responses:
"204": { description: Gateway grant revoked }
default: { $ref: "#/components/responses/GatewayError" }
/v1/gateway/llm/{capability}:
post:
summary: Run a metered polish, AI, or agent request
description: |
The server deterministically selects model, thinking, search, tools, retry,
and output-budget policy from `capability` plus optional `taskKind`. It
never infers task type from `input` or `context`, and clients cannot
supply provider parameters. Ordinary AI questions allow model-selected
server-side web search; current-information questions require it. Other
tools remain disabled. Search has no separate credit fee; settlement
uses the provider-reported LLM input and output Token counts, including
any search context charged by the provider.
For account grants, `oobe` is accepted only for dictation polish and the
first successful request per account is complimentary. Anonymous OOBE
grants require a matching `oobeFeature` and allow one successful request
per feature within that grant. A new OOBE grant starts a fresh guided
session; repeated calls within one page still fail without paid fallback.
parameters:
- $ref: "#/components/parameters/RequestId"
- name: capability
in: path
required: true
schema: { type: string, enum: [polish, ai, agent] }
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/TextGatewayRequest" }
responses:
"200":
description: JSON response, or SSE when stream is true
default: { $ref: "#/components/responses/GatewayError" }
/v1/gateway/asr:
post:
summary: Run buffered metered ASR
parameters:
- $ref: "#/components/parameters/RequestId"
- { name: X-Audio-Duration-Ms, in: header, required: true, schema: { type: integer, minimum: 1, maximum: 600000 } }
- { name: X-Audio-Format, in: header, schema: { type: string, enum: [pcm, wav, ogg, mp3], default: pcm } }
- { name: X-Audio-Codec, in: header, schema: { type: string, enum: [raw, opus], default: raw } }
requestBody:
required: true
content:
application/octet-stream:
schema: { type: string, contentEncoding: binary }
responses:
"200": { description: Newline-delimited Volcengine ASR result frames }
default: { $ref: "#/components/responses/GatewayError" }
/v1/gateway/asr/sessions:
post:
summary: Reserve credits and create a one-shot streaming ASR session
parameters:
- $ref: "#/components/parameters/RequestId"
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/CreateAsrSessionRequest" }
responses:
"201":
description: Session created
content:
application/json:
schema: { $ref: "#/components/schemas/CreateAsrSessionResponse" }
default: { $ref: "#/components/responses/GatewayError" }
/v1/gateway/asr/sessions/{sessionId}/stream:
get:
summary: Upgrade to WebSocket and stream binary audio frames
description: |
Send authenticated binary audio frames, then the exact text frame
`{"type":"end"}`. The server forwards binary provider-result frames and
closes the one-shot session after settlement.
parameters:
- name: sessionId
in: path
required: true
schema: { type: string, format: uuid }
responses:
"101": { description: WebSocket upgrade }
default: { $ref: "#/components/responses/GatewayError" }
/v1/content/skills:
get:
security: []
summary: Return the enabled official Skill catalog
parameters:
- name: If-None-Match
in: header
schema: { type: string }
responses:
"200":
description: Versioned official Skill catalog
headers:
ETag: { schema: { type: string } }
Cache-Control: { schema: { type: string, const: "public,max-age=300" } }
content:
application/json:
schema: { $ref: "#/components/schemas/OfficialSkillCatalog" }
"304": { description: The caller already has the current revision }
/v1/content/hints/manifest:
get:
security: []
summary: Return the published AI Hint pack manifest
parameters:
- name: If-None-Match
in: header
schema: { type: string }
responses:
"200":
description: Published locale manifest
headers:
ETag: { schema: { type: string } }
Cache-Control: { schema: { type: string, const: "public,max-age=300" } }
content:
application/json:
schema: { $ref: "#/components/schemas/AIHintManifest" }
"304": { description: The caller already has the current manifest }
/v1/content/hints/{locale}:
get:
security: []
summary: Return a published AI Hint pack
parameters:
- $ref: "#/components/parameters/HintLocale"
- name: If-None-Match
in: header
schema: { type: string }
responses:
"200":
description: Published AI Hint pack
headers:
ETag: { schema: { type: string } }
Cache-Control: { schema: { type: string, const: "public,max-age=300" } }
content:
application/json:
schema: { $ref: "#/components/schemas/AIHintPack" }
"304": { description: The caller already has this pack version }
"404": { description: Locale is unsupported or has not been published }
/hints/manifest.json:
get:
security: []
summary: Return the AI Hint manifest at the legacy file path
parameters:
- name: If-None-Match
in: header
schema: { type: string }
responses:
"200":
description: Same payload and cache validators as /v1/content/hints/manifest
headers:
ETag: { schema: { type: string } }
Cache-Control: { schema: { type: string, const: "public,max-age=300" } }
content:
application/json:
schema: { $ref: "#/components/schemas/AIHintManifest" }
"304": { description: The caller already has the current manifest }
/hints/hints-{locale}.json:
get:
security: []
summary: Return an AI Hint pack at the legacy file path
parameters:
- $ref: "#/components/parameters/HintLocale"
- name: If-None-Match
in: header
schema: { type: string }
responses:
"200":
description: Same payload and cache validators as /v1/content/hints/{locale}
headers:
ETag: { schema: { type: string } }
Cache-Control: { schema: { type: string, const: "public,max-age=300" } }
content:
application/json:
schema: { $ref: "#/components/schemas/AIHintPack" }
"304": { description: The caller already has this pack version }
"404": { description: Locale is unsupported or has not been published }
/.well-known/apple-app-site-association:
get:
servers:
- url: https://osglab.com
security: []
summary: Return the Apple Universal Links association document
responses:
"200":
description: AASA JSON without a redirect
headers:
Cache-Control:
schema: { type: string, const: "no-store, max-age=0" }
content:
application/json:
schema: { $ref: "#/components/schemas/AppleAppSiteAssociation" }
/apple-app-site-association:
get:
servers:
- url: https://osglab.com
security: []
summary: Return the root compatibility AASA document
responses:
"200":
description: AASA JSON without a redirect
headers:
Cache-Control:
schema: { type: string, const: "no-store, max-age=0" }
content:
application/json:
schema: { $ref: "#/components/schemas/AppleAppSiteAssociation" }
/i/{code}:
get:
servers:
- url: https://osglab.com
security: []
summary: Privacy-preserving invitation landing page
parameters:
- name: code
in: path
required: true
schema: { type: string, pattern: "^[A-Za-z0-9_-]{22}$" }
responses:
"200": { description: Bilingual HTML landing page }
"404": { description: Invalid, unknown, or expired invitation }
"503": { description: Invitation lookup is temporarily unavailable }
/v1/admin/content/skills:
get:
security:
- adminMtls: []
adminSession: []
summary: List all official Skills including disabled entries
responses:
"200":
description: Administrative Skill catalog
content:
application/json:
schema: { $ref: "#/components/schemas/AdminOfficialSkillCatalog" }
"403": { description: SUPER_ADMIN or SUPPORT role is required }
post:
security:
- adminMtls: []
adminSession: []
summary: Create a disabled official Skill
parameters:
- $ref: "#/components/parameters/AdminCsrf"
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/CreateOfficialSkillRequest" }
responses:
"201":
description: Skill created
content:
application/json:
schema: { $ref: "#/components/schemas/AdminOfficialSkill" }
"400": { description: Request is invalid }
"403": { description: SUPER_ADMIN role and valid CSRF are required }
"409": { description: Skill ID already exists }
/v1/admin/content/skills/{id}:
put:
security:
- adminMtls: []
adminSession: []
summary: Update an official Skill and increment catalog revision
parameters:
- $ref: "#/components/parameters/OfficialSkillId"
- $ref: "#/components/parameters/AdminCsrf"
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/UpdateOfficialSkillRequest" }
responses:
"200":
description: Skill updated
content:
application/json:
schema: { $ref: "#/components/schemas/AdminOfficialSkill" }
"400": { description: Request is invalid }
"403": { description: SUPER_ADMIN role and valid CSRF are required }
"404": { description: Skill was not found }
/v1/admin/content/skills/{id}/enable:
post:
security:
- adminMtls: []
adminSession: []
summary: Enable an official Skill and increment catalog revision
parameters:
- $ref: "#/components/parameters/OfficialSkillId"
- $ref: "#/components/parameters/AdminCsrf"
responses:
"204": { description: Skill enabled }
"403": { description: SUPER_ADMIN role and valid CSRF are required }
"404": { description: Skill was not found }
/v1/admin/content/skills/{id}/disable:
post:
security:
- adminMtls: []
adminSession: []
summary: Disable an official Skill and increment catalog revision
parameters:
- $ref: "#/components/parameters/OfficialSkillId"
- $ref: "#/components/parameters/AdminCsrf"
responses:
"204": { description: Skill disabled }
"403": { description: SUPER_ADMIN role and valid CSRF are required }
"404": { description: Skill was not found }
/v1/admin/content/hints/generation/settings:
get:
security:
- adminMtls: []
adminSession: []
summary: Return non-secret AI Hint generation settings
responses:
"200":
description: Current generation settings and secret availability flags
content:
application/json:
schema: { $ref: "#/components/schemas/HintFeedSettings" }
"403": { description: SUPER_ADMIN or SUPPORT role is required }
put:
security:
- adminMtls: []
adminSession: []
summary: Update non-secret AI Hint generation settings
parameters:
- $ref: "#/components/parameters/AdminCsrf"
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/UpdateHintFeedSettingsRequest" }
responses:
"200":
description: Updated generation settings
content:
application/json:
schema: { $ref: "#/components/schemas/HintFeedSettings" }
"400": { description: Settings are invalid }
"403": { description: SUPER_ADMIN role and valid CSRF are required }
/v1/admin/content/hints/generation/status:
get:
security:
- adminMtls: []
adminSession: []
summary: Return AI Hint generation and scheduler status
responses:
"200":
description: Durable generation status and current pack versions
content:
application/json:
schema: { $ref: "#/components/schemas/HintFeedGenerationStatus" }
"403": { description: SUPER_ADMIN or SUPPORT role is required }
/v1/admin/content/hints/generation/regenerate:
post:
security:
- adminMtls: []
adminSession: []
summary: Generate and atomically publish both AI Hint locale packs
parameters:
- $ref: "#/components/parameters/AdminCsrf"
responses:
"200":
description: Both locale packs were generated and published
content:
application/json:
schema: { $ref: "#/components/schemas/HintFeedGenerationResponse" }
"403": { description: SUPER_ADMIN role and valid CSRF are required }
"409": { description: A generation is already in progress }
"502": { description: Generation failed and the previous packs remain published }
/v1/admin/content/hints/{locale}:
get:
security:
- adminMtls: []
adminSession: []
summary: Return a locale Hint pack for editing
parameters:
- $ref: "#/components/parameters/HintLocale"
responses:
"200":
description: Existing pack or an empty version-zero editor document
content:
application/json:
schema: { $ref: "#/components/schemas/AdminAIHintPack" }
"403": { description: SUPER_ADMIN or SUPPORT role is required }
put:
security:
- adminMtls: []
adminSession: []
summary: Save and immediately apply edits to a locale Hint pack
parameters:
- $ref: "#/components/parameters/HintLocale"
- $ref: "#/components/parameters/AdminCsrf"
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/UpdateAIHintPackRequest" }
responses:
"200":
description: Saved active pack
content:
application/json:
schema: { $ref: "#/components/schemas/AdminAIHintPack" }
"400": { description: Pack is invalid }
"403": { description: SUPER_ADMIN role and valid CSRF are required }
/v1/admin/auth/session:
get:
security:
- adminMtls: []
summary: Check the current administrator session
responses:
"200":
description: Authenticated or anonymous session state
content:
application/json:
schema: { $ref: "#/components/schemas/AdminSessionState" }
"404": { description: Verified administrator client certificate is absent while mTLS is required }
/v1/admin/auth/login:
post:
security:
- adminMtls: []
summary: Authenticate an administrator with password and TOTP
requestBody:
required: true
content:
application/json:
schema:
type: object
additionalProperties: false
required: [username, password, totpCode]
properties:
username: { type: string, minLength: 3, maxLength: 64 }
password: { type: string, minLength: 1, maxLength: 1024 }
totpCode: { type: string, pattern: "^[0-9]{6}$" }
responses:
"200":
description: Secure session and CSRF cookies created
content:
application/json:
schema: { $ref: "#/components/schemas/AdminLoginResponse" }
"401": { description: Credentials are invalid }
"429": { description: Login is locked or rate limited }
/v1/admin/auth/logout:
post:
security:
- adminMtls: []
adminSession: []
summary: Revoke the current administrator session
parameters:
- $ref: "#/components/parameters/AdminCsrf"
responses:
"204": { description: Session revoked }
"401": { description: Session is invalid }
/v1/admin/providers:
get:
security:
- adminMtls: []
adminSession: []
summary: List effective provider credential status without returning secrets
responses:
"200":
description: Provider credential status
content:
application/json:
schema:
type: array
minItems: 2
maxItems: 2
items: { $ref: "#/components/schemas/ProviderCredentialStatus" }
"403": { description: SUPER_ADMIN role is required }
/v1/admin/providers/{providerId}/api-key:
put:
security:
- adminMtls: []
adminSession: []
summary: Replace the runtime provider API key for new upstream requests
parameters:
- name: providerId
in: path
required: true
schema: { type: string, enum: [deepseek, volcengine] }
- $ref: "#/components/parameters/AdminCsrf"
- name: X-Request-ID
in: header
required: false
schema: { type: string, pattern: "^[A-Za-z0-9_-]{8,64}$" }
requestBody:
required: true
content:
application/json:
schema:
type: object
additionalProperties: false
required: [apiKey]
properties:
apiKey:
type: string
minLength: 1
maxLength: 4096
pattern: "^[^\\r\\n]+$"
writeOnly: true
responses:
"200":
description: Runtime override saved, encrypted, and audited
content:
application/json:
schema: { $ref: "#/components/schemas/ProviderCredentialStatus" }
"400": { description: API key is blank, multiline, oversized, or malformed }
"403": { description: SUPER_ADMIN role and valid CSRF are required }
"404": { description: Provider is not supported }
/v1/admin/overview:
get:
security:
- adminMtls: []
adminSession: []
summary: Return registration, activity, and credit overview statistics
parameters:
- $ref: "#/components/parameters/AdminRange"
responses:
"200":
description: Overview statistics
content:
application/json:
schema: { $ref: "#/components/schemas/AdminOverview" }
"400": { description: Range is invalid }
"401": { description: Session is invalid }
/v1/admin/referrals:
get:
security:
- adminMtls: []
adminSession: []
summary: Return referral funnel and ranking statistics
parameters:
- $ref: "#/components/parameters/AdminRange"
- name: sort
in: query
schema: { type: string, enum: [invited, qualified, creditsEarned] }
- $ref: "#/components/parameters/AdminSortOrder"
- name: limit
in: query
schema: { type: integer, minimum: 1, maximum: 100, default: 20 }
responses:
"200":
description: Referral statistics
content:
application/json:
schema: { $ref: "#/components/schemas/AdminReferralOverview" }
"400": { description: Range, sort, order, or limit is invalid }
"401": { description: Session is invalid }
/v1/admin/analytics:
get:
security:
- adminMtls: []
adminSession: []
summary: Return product growth, retention, usage and monetization analytics
parameters:
- $ref: "#/components/parameters/AdminRange"
responses:
"200":
description: Privacy-minimized product analytics aggregates
content:
application/json:
schema: { $ref: "#/components/schemas/AdminProductAnalytics" }
"400": { description: Range is invalid }
"401": { description: Session is invalid }
/v1/admin/users:
get:
security:
- adminMtls: []
adminSession: []
summary: List users or search by full or 8-character internal user ID suffix
parameters:
- name: q
in: query
schema: { type: string, maxLength: 36 }
- name: cursor
in: query
schema: { type: string, maxLength: 256 }
- $ref: "#/components/parameters/Limit"
- $ref: "#/components/parameters/AdminFrom"
- $ref: "#/components/parameters/AdminUntil"
- name: status
in: query
schema: { type: string, enum: [active, suspended] }
- $ref: "#/components/parameters/AdminCreatedAtSort"
- $ref: "#/components/parameters/AdminSortOrder"
responses:
"200":
description: Privacy-minimized user summaries
content:
application/json:
schema: { $ref: "#/components/schemas/AdminUserPage" }
"400": { description: Query, filter, sort, limit, or cursor is malformed }
"401": { description: Session is invalid }
"403": { description: ANALYST role cannot access user records }
/v1/admin/users/{userId}:
get:
security:
- adminMtls: []
adminSession: []
summary: Return privacy-minimized user details
parameters:
- $ref: "#/components/parameters/AdminUserId"
responses:
"200":
description: User details
content:
application/json:
schema: { $ref: "#/components/schemas/AdminUserDetail" }
"403": { description: ANALYST role cannot access user records }
"404": { description: User was not found }
/v1/admin/users/{userId}/ledger:
get:
security:
- adminMtls: []
adminSession: []
summary: Return the immutable credit ledger for a user
parameters:
- $ref: "#/components/parameters/AdminUserId"
- name: cursor
in: query
schema: { type: string, maxLength: 256 }
- $ref: "#/components/parameters/AdminLedgerLimit"
- $ref: "#/components/parameters/AdminFrom"
- $ref: "#/components/parameters/AdminUntil"
- $ref: "#/components/parameters/AdminLedgerType"
- $ref: "#/components/parameters/AdminLedgerEntryType"
- $ref: "#/components/parameters/AdminLedgerUsageType"
- $ref: "#/components/parameters/AdminLedgerReferenceId"
- $ref: "#/components/parameters/AdminLedgerSort"
- $ref: "#/components/parameters/AdminSortOrder"
responses:
"200":
description: Credit ledger entries ordered by the requested field and unique entry ID
content:
application/json:
schema: { $ref: "#/components/schemas/AdminLedgerPage" }
"400": { description: Filter, sort, limit, or cursor is malformed }
"403": { description: ANALYST role cannot access credit ledger records }
"404": { description: User was not found }
/v1/admin/credits/ledger:
get:
security:
- adminMtls: []
adminSession: []
summary: Return the latest immutable credit ledger entries across users
parameters:
- name: cursor
in: query
schema: { type: string, maxLength: 256 }
- $ref: "#/components/parameters/AdminLedgerLimit"
- $ref: "#/components/parameters/AdminFrom"
- $ref: "#/components/parameters/AdminUntil"
- $ref: "#/components/parameters/AdminLedgerType"
- $ref: "#/components/parameters/AdminLedgerEntryType"
- $ref: "#/components/parameters/AdminLedgerUsageType"
- $ref: "#/components/parameters/AdminLedgerReferenceId"
- $ref: "#/components/parameters/AdminLedgerSort"
- $ref: "#/components/parameters/AdminSortOrder"
responses:
"200":
description: Latest credit ledger entries ordered by the requested field and unique entry ID
content:
application/json:
schema: { $ref: "#/components/schemas/AdminLedgerPage" }
"400": { description: Filter, sort, limit, or cursor is malformed }
"403": { description: ANALYST role cannot access credit ledger records }
/v1/admin/credits/grants:
post:
security:
- adminMtls: []
adminSession: []
summary: Grant integer credits through an idempotent ledger transaction
parameters:
- $ref: "#/components/parameters/AdminCsrf"
- $ref: "#/components/parameters/IdempotencyKey"
requestBody:
required: true
content:
application/json:
schema:
type: object
additionalProperties: false
required: [userId, amount, reason]
properties:
userId: { type: string, format: uuid }
amount: { type: integer, format: int64, minimum: 1 }
reason: { type: string, minLength: 4, maxLength: 200 }
responses:
"200":
description: Grant applied or replayed
content:
application/json:
schema: { $ref: "#/components/schemas/AdminGrantResponse" }
"400": { description: Grant input or idempotency key is invalid }
"403": { description: CSRF or role authorization failed }
"404": { description: Target user was not found }
"409": { description: Idempotency key conflicts with another grant }
/v1/admin/operators/summary:
get:
security:
- adminMtls: []
adminSession: []
summary: Return administrator and active-session security indicators
responses:
"200":
description: Security indicators
content:
application/json:
schema:
type: object
additionalProperties: false
required: [enabledOperators, lockedOperators, activeSessions]
properties:
enabledOperators: { type: integer, minimum: 0 }
lockedOperators: { type: integer, minimum: 0 }
activeSessions: { type: integer, format: int64, minimum: 0 }
"403": { description: INSUFFICIENT_PERMISSION; SUPER_ADMIN is required }
/v1/admin/operators:
get:
security:
- adminMtls: []
adminSession: []
summary: List administrator operators
parameters:
- name: cursor
in: query
schema: { type: string, maxLength: 256 }
- $ref: "#/components/parameters/Limit"
- $ref: "#/components/parameters/AdminFrom"
- $ref: "#/components/parameters/AdminUntil"
- name: role
in: query
schema: { type: string, enum: [SUPER_ADMIN, SUPPORT, ANALYST] }
- name: enabled
in: query
schema: { type: boolean }
- name: locked
in: query
description: Whether locked_until is later than the request time.
schema: { type: boolean }
- name: sort
in: query
schema:
type: string
enum: [createdAt, username, lastLoginAt]
default: createdAt
- $ref: "#/components/parameters/AdminSortOrder"
responses:
"200":
description: Operators ordered by the requested stable sort and operator ID; null lastLoginAt values are last
content:
application/json:
schema: { $ref: "#/components/schemas/AdminOperatorPage" }
"403": { description: INSUFFICIENT_PERMISSION; SUPER_ADMIN is required }
"400": { description: Filter, sort, limit, or cursor is malformed }
post:
security:
- adminMtls: []
adminSession: []
summary: Create an operator and return TOTP provisioning data once
parameters:
- $ref: "#/components/parameters/AdminCsrf"
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/AdminOperatorCreateRequest" }
responses:
"201":
description: Operator created; plaintext TOTP material is returned only here
content:
application/json:
schema: { $ref: "#/components/schemas/AdminOperatorProvisioning" }
"400": { description: VALIDATION_ERROR }
"403": { description: CSRF_INVALID, ORIGIN_INVALID, or INSUFFICIENT_PERMISSION }
"409": { description: ADMIN_USERNAME_CONFLICT }
/v1/admin/operators/{operatorId}/enable:
post:
security:
- adminMtls: []
adminSession: []
summary: Enable an operator
parameters:
- $ref: "#/components/parameters/AdminOperatorId"
- $ref: "#/components/parameters/AdminCsrf"
responses:
"204": { description: Operator enabled and action audited }
"403": { description: CSRF_INVALID, ORIGIN_INVALID, or INSUFFICIENT_PERMISSION }
"404": { description: ADMIN_OPERATOR_NOT_FOUND }
/v1/admin/operators/{operatorId}/disable:
post:
security:
- adminMtls: []
adminSession: []
summary: Disable an operator and atomically revoke all active sessions
parameters:
- $ref: "#/components/parameters/AdminOperatorId"
- $ref: "#/components/parameters/AdminCsrf"
responses:
"204": { description: Operator disabled and sessions revoked }
"403": { description: CSRF_INVALID, ORIGIN_INVALID, or INSUFFICIENT_PERMISSION }
"404": { description: ADMIN_OPERATOR_NOT_FOUND }
"409": { description: CANNOT_DISABLE_SELF or LAST_SUPER_ADMIN_REQUIRED }
/v1/admin/operators/{operatorId}/unlock:
post:
security:
- adminMtls: []
adminSession: []
summary: Clear an operator login lock
parameters:
- $ref: "#/components/parameters/AdminOperatorId"
- $ref: "#/components/parameters/AdminCsrf"
responses:
"204": { description: Operator unlocked }
"403": { description: CSRF_INVALID, ORIGIN_INVALID, or INSUFFICIENT_PERMISSION }
"404": { description: ADMIN_OPERATOR_NOT_FOUND }
/v1/admin/operators/{operatorId}/credentials/reset:
post:
security:
- adminMtls: []
adminSession: []
summary: Reset password and TOTP, atomically revoking all active sessions
parameters:
- $ref: "#/components/parameters/AdminOperatorId"
- $ref: "#/components/parameters/AdminCsrf"
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/AdminOperatorPasswordRequest" }
responses:
"200":
description: Credentials reset; plaintext TOTP material is returned only here
content:
application/json:
schema: { $ref: "#/components/schemas/AdminOperatorProvisioning" }
"400": { description: VALIDATION_ERROR }
"403": { description: CSRF_INVALID, ORIGIN_INVALID, or INSUFFICIENT_PERMISSION }
"404": { description: ADMIN_OPERATOR_NOT_FOUND }
/v1/admin/operators/{operatorId}/sessions/revoke:
post:
security:
- adminMtls: []
adminSession: []
summary: Revoke every active session for an operator
parameters:
- $ref: "#/components/parameters/AdminOperatorId"
- $ref: "#/components/parameters/AdminCsrf"
responses:
"204": { description: All active sessions revoked }
"403": { description: CSRF_INVALID, ORIGIN_INVALID, or INSUFFICIENT_PERMISSION }
"404": { description: ADMIN_OPERATOR_NOT_FOUND }
/v1/admin/audit:
get:
security:
- adminMtls: []
adminSession: []
summary: Return append-only administrator audit events
parameters:
- name: cursor
in: query
schema: { type: string, maxLength: 256 }
- $ref: "#/components/parameters/Limit"
- $ref: "#/components/parameters/AdminFrom"
- $ref: "#/components/parameters/AdminUntil"
- name: action
in: query
schema:
type: string
enum:
- LOGIN_SUCCEEDED
- LOGIN_FAILED
- SESSION_REVOKED
- OPERATOR_CREATED
- OPERATOR_ENABLED
- OPERATOR_DISABLED
- OPERATOR_UNLOCKED
- OPERATOR_CREDENTIALS_RESET
- OPERATOR_SESSIONS_REVOKED
- MANUAL_CREDIT_GRANTED
- CONTENT_SKILL_CREATED
- CONTENT_SKILL_UPDATED
- CONTENT_SKILL_ENABLED
- CONTENT_SKILL_DISABLED
- CONTENT_HINT_PACK_PUBLISHED
- CONTENT_HINT_PACK_SAVED
- CONTENT_HINT_FEED_SETTINGS_UPDATED
- CONTENT_HINT_FEED_GENERATED
- PROVIDER_API_KEY_UPDATED
- name: result
in: query
schema: { type: string, enum: [success, rejected] }
- $ref: "#/components/parameters/AdminCreatedAtSort"
- $ref: "#/components/parameters/AdminSortOrder"
responses:
"200":
description: Recent audit events
content:
application/json:
schema: { $ref: "#/components/schemas/AdminAuditPage" }
"400": { description: Filter, sort, limit, or cursor is malformed }
"403": { description: Super-administrator role is required }
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
adminMtls:
type: mutualTLS
description: Client certificate issued by the dedicated administrator CA; required when ADMIN_MTLS_REQUIRED is true.
adminSession:
type: apiKey
in: cookie
name: osg_admin_session
parameters:
Limit:
name: limit
in: query
schema: { type: integer, minimum: 1, maximum: 100, default: 50 }
RequestId:
name: X-Request-ID
in: header
required: true
schema: { type: string, pattern: "^[A-Za-z0-9_-]{8,64}$" }
IdempotencyKey:
name: Idempotency-Key
in: header
required: true
schema: { type: string, minLength: 8, maxLength: 128 }
OfficialSkillId:
name: id
in: path
required: true
schema:
type: string
maxLength: 100
pattern: "^official\\.[a-z0-9._-]+$"
HintLocale:
name: locale
in: path
required: true
schema: { type: string, enum: [zh, en] }
AdminCsrf:
name: X-CSRF-Token
in: header
required: true
schema: { type: string, minLength: 32, maxLength: 512 }
AdminRange:
name: range
in: query
description: Covers exactly 7, 30, or 90 UTC calendar dates, from 00:00 on the first date through the current instant. The current UTC date is partial.
schema: { type: string, enum: [7d, 30d, 90d], default: 30d }
AdminFrom:
name: from
in: query
description: Inclusive UTC lower bound. When both bounds are present, from must be earlier than until.
schema: { type: string, format: date-time }
AdminUntil:
name: until
in: query
description: Exclusive UTC upper bound.
schema: { type: string, format: date-time }
AdminCreatedAtSort:
name: sort
in: query
schema: { type: string, enum: [createdAt], default: createdAt }
AdminSortOrder:
name: order
in: query
schema: { type: string, enum: [asc, desc] }
AdminLedgerType:
name: type
in: query
schema:
type: string
enum: [grant, reserve, settle, refund]
AdminLedgerEntryType:
name: entryType
in: query
description: Exact immutable ledger entry type. This filter is ANDed with all other filters.
schema:
$ref: "#/components/schemas/LedgerEntryType"
AdminLedgerUsageType:
name: usageType
in: query
description: Product usage associated with the reservation. HOTWORD request source takes precedence over capability.
schema: { type: string, enum: [polish, asr, ai, agent, hotword] }
AdminLedgerReferenceId:
name: referenceId
in: query
schema: { type: string, format: uuid }
AdminLedgerSort:
name: sort
in: query
schema: { type: string, enum: [createdAt, amount], default: createdAt }
AdminLedgerLimit:
name: limit
in: query
schema: { type: integer, minimum: 1, maximum: 100, default: 100 }
AdminUserId:
name: userId
in: path
required: true
schema: { type: string, format: uuid }
AdminOperatorId:
name: operatorId
in: path
required: true
schema: { type: string, format: uuid }
responses:
HealthUp:
description: Service is healthy
content:
application/json:
schema:
type: object
required: [status]
properties:
status: { type: string, const: UP }
Error:
description: API error
content:
application/json:
schema: { $ref: "#/components/schemas/ApiErrorEnvelope" }
GatewayError:
description: Managed-provider error
content:
application/json:
schema: { $ref: "#/components/schemas/GatewayError" }
schemas:
ProductAnalyticsEvent:
type: object
additionalProperties: false
required: [clientEventId, eventType, occurredAt, surface]
properties:
clientEventId: { type: string, format: uuid }
eventType:
type: string
enum:
- FIRST_OPEN
- SESSION_STARTED
- KEYBOARD_ACTIVATED
- AI_FEATURE_STARTED
- AI_FEATURE_SUCCEEDED
- AI_FEATURE_FAILED
- PURCHASE_VIEWED
- PURCHASE_STARTED
- PURCHASE_CANCELLED
- REFERRAL_SHARED
- INVITE_OPENED
occurredAt: { type: string, format: date-time }
surface: { type: string, enum: [APP, KEYBOARD, INVITE_WEB] }
acquisitionChannel:
type: string
enum: [APP_STORE_ORGANIC, REFERRAL, SOCIAL_CONTENT, UNKNOWN]
feature:
type: string
enum: [TRANSCRIPTION, POLISH, AI_ASSISTANT, AGENT, HOTWORD, OTHER]
executionMode: { type: string, enum: [MANAGED, LOCAL, BYOK] }
failureCategory:
type: string
enum: [NETWORK, PROVIDER, TIMEOUT, CANCELLED, INSUFFICIENT_CREDITS, VALIDATION, UNKNOWN]
durationBucket:
type: string
enum: [LT_1S, S1_TO_3, S3_TO_10, S10_TO_30, GTE_30S]
appVersion:
type: string
minLength: 1
maxLength: 32
pattern: "^[A-Za-z0-9._+-]+$"
osVersion:
type: string
minLength: 1
maxLength: 32
pattern: "^[A-Za-z0-9._+-]+$"
installationId:
type: string
format: uuid
deprecated: true
description: Transitional legacy field; new clients must use the batch-level installationId.
ProductAnalyticsBatchRequest:
type: object
additionalProperties: false
required: [events]
properties:
installationId:
type: string
format: uuid
description: Required for new clients; legacy batches may instead repeat one identical ID on every event.
events:
type: array
minItems: 1
maxItems: 50
items: { $ref: "#/components/schemas/ProductAnalyticsEvent" }
KeyboardUsageSummary:
type: object
additionalProperties: false
required:
- clientSummaryId
- summaryDate
- chineseCharacterCount
- englishCharacterCount
- otherCharacterCount
- inputSessionCount
- chineseOnlySessionCount
- englishOnlySessionCount
- mixedLanguageSessionCount
- otherOnlySessionCount
properties:
clientSummaryId: { type: string, format: uuid }
summaryDate:
type: string
format: date
description: Finalized UTC date; accepted from 35 days ago through yesterday.
chineseCharacterCount: { type: integer, format: int64, minimum: 0, maximum: 1000000 }
englishCharacterCount: { type: integer, format: int64, minimum: 0, maximum: 1000000 }
otherCharacterCount: { type: integer, format: int64, minimum: 0, maximum: 1000000 }
inputSessionCount: { type: integer, format: int64, minimum: 1, maximum: 100000 }
chineseOnlySessionCount: { type: integer, format: int64, minimum: 0, maximum: 100000 }
englishOnlySessionCount: { type: integer, format: int64, minimum: 0, maximum: 100000 }
mixedLanguageSessionCount: { type: integer, format: int64, minimum: 0, maximum: 100000 }
otherOnlySessionCount: { type: integer, format: int64, minimum: 0, maximum: 100000 }
appVersion:
type: string
minLength: 1
maxLength: 32
pattern: "^[A-Za-z0-9._+-]+$"
osVersion:
type: string
minLength: 1
maxLength: 32
pattern: "^[A-Za-z0-9._+-]+$"
KeyboardUsageBatchRequest:
type: object
additionalProperties: false
required: [installationId, summaries]
properties:
installationId: { type: string, format: uuid }
summaries:
type: array
minItems: 1
maxItems: 50
items: { $ref: "#/components/schemas/KeyboardUsageSummary" }
ProductAnalyticsBatchResponse:
type: object
additionalProperties: false
required: [accepted, replayed]
properties:
accepted: { type: integer, minimum: 0, maximum: 50 }
replayed: { type: integer, minimum: 0, maximum: 50 }
SkillLocalization:
type: object
additionalProperties: false
required: [name, summary, prompt]
properties:
name: { type: string, minLength: 1, maxLength: 40 }
summary: { type: string, minLength: 1, maxLength: 200 }
prompt: { type: string, minLength: 1, maxLength: 6000 }
SkillLocalizations:
type: object
additionalProperties: false
required: [zh-Hans, en]
properties:
zh-Hans: { $ref: "#/components/schemas/SkillLocalization" }
en: { $ref: "#/components/schemas/SkillLocalization" }
OfficialSkill:
type: object
additionalProperties: false
required: [id, systemImage, sortOrder, kind, thinkingEnabled, localizations]
properties:
id:
type: string
maxLength: 100
pattern: "^official\\.[a-z0-9._-]+$"
systemImage: { type: string, minLength: 1, maxLength: 100 }
sortOrder: { type: integer, minimum: 0, maximum: 100000 }
kind: { type: string, const: transform }
thinkingEnabled: { type: boolean }
localizations: { $ref: "#/components/schemas/SkillLocalizations" }
AdminOfficialSkill:
type: object
additionalProperties: false
required: [id, systemImage, sortOrder, kind, thinkingEnabled, enabled, localizations]
properties:
id:
type: string
maxLength: 100
pattern: "^official\\.[a-z0-9._-]+$"
systemImage: { type: string, minLength: 1, maxLength: 100 }
sortOrder: { type: integer, minimum: 0, maximum: 100000 }
kind: { type: string, const: transform }
thinkingEnabled: { type: boolean }
enabled: { type: boolean }
localizations: { $ref: "#/components/schemas/SkillLocalizations" }
OfficialSkillCatalog:
type: object
additionalProperties: false
required: [schemaVersion, revision, skills]
properties:
schemaVersion: { type: integer, const: 1 }
revision: { type: integer, format: int64, minimum: 0 }
generatedAt: { type: string, format: date-time }
skills:
type: array
maxItems: 100
items: { $ref: "#/components/schemas/OfficialSkill" }
AdminOfficialSkillCatalog:
type: object
additionalProperties: false
required: [revision, skills]
properties:
revision: { type: integer, format: int64, minimum: 0 }
generatedAt: { type: string, format: date-time }
skills:
type: array
items: { $ref: "#/components/schemas/AdminOfficialSkill" }
CreateOfficialSkillRequest:
type: object
additionalProperties: false
required: [id, systemImage, sortOrder, thinkingEnabled, localizations]
properties:
id:
type: string
maxLength: 100
pattern: "^official\\.[a-z0-9._-]+$"
systemImage: { type: string, minLength: 1, maxLength: 100 }
sortOrder: { type: integer, minimum: 0, maximum: 100000 }
thinkingEnabled: { type: boolean }
localizations: { $ref: "#/components/schemas/SkillLocalizations" }
UpdateOfficialSkillRequest:
type: object
additionalProperties: false
required: [systemImage, sortOrder, thinkingEnabled, localizations]
properties:
systemImage: { type: string, minLength: 1, maxLength: 100 }
sortOrder: { type: integer, minimum: 0, maximum: 100000 }
thinkingEnabled: { type: boolean }
localizations: { $ref: "#/components/schemas/SkillLocalizations" }
AIHintCard:
type: object
additionalProperties: false
required: [id, prompt, category, priority, source, locale, conditions]
anyOf:
- required: [displayText]
- required: [text]
properties:
id: { type: string, minLength: 1, maxLength: 128 }
displayText: { type: string, minLength: 1, maxLength: 500 }
text: { type: string, minLength: 1, maxLength: 500 }
prompt: { type: string, minLength: 1, maxLength: 16000 }
category: { type: string, minLength: 1, maxLength: 64 }
priority: { type: integer, minimum: -10000, maximum: 10000 }
source: { type: string, minLength: 1, maxLength: 64 }
locale: { type: string, enum: [zh, en] }
conditions:
type: array
maxItems: 20
items: { type: string, minLength: 1, maxLength: 64 }
metadata:
type: object
additionalProperties: true
AIHintPack:
type: object
additionalProperties: false
required: [locale, version, cards]
properties:
locale: { type: string, enum: [zh, en] }
generatedAt: { type: string, format: date-time }
expiresAt: { type: string, format: date-time }
version: { type: integer, minimum: 1 }
cards:
type: array
maxItems: 500
items: { $ref: "#/components/schemas/AIHintCard" }
AdminAIHintPack:
type: object
additionalProperties: false
required: [locale, version, cards]
properties:
locale: { type: string, enum: [zh, en] }
generatedAt: { type: string, format: date-time }
expiresAt: { type: string, format: date-time }
intervalHours: { type: integer, minimum: 1, maximum: 168 }
version: { type: integer, minimum: 0 }
cards:
type: array
maxItems: 500
items: { $ref: "#/components/schemas/AIHintCard" }
UpdateAIHintPackRequest:
type: object
additionalProperties: false
required: [cards]
properties:
generatedAt: { type: string, format: date-time }
expiresAt: { type: string, format: date-time }
intervalHours: { type: integer, minimum: 1, maximum: 168 }
cards:
type: array
maxItems: 500
items: { $ref: "#/components/schemas/AIHintCard" }
AIHintManifest:
type: object
additionalProperties: false
required: [locales, files]
properties:
generatedAt: { type: string, format: date-time }
expiresAt: { type: string, format: date-time }
intervalHours: { type: integer, minimum: 1, maximum: 168 }
locales:
type: array
uniqueItems: true
items: { type: string, enum: [zh, en] }
files:
type: object
additionalProperties:
type: [string, "null"]
sources:
type: object
additionalProperties:
type: array
items: { type: string }
HintFeedSettings:
type: object
additionalProperties: false
required:
- enabled
- topHubApiKeyConfigured
- generationIntervalHours
- holidayCountriesZh
- holidayCountriesEn
- weatherCitiesZh
- weatherCitiesEn
- googleTrendsGeos
properties:
enabled: { type: boolean }
topHubApiKeyConfigured: { type: boolean }
generationIntervalHours: { type: integer, minimum: 1, maximum: 168 }
holidayCountriesZh: { type: string, minLength: 2, maxLength: 255 }
holidayCountriesEn: { type: string, minLength: 2, maxLength: 255 }
weatherCitiesZh: { type: string, minLength: 1, maxLength: 2000 }
weatherCitiesEn: { type: string, minLength: 1, maxLength: 2000 }
googleTrendsGeos: { type: string, minLength: 2, maxLength: 255 }
UpdateHintFeedSettingsRequest:
type: object
additionalProperties: false
required:
- generationIntervalHours
- holidayCountriesZh
- holidayCountriesEn
- weatherCitiesZh
- weatherCitiesEn
- googleTrendsGeos
properties:
generationIntervalHours: { type: integer, minimum: 1, maximum: 168 }
holidayCountriesZh: { type: string, minLength: 2, maxLength: 255 }
holidayCountriesEn: { type: string, minLength: 2, maxLength: 255 }
weatherCitiesZh: { type: string, minLength: 1, maxLength: 2000 }
weatherCitiesEn: { type: string, minLength: 1, maxLength: 2000 }
googleTrendsGeos: { type: string, minLength: 2, maxLength: 255 }
HintFeedGenerationStatus:
type: object
additionalProperties: false
required:
- enabled
- outcome
- intervalHours
- topHubApiKeyConfigured
properties:
enabled: { type: boolean }
outcome: { type: string, enum: [IDLE, RUNNING, SUCCEEDED, FAILED] }
intervalHours: { type: integer, minimum: 1, maximum: 168 }
lastStartedAt: { type: string, format: date-time }
lastCompletedAt: { type: string, format: date-time }
lastErrorCode: { type: string, maxLength: 64 }
nextScheduledAt: { type: string, format: date-time }
topHubApiKeyConfigured: { type: boolean }
zhVersion: { type: integer, minimum: 1 }
zhCardCount: { type: integer, minimum: 0, maximum: 40 }
enVersion: { type: integer, minimum: 1 }
enCardCount: { type: integer, minimum: 0, maximum: 40 }
HintFeedGenerationResponse:
type: object
additionalProperties: false
required: [generationId, generatedAt, zh, en]
properties:
generationId: { type: string, format: uuid }
generatedAt: { type: string, format: date-time }
zh: { $ref: "#/components/schemas/HintFeedPackGenerationResult" }
en: { $ref: "#/components/schemas/HintFeedPackGenerationResult" }
HintFeedPackGenerationResult:
type: object
additionalProperties: false
required: [version, cardCount]
properties:
version: { type: integer, minimum: 1 }
cardCount: { type: integer, minimum: 0, maximum: 40 }
AdminSessionState:
type: object
additionalProperties: false
required: [authenticated]
properties:
authenticated: { type: boolean }
operatorName: { type: ["string", "null"], minLength: 3, maxLength: 64 }
role:
type: ["string", "null"]
enum: [SUPER_ADMIN, SUPPORT, ANALYST, null]
description: CSRF material is intentionally not reconstructed or returned by session checks.
ProviderCredentialStatus:
type: object
additionalProperties: false
required: [providerId, configured, source]
properties:
providerId: { type: string, enum: [deepseek, volcengine] }
configured: { type: boolean }
source: { type: string, enum: [ENVIRONMENT, RUNTIME_OVERRIDE] }
updatedAt:
type: ["string", "null"]
format: date-time
description: Present only for a runtime override.
description: API key material is never returned.
AdminLoginResponse:
type: object
additionalProperties: false
required: [operatorName, role, csrfToken]
properties:
operatorName: { type: string, minLength: 3, maxLength: 64 }
role: { type: string, enum: [SUPER_ADMIN, SUPPORT, ANALYST] }
csrfToken:
type: string
minLength: 32
maxLength: 512
description: Returned once for the new session; the CSRF cookie is the reload fallback.
AdminUsageAggregate:
type: object
additionalProperties: false
required: [kind, requests, chargedCredits, asrMillis, inputTokens, outputTokens]
properties:
kind: { type: string }
requests: { type: integer, format: int64, minimum: 0 }
chargedCredits: { type: integer, format: int64, minimum: 0 }
asrMillis: { type: integer, format: int64, minimum: 0 }
inputTokens: { type: integer, format: int64, minimum: 0 }
outputTokens: { type: integer, format: int64, minimum: 0 }
AdminTrendPoint:
type: object
additionalProperties: false
required: [date, registrations, creditsUsed]
properties:
date: { type: string, format: date }
registrations: { type: integer, format: int64, minimum: 0 }
creditsUsed: { type: integer, format: int64, minimum: 0 }
AdminStatsPeriod:
type: object
additionalProperties: false
required: [from, until]
properties:
from: { type: string, format: date-time, description: Inclusive UTC lower bound. }
until: { type: string, format: date-time, description: Exclusive current-instant upper bound. }
AdminOverview:
type: object
additionalProperties: false
required:
- period
- totalUsers
- activeUsers
- newUsers
- totalCreditBalance
- creditsGranted
- creditsUsed
- trend
- usage
properties:
period: { $ref: "#/components/schemas/AdminStatsPeriod" }
totalUsers: { type: integer, format: int64, minimum: 0 }
activeUsers:
type: integer
format: int64
minimum: 0
description: Registered accounts with successful AI use or manually committed keyboard input in the period.
newUsers: { type: integer, format: int64, minimum: 0 }
totalCreditBalance: { type: integer, format: int64, minimum: 0 }
creditsGranted: { type: integer, format: int64, minimum: 0 }
creditsUsed: { type: integer, format: int64, minimum: 0 }
trend:
type: array
items: { $ref: "#/components/schemas/AdminTrendPoint" }
usage:
type: array
items: { $ref: "#/components/schemas/AdminUsageAggregate" }
AdminFunnelStep:
type: object
additionalProperties: false
required: [label, count]
properties:
label:
type: string
enum: [成功绑定, 绑定后首次 AI 成功, 完成奖励]
count: { type: integer, format: int64, minimum: 0 }
AdminReferralRank:
type: object
additionalProperties: false
required: [userId, invited, qualified, creditsEarned]
properties:
userId: { type: string, format: uuid }
invited: { type: integer, format: int64, minimum: 0 }
qualified: { type: integer, format: int64, minimum: 0 }
creditsEarned: { type: integer, format: int64, minimum: 0 }
AdminReferralOverview:
type: object
additionalProperties: false
required: [period, pendingBindings, ineligibleBindings, funnel, ranking]
properties:
period: { $ref: "#/components/schemas/AdminStatsPeriod" }
pendingBindings: { type: integer, format: int64, minimum: 0 }
ineligibleBindings: { type: integer, format: int64, minimum: 0 }
funnel:
type: array
items: { $ref: "#/components/schemas/AdminFunnelStep" }
ranking:
type: array
items: { $ref: "#/components/schemas/AdminReferralRank" }
AdminAnalyticsRate:
type: object
additionalProperties: false
required: [numerator, denominator]
properties:
numerator: { type: integer, format: int64, minimum: 0 }
denominator: { type: integer, format: int64, minimum: 0 }
percent:
type: ["number", "null"]
minimum: 0
maximum: 100
description: Null means unavailable, usually because the denominator is zero; clients must not render it as 0%.
AdminAnalyticsFunnelStep:
type: object
additionalProperties: false
required: [label, count]
properties:
label: { type: string, maxLength: 64 }
count: { type: integer, format: int64, minimum: 0 }
AdminAnalyticsChannel:
type: object
additionalProperties: false
required: [channel, installations, activated, activationRate]
properties:
channel:
type: string
enum: [APP_STORE_ORGANIC, REFERRAL, SOCIAL_CONTENT, UNKNOWN]
installations:
type: integer
format: int64
minimum: 0
description: Installations in this channel with a completed 24-hour observation window.
activated: { type: integer, format: int64, minimum: 0 }
activationRate: { $ref: "#/components/schemas/AdminAnalyticsRate" }
AdminAnalyticsCohort:
type: object
additionalProperties: false
required: [cohortDate, size]
properties:
cohortDate: { type: string, format: date }
size: { type: integer, format: int64, minimum: 0 }
d1:
anyOf:
- { $ref: "#/components/schemas/AdminAnalyticsRate" }
- { type: "null" }
d7:
anyOf:
- { $ref: "#/components/schemas/AdminAnalyticsRate" }
- { type: "null" }
d30:
anyOf:
- { $ref: "#/components/schemas/AdminAnalyticsRate" }
- { type: "null" }
AdminAnalyticsFeatureUsage:
type: object
additionalProperties: false
required: [feature, executionMode, users, successes]
properties:
feature:
type: string
enum: [TRANSCRIPTION, POLISH, AI_ASSISTANT, AGENT, HOTWORD, OTHER]
executionMode: { type: string, enum: [MANAGED, LOCAL, BYOK] }
users: { type: integer, format: int64, minimum: 0 }
successes: { type: integer, format: int64, minimum: 0 }
AdminAnalyticsLatencyBucket:
type: object
additionalProperties: false
required: [bucket, successful, failed]
properties:
bucket:
type: string
enum: [LT_1S, S1_TO_3, S3_TO_10, S10_TO_30, GTE_30S]
successful: { type: integer, format: int64, minimum: 0 }
failed: { type: integer, format: int64, minimum: 0 }
AdminAnalyticsKeyboardUsage:
type: object
additionalProperties: false
required:
- activeUsers
- activationToInput
- chineseActiveUsers
- englishActiveUsers
- bilingualActiveUsers
- totalCharacters
- chineseCharacters
- englishCharacters
- otherCharacters
- inputSessions
- chineseOnlySessions
- englishOnlySessions
- mixedLanguageSessions
- otherOnlySessions
properties:
activeUsers: { type: integer, format: int64, minimum: 0 }
activationToInput: { $ref: "#/components/schemas/AdminAnalyticsRate" }
chineseActiveUsers: { type: integer, format: int64, minimum: 0 }
englishActiveUsers: { type: integer, format: int64, minimum: 0 }
bilingualActiveUsers: { type: integer, format: int64, minimum: 0 }
totalCharacters: { type: integer, format: int64, minimum: 0 }
chineseCharacters: { type: integer, format: int64, minimum: 0 }
englishCharacters: { type: integer, format: int64, minimum: 0 }
otherCharacters: { type: integer, format: int64, minimum: 0 }
chineseSharePercent: { type: ["number", "null"], minimum: 0, maximum: 100 }
englishSharePercent: { type: ["number", "null"], minimum: 0, maximum: 100 }
inputSessions: { type: integer, format: int64, minimum: 0 }
averageCharactersPerInputSession: { type: ["number", "null"], minimum: 0 }
chineseOnlySessions: { type: integer, format: int64, minimum: 0 }
englishOnlySessions: { type: integer, format: int64, minimum: 0 }
mixedLanguageSessions: { type: integer, format: int64, minimum: 0 }
otherOnlySessions: { type: integer, format: int64, minimum: 0 }
AdminProductAnalytics:
type: object
additionalProperties: false
required:
- period
- northStar
- growth
- activity
- consumption
- monetization
- growthFunnel
- retention
- aiFeatures
- keyboardUsage
- referralSignals
- referralFunnel
- guardrails
properties:
period: { $ref: "#/components/schemas/AdminStatsPeriod" }
northStar:
type: object
additionalProperties: false
required: [weeklyAiActiveUsers, previousWeeklyAiActiveUsers]
properties:
weeklyAiActiveUsers: { type: integer, format: int64, minimum: 0 }
previousWeeklyAiActiveUsers: { type: integer, format: int64, minimum: 0 }
weekOverWeekPercent: { type: ["number", "null"] }
growth:
type: object
additionalProperties: false
required: [newInstallations, newAccounts, activation24h, channels]
properties:
newInstallations: { type: integer, format: int64, minimum: 0 }
newAccounts: { type: integer, format: int64, minimum: 0 }
activation24h: { $ref: "#/components/schemas/AdminAnalyticsRate" }
medianTimeToValueMinutes: { type: ["number", "null"], minimum: 0 }
channels:
type: array
items: { $ref: "#/components/schemas/AdminAnalyticsChannel" }
activity:
type: object
additionalProperties: false
required: [dau, wau, mau, successfulAiRequests]
properties:
dau:
type: integer
format: int64
minimum: 0
description: Distinct AI value-active identities in the rolling 1-day window ending at period.until.
wau:
type: integer
format: int64
minimum: 0
description: Distinct AI value-active identities in the rolling 7-day window ending at period.until.
mau:
type: integer
format: int64
minimum: 0
description: Distinct AI value-active identities in the rolling 30-day window ending at period.until.
stickinessPercent: { type: ["number", "null"], minimum: 0, maximum: 100 }
successfulAiRequests: { type: integer, format: int64, minimum: 0 }
successfulRequestsPerActiveUser: { type: ["number", "null"], minimum: 0 }
consumption:
type: object
additionalProperties: false
required: [totalCredits]
properties:
totalCredits: { type: integer, format: int64, minimum: 0 }
averageDailyCreditsPerActiveUser: { type: ["number", "null"], minimum: 0 }
medianUserDailyCredits: { type: ["number", "null"], minimum: 0 }
averageCreditsPerManagedRequest: { type: ["number", "null"], minimum: 0 }
monetization:
type: object
additionalProperties: false
required:
[payingUsers, purchases, creditsPurchased, conversion7d, conversion30d, repeatPurchaseRate, purchaseFunnel, cancelledUsers]
properties:
payingUsers: { type: integer, format: int64, minimum: 0 }
purchases: { type: integer, format: int64, minimum: 0 }
creditsPurchased: { type: integer, format: int64, minimum: 0 }
conversion7d:
$ref: "#/components/schemas/AdminAnalyticsRate"
description: Conversion for account cohorts whose full 7-day observation window matures inside the selected report period.
conversion30d:
$ref: "#/components/schemas/AdminAnalyticsRate"
description: Conversion for account cohorts whose full 30-day observation window matures inside the selected report period.
repeatPurchaseRate: { $ref: "#/components/schemas/AdminAnalyticsRate" }
purchaseFunnel:
type: array
description: Strict installation cohort from purchase view through server-verified StoreKit purchase.
items: { $ref: "#/components/schemas/AdminAnalyticsFunnelStep" }
cancelledUsers: { type: integer, format: int64, minimum: 0 }
growthFunnel:
type: array
items: { $ref: "#/components/schemas/AdminAnalyticsFunnelStep" }
retention:
type: array
items: { $ref: "#/components/schemas/AdminAnalyticsCohort" }
aiFeatures:
type: array
items: { $ref: "#/components/schemas/AdminAnalyticsFeatureUsage" }
keyboardUsage:
$ref: "#/components/schemas/AdminAnalyticsKeyboardUsage"
referralSignals:
type: object
additionalProperties: false
required: [shared, opened]
description: Directional signals only; these counts are not funnel stages.
properties:
shared: { type: integer, format: int64, minimum: 0 }
opened: { type: integer, format: int64, minimum: 0 }
referralFunnel:
type: array
items: { $ref: "#/components/schemas/AdminAnalyticsFunnelStep" }
guardrails:
type: object
additionalProperties: false
required: [clientAiSuccessRate, managedSuccessRate, creditBlockedUsers, latencyBuckets]
properties:
clientAiSuccessRate: { $ref: "#/components/schemas/AdminAnalyticsRate" }
managedSuccessRate: { $ref: "#/components/schemas/AdminAnalyticsRate" }
creditBlockedUsers: { type: integer, format: int64, minimum: 0 }
latencyBuckets:
type: array
description: Client AI terminal events grouped into declared duration buckets.
items: { $ref: "#/components/schemas/AdminAnalyticsLatencyBucket" }
AdminUserSummary:
type: object
additionalProperties: false
required: [userId, displayName, status, creditBalance, consumedCredits, createdAt]
properties:
userId: { type: string, format: uuid }
displayName: { type: string }
status: { type: string, enum: [active, suspended, closed] }
creditBalance: { type: integer, format: int64, minimum: 0 }
consumedCredits: { type: integer, format: int64, minimum: 0 }
createdAt: { type: string, format: date-time }
AdminUserPage:
type: object
additionalProperties: false
required: [items]
properties:
items:
type: array
items: { $ref: "#/components/schemas/AdminUserSummary" }
nextCursor: { type: ["string", "null"] }
AdminUserReferral:
type: object
additionalProperties: false
required: [invitedUsers, rewardedInvites]
properties:
inviterUserId: { type: ["string", "null"], format: uuid }
invitedUsers: { type: integer, format: int64, minimum: 0 }
rewardedInvites: { type: integer, format: int64, minimum: 0 }
AdminUserDetail:
type: object
additionalProperties: false
required:
- userId
- displayName
- status
- creditBalance
- consumedCredits
- createdAt
- qualifiedUsage
- usage
- referral
properties:
userId: { type: string, format: uuid }
displayName: { type: string }
status: { type: string, enum: [active, suspended, closed] }
creditBalance: { type: integer, format: int64, minimum: 0 }
consumedCredits: { type: integer, format: int64, minimum: 0 }
createdAt: { type: string, format: date-time }
lastActiveAt: { type: ["string", "null"], format: date-time }
qualifiedUsage: { type: boolean }
referralCode: { type: ["string", "null"] }
referredByUserId: { type: ["string", "null"], format: uuid }
usage:
type: array
items: { $ref: "#/components/schemas/AdminUsageAggregate" }
referral: { $ref: "#/components/schemas/AdminUserReferral" }
LedgerEntryType:
type: string
enum:
- SIGNUP_TRIAL
- MANUAL_GRANT
- USAGE_RESERVE
- USAGE_SETTLE
- USAGE_RELEASE
- USAGE_REFUND
- REFERRAL_INVITER
- REFERRAL_INVITEE
- STOREKIT_PURCHASE
- SUBSCRIPTION_GRANT
AdminLedgerDetails:
type: object
additionalProperties: false
required: [kind]
description: Privacy-minimized source metadata. Missing source associations omit the details object.
properties:
kind: { type: string, enum: [manualGrant, storeKit, referral, usage] }
reason: { type: string, description: Present for manualGrant details }
operatorName: { type: string, description: Present for manualGrant details }
productId: { type: string, description: Present for storeKit details }
transactionId: { type: string, description: Present for storeKit details }
originalTransactionId: { type: string, description: Present for storeKit details }
environment: { type: string, enum: [SANDBOX, PRODUCTION], description: Present for storeKit details }
purchasedAt: { type: string, format: date-time, description: Present for storeKit details }
role: { type: string, enum: [inviter, invitee], description: Present for referral details }
relatedUserId: { type: string, format: uuid, description: Present for referral details }
reservationId: { type: string, format: uuid, description: Present for usage details }
AdminLedgerEntry:
type: object
additionalProperties: false
required: [entryId, userId, type, entryType, amount, balanceAfter, reasonCode, createdAt]
properties:
entryId: { type: string, format: uuid }
userId: { type: string, format: uuid }
type: { type: string, enum: [grant, reserve, settle, refund] }
entryType: { $ref: "#/components/schemas/LedgerEntryType" }
amount: { type: integer, format: int64 }
balanceAfter: { type: integer, format: int64, minimum: 0 }
reasonCode:
allOf:
- $ref: "#/components/schemas/LedgerEntryType"
deprecated: true
description: Compatibility alias for entryType
referenceId: { type: ["string", "null"], format: uuid }
usageType:
type: ["string", "null"]
enum: [polish, asr, ai, agent, hotword, null]
description: Product usage associated with this ledger operation
details: { $ref: "#/components/schemas/AdminLedgerDetails" }
createdAt: { type: string, format: date-time }
AdminLedgerPage:
type: object
additionalProperties: false
required: [items]
properties:
items:
type: array
items: { $ref: "#/components/schemas/AdminLedgerEntry" }
nextCursor: { type: ["string", "null"] }
AdminGrantResponse:
type: object
additionalProperties: false
required: [transactionId, balanceAfter]
properties:
transactionId: { type: string, format: uuid }
balanceAfter: { type: integer, format: int64, minimum: 0 }
AdminAudit:
type: object
additionalProperties: false
required: [auditId, operatorName, action, targetType, targetId, result, createdAt]
properties:
auditId: { type: string, format: uuid }
operatorName: { type: string }
action: { type: string }
targetType: { type: string }
targetId: { type: string }
requestId: { type: ["string", "null"] }
result: { type: string, enum: [success, rejected] }
createdAt: { type: string, format: date-time }
AdminAuditPage:
type: object
additionalProperties: false
required: [items]
properties:
items:
type: array
items: { $ref: "#/components/schemas/AdminAudit" }
nextCursor: { type: ["string", "null"] }
AdminOperator:
type: object
additionalProperties: false
required:
- operatorId
- username
- role
- enabled
- failedLoginCount
- createdAt
- updatedAt
properties:
operatorId: { type: string, format: uuid }
username:
type: string
pattern: "^[a-z0-9][a-z0-9._@-]{2,63}$"
role: { type: string, enum: [SUPER_ADMIN, SUPPORT, ANALYST] }
enabled: { type: boolean }
failedLoginCount: { type: integer, minimum: 0 }
lockedUntil: { type: ["string", "null"], format: date-time }
lastLoginAt: { type: ["string", "null"], format: date-time }
createdAt: { type: string, format: date-time }
updatedAt: { type: string, format: date-time }
AdminOperatorPage:
type: object
additionalProperties: false
required: [items]
properties:
items:
type: array
items: { $ref: "#/components/schemas/AdminOperator" }
nextCursor: { type: ["string", "null"] }
AdminOperatorCreateRequest:
type: object
additionalProperties: false
required: [username, password, role]
properties:
username:
type: string
minLength: 3
maxLength: 64
pattern: "^[A-Za-z0-9][A-Za-z0-9._@-]{2,63}$"
password: { type: string, minLength: 12, maxLength: 1024 }
role: { type: string, enum: [SUPER_ADMIN, SUPPORT, ANALYST] }
AdminOperatorPasswordRequest:
type: object
additionalProperties: false
required: [password]
properties:
password: { type: string, minLength: 12, maxLength: 1024 }
AdminOperatorProvisioning:
type: object
additionalProperties: false
required: [operatorId, totpSecret, otpauthUri]
properties:
operatorId: { type: string, format: uuid }
operator:
oneOf:
- $ref: "#/components/schemas/AdminOperator"
- type: "null"
totpSecret:
type: string
pattern: "^[A-Z2-7]{32}$"
description: 160-bit Base32 secret returned once; never persisted in plaintext.
otpauthUri:
type: string
pattern: "^otpauth://totp/"
description: Provisioning URI returned once; never persisted.
AppleSignInRequest:
type: object
additionalProperties: false
required: [identityToken, authorizationCode, nonce]
properties:
identityToken:
type: string
description: Identity token returned by Sign in with Apple.
authorizationCode:
type: string
description: Single-use authorization code returned by Sign in with Apple.
nonce:
type: string
description: Raw nonce whose lowercase SHA-256 hex digest was sent to Apple.
displayName:
type: ["string", "null"]
maxLength: 128
description: Optional first-authorization Apple name used only to seed the nickname.
deviceCheckToken:
type: ["string", "null"]
description: Ephemeral DeviceCheck token; never persisted in plaintext.
appAttest:
oneOf:
- $ref: "#/components/schemas/AppAttestAssertion"
- type: "null"
AppAttestAssertion:
type: object
description: |
For Apple sign-in, generate the assertion with `clientDataHash` equal to
SHA-256 of the exact UTF-8 payload below, including the final line feed:
```
osg-app-attest-v1
purpose=apple-sign-in
challenge=<challenge>
identity_token_sha256=<identity-token-digest>
authorization_code_sha256=<authorization-code-digest>
nonce_sha256=<raw-nonce-digest>
```
`challenge` is the Base64URL value returned by `/v1/integrity/challenges`.
Each digest is SHA-256 of the corresponding UTF-8 request value, encoded
as unpadded Base64URL. The server reconstructs this payload and never
trusts a client-supplied hash.
additionalProperties: false
required: [keyId, challengeId, challenge, assertion]
properties:
keyId: { type: string, maxLength: 128 }
challengeId: { type: string, format: uuid }
challenge: { type: string, description: Base64URL challenge returned by the server }
assertion: { type: string, contentEncoding: base64 }
SessionTokenResponse:
type: object
required: [accountId, tokenType, accessToken, accessTokenExpiresAtEpochSeconds, refreshToken, refreshTokenExpiresAtEpochSeconds]
properties:
accountId: { type: string, format: uuid }
tokenType: { type: string, const: Bearer }
accessToken: { type: string }
accessTokenExpiresAtEpochSeconds: { type: integer, format: int64 }
refreshToken: { type: string }
refreshTokenExpiresAtEpochSeconds: { type: integer, format: int64 }
SessionTokenEnvelope:
type: object
additionalProperties: false
required: [data]
properties:
data: { $ref: "#/components/schemas/SessionTokenResponse" }
Account:
type: object
required: [id, createdAtEpochSeconds]
properties:
id: { type: string, format: uuid }
createdAtEpochSeconds: { type: integer, format: int64 }
displayName:
type: ["string", "null"]
maxLength: 64
AccountEnvelope:
type: object
additionalProperties: false
required: [data]
properties:
data: { $ref: "#/components/schemas/Account" }
UpdateAccountProfileRequest:
type: object
additionalProperties: false
required: [displayName]
properties:
displayName:
type: string
minLength: 1
maxLength: 64
DeleteAccountRequest:
type: object
additionalProperties: false
required: [identityToken, authorizationCode, nonce]
properties:
identityToken: { type: string, maxLength: 16384 }
authorizationCode: { type: string, maxLength: 4096 }
nonce: { type: string, maxLength: 512 }
CreditAccount:
type: object
additionalProperties: true
required: [userId, balance, lifetimeUsed]
properties:
userId: { type: string, format: uuid }
balance: { type: integer, format: int64, minimum: 0 }
lifetimeUsed:
type: integer
format: int64
minimum: 0
description: Settled usage minus credits returned by refunds
LedgerEntry:
type: object
additionalProperties: true
required: [id, amountDelta, balanceAfter]
properties:
id: { type: string, format: uuid }
amountDelta: { type: integer, format: int64 }
balanceAfter: { type: integer, format: int64, minimum: 0 }
StoreKitProduct:
type: object
additionalProperties: false
required: [productId, credits]
properties:
productId:
type: string
enum: [500tks, 1500tks, 3000tks]
credits: { type: integer, format: int64, minimum: 1 }
StoreKitTransactionRequest:
type: object
additionalProperties: false
required: [signedTransaction]
properties:
signedTransaction:
type: string
minLength: 100
maxLength: 32768
description: StoreKit 2 VerificationResult.jwsRepresentation
StoreKitPurchase:
type: object
additionalProperties: false
required: [transactionId, productId, creditsGranted, balanceAfter, replayed]
properties:
transactionId: { type: string, pattern: "^[0-9]{1,64}$" }
productId: { type: string, minLength: 3, maxLength: 128 }
creditsGranted: { type: integer, format: int64, minimum: 1 }
balanceAfter: { type: integer, format: int64, minimum: 0 }
replayed: { type: boolean }
StoreKitTransactionHistoryItem:
type: object
additionalProperties: false
required:
[transactionId, productId, creditsGranted, balanceAfter, purchasedAt, status]
properties:
transactionId: { type: string, pattern: "^[0-9]{1,64}$" }
productId: { type: string, minLength: 3, maxLength: 128 }
creditsGranted: { type: integer, format: int64, minimum: 1 }
balanceAfter: { type: integer, format: int64, minimum: 0 }
purchasedAt: { type: string, format: date-time }
status: { type: string, enum: [credited] }
StoreKitTransactionHistory:
type: object
additionalProperties: false
required: [items, nextCursor]
properties:
items:
type: array
items: { $ref: "#/components/schemas/StoreKitTransactionHistoryItem" }
nextCursor:
type: ["string", "null"]
maxLength: 256
AppleAppSiteAssociation:
type: object
additionalProperties: false
required: [applinks]
properties:
applinks:
type: object
additionalProperties: false
required: [details]
properties:
details:
type: array
items:
type: object
required: [appIDs, components]
properties:
appIDs:
type: array
items: { type: string }
components:
type: array
items: { type: object, additionalProperties: true }
ReferralCode:
type: object
additionalProperties: false
required: [code, inviteUrl, createdAt]
properties:
code:
type: string
pattern: "^[A-Za-z0-9_-]{22}$"
description: Permanent opaque identifier assigned once to the account.
inviteUrl:
type: string
format: uri
description: Stable first-party invitation URL containing the permanent code.
campaignId:
type: ["string", "null"]
format: uuid
description: Legacy creation metadata; it does not limit the invitation lifetime.
createdAt: { type: string, format: date-time }
ReferralProfile:
type: object
additionalProperties: false
required: [code]
properties:
code: { $ref: "#/components/schemas/ReferralCode" }
binding:
type: ["object", "null"]
additionalProperties: true
RedeemReferralRequest:
type: object
additionalProperties: false
required: [code]
properties:
code: { type: string, pattern: "^[A-Za-z0-9_-]{22}$" }
AppAttestChallengeRequest:
type: object
additionalProperties: false
required: [purpose, keyId]
properties:
purpose: { type: string, enum: [attestation, assertion] }
keyId: { type: string, maxLength: 128 }
AppAttestChallengeResponse:
type: object
required: [challengeId, challenge, expiresAtEpochSeconds]
properties:
challengeId: { type: string, format: uuid }
challenge: { type: string }
expiresAtEpochSeconds: { type: integer, format: int64 }
AppAttestationRequest:
type: object
additionalProperties: false
required: [challengeId, challenge, keyId, attestationObject]
properties:
challengeId: { type: string, format: uuid }
challenge: { type: string }
keyId: { type: string }
attestationObject: { type: string, contentEncoding: base64 }
AppAssertionRequest:
type: object
additionalProperties: false
required: [challengeId, challenge, keyId, assertion, clientDataHash]
properties:
challengeId: { type: string, format: uuid }
challenge: { type: string }
keyId: { type: string }
assertion: { type: string, contentEncoding: base64 }
clientDataHash: { type: string, description: Base64URL SHA-256 of the echoed challenge }
TextGatewayRequest:
type: object
additionalProperties: false
required: [input]
properties:
input: { type: string, minLength: 1, maxLength: 32000 }
context: { type: ["string", "null"], maxLength: 32000 }
maxOutputTokens:
type: integer
minimum: 1
maximum: 4096
default: 512
description: |
Requested output budget. The server clamps dictation polish and
edit-last-input to 512 tokens; translation, clipboard transform,
and custom skill to 2,048; and reasoning tasks to 4,096.
temperature: { type: number, minimum: 0, maximum: 1, default: 0.2 }
stream: { type: boolean, default: false }
taskKind:
type: ["string", "null"]
enum:
- dictation_polish
- translation
- edit_last_input
- ai_question
- current_information_question
- clipboard_transform
- custom_skill
- agent_planning
- null
description: |
Optional deterministic task selector. Allowed combinations are:
`polish` with `dictation_polish`, `translation`, or `edit_last_input`;
`ai` with `ai_question`, `current_information_question`,
`clipboard_transform`, or `custom_skill`;
and `agent` with `agent_planning`. Omission defaults respectively to
`dictation_polish`, `ai_question`, and `agent_planning`. A mismatch
returns `400 invalid_request`.
requestSource:
type: ["string", "null"]
enum: [hotword, null]
description: Optional product entry point; hotword is accepted only for AI requests
requestPurpose:
type: ["string", "null"]
enum: [oobe, null]
description: |
Optional server-audited billing purpose. Account grants accept `oobe`
only for complimentary dictation polish. Anonymous OOBE grants require
`oobe` together with an `oobeFeature`.
oobeFeature:
type: ["string", "null"]
enum: [voice_input, clipboard_translate, clipboard_reply, ask_ai, null]
description: |
Required for anonymous OOBE grants. The server validates that the
feature matches the requested capability and task kind, and allows
each feature to succeed only once per short-lived OOBE grant.
CreateOobeGrantRequest:
type: object
additionalProperties: false
required: [challengeId, challenge, keyId, installationId, assertion]
properties:
challengeId: { type: string, format: uuid }
challenge: { type: string, description: Base64URL challenge returned by the integrity API }
keyId: { type: string, minLength: 1, maxLength: 256 }
installationId: { type: string, format: uuid }
assertion: { type: string, contentEncoding: base64 }
RefreshOobeGrantRequest:
type: object
additionalProperties: false
required: [refreshToken]
properties:
refreshToken: { type: string, minLength: 32, maxLength: 512 }
OobeGrantTokens:
type: object
additionalProperties: false
required:
[grantId, scopes, features, accessToken, accessExpiresAt, refreshToken, refreshExpiresAt]
properties:
grantId: { type: string, format: uuid }
scopes:
type: array
uniqueItems: true
items: { type: string, enum: [polish, ai] }
features:
type: array
uniqueItems: true
items: { type: string, enum: [voice_input, clipboard_translate, clipboard_reply, ask_ai] }
accessToken: { type: string }
accessExpiresAt: { type: string, format: date-time }
refreshToken: { type: string }
refreshExpiresAt: { type: string, format: date-time }
CreateGatewayGrantRequest:
type: object
additionalProperties: false
required: [scopes]
properties:
scopes:
type: array
uniqueItems: true
minItems: 1
items: { type: string, enum: [polish, ai, agent, asr] }
lifetimeSeconds: { type: ["integer", "null"], format: int64, minimum: 1 }
RefreshGatewayGrantRequest:
type: object
additionalProperties: false
required: [refreshToken]
properties:
refreshToken: { type: string, minLength: 32 }
GatewayGrantTokens:
type: object
additionalProperties: false
required: [grantId, scopes, accessToken, accessExpiresAt, refreshToken, refreshExpiresAt]
properties:
grantId: { type: string, format: uuid }
scopes:
type: array
uniqueItems: true
items: { type: string, enum: [polish, ai, agent, asr] }
accessToken: { type: string }
accessExpiresAt: { type: string, format: date-time }
refreshToken: { type: string }
refreshExpiresAt: { type: string, format: date-time }
CreateAsrSessionRequest:
type: object
additionalProperties: false
required: [estimatedDurationMillis]
properties:
format: { type: string, enum: [pcm, wav, ogg, mp3], default: pcm }
codec: { type: string, enum: [raw, opus], default: raw }
sampleRate: { type: integer, const: 16000 }
bits: { type: integer, const: 16 }
channels: { type: integer, minimum: 1, maximum: 2, default: 1 }
language: { type: ["string", "null"], maxLength: 32 }
estimatedDurationMillis: { type: integer, minimum: 1, maximum: 600000 }
CreateAsrSessionResponse:
type: object
required: [sessionId, websocketPath, maxFrameBytes, idleTimeoutMillis]
properties:
sessionId: { type: string, format: uuid }
websocketPath: { type: string }
maxFrameBytes: { type: integer }
idleTimeoutMillis: { type: integer, format: int64 }
ApiError:
type: object
additionalProperties: false
required: [code, message]
properties:
code: { type: string }
message: { type: string }
ApiErrorEnvelope:
type: object
additionalProperties: false
required: [error]
properties:
error: { $ref: "#/components/schemas/ApiError" }
GatewayError:
type: object
required: [code, message, requestId]
properties:
code: { type: string }
message: { type: string }
requestId: { type: string }