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. 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/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 idempotently provision its invite code responses: "200": description: Referral profile content: application/json: schema: { type: object, additionalProperties: true } default: { $ref: "#/components/responses/Error" } /v1/referrals/code: post: summary: Idempotently create the current campaign 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/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. Search and tools are currently disabled. An authenticated `oobe` purpose is accepted only for dictation polish. The first successful request per account is complimentary; later attempts fail without falling through to paid billing. 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" } /.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/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/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/AdminCreatedAtSort" - $ref: "#/components/parameters/AdminSortOrder" responses: "200": description: Credit ledger entries ordered by creation time and 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/AdminCreatedAtSort" - $ref: "#/components/parameters/AdminSortOrder" responses: "200": description: Latest credit ledger entries ordered by creation time and 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 - 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 } AdminCsrf: name: X-CSRF-Token in: header required: true schema: { type: string, minLength: 32, maxLength: 512 } AdminRange: name: range in: query 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, adjustment] 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._+-]+$" ProductAnalyticsBatchRequest: type: object additionalProperties: false required: [installationId, events] properties: installationId: { type: string, format: uuid } events: type: array minItems: 1 maxItems: 50 items: { $ref: "#/components/schemas/ProductAnalyticsEvent" } ProductAnalyticsBatchResponse: type: object additionalProperties: false required: [accepted, replayed] properties: accepted: { type: integer, minimum: 0, maximum: 50 } replayed: { type: integer, minimum: 0, maximum: 50 } 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. 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 } AdminOverview: type: object additionalProperties: false required: - totalUsers - activeUsers - newUsers - totalCreditBalance - creditsGranted - creditsUsed - trend - usage properties: totalUsers: { type: integer, format: int64, minimum: 0 } activeUsers: { type: integer, format: int64, minimum: 0 } 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: [邀请码创建, 成功绑定, 有效使用并奖励] 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: [pendingBindings, ineligibleBindings, funnel, ranking] properties: 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 } 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 } 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 } AdminProductAnalytics: type: object additionalProperties: false required: - period - northStar - growth - activity - consumption - monetization - growthFunnel - retention - aiFeatures - referralFunnel - guardrails properties: period: type: object additionalProperties: false required: [from, until] properties: from: { type: string, format: date-time } until: { type: string, format: date-time } 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 } wau: { type: integer, format: int64, minimum: 0 } mau: { type: integer, format: int64, minimum: 0 } 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] 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" } conversion30d: { $ref: "#/components/schemas/AdminAnalyticsRate" } repeatPurchaseRate: { $ref: "#/components/schemas/AdminAnalyticsRate" } growthFunnel: type: array items: { $ref: "#/components/schemas/AdminAnalyticsFunnelStep" } retention: type: array items: { $ref: "#/components/schemas/AdminAnalyticsCohort" } aiFeatures: type: array items: { $ref: "#/components/schemas/AdminAnalyticsFeatureUsage" } referralFunnel: type: array items: { $ref: "#/components/schemas/AdminAnalyticsFunnelStep" } guardrails: type: object additionalProperties: false required: [clientAiSuccessRate, managedSuccessRate, creditBlockedUsers] properties: clientAiSuccessRate: { $ref: "#/components/schemas/AdminAnalyticsRate" } managedSuccessRate: { $ref: "#/components/schemas/AdminAnalyticsRate" } creditBlockedUsers: { type: integer, format: int64, minimum: 0 } 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" } AdminLedgerEntry: type: object additionalProperties: false required: [entryId, userId, type, amount, balanceAfter, reasonCode, createdAt] properties: entryId: { type: string, format: uuid } userId: { type: string, format: uuid } type: { type: string, enum: [grant, reserve, settle, refund, adjustment] } amount: { type: integer, format: int64 } balanceAfter: { type: integer, format: int64, minimum: 0 } reasonCode: { type: string } usageType: type: ["string", "null"] enum: [polish, asr, ai, agent, hotword, null] description: Product usage associated with this ledger operation 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= identity_token_sha256= authorization_code_sha256= nonce_sha256= ``` `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: true properties: code: { type: string, pattern: "^[A-Za-z0-9_-]{22}$" } 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 - 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`, `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. `oobe` is valid only with `polish` and `dictation_polish`, and is complimentary once per account. 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 }