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/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: 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 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 } /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" responses: "200": description: Referral statistics content: application/json: schema: { $ref: "#/components/schemas/AdminReferralOverview" } "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" responses: "200": description: Privacy-minimized user summaries content: application/json: schema: { $ref: "#/components/schemas/AdminUserPage" } "400": { description: Query 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/Limit" responses: "200": description: Credit ledger entries ordered by creation time and entry ID content: application/json: schema: { $ref: "#/components/schemas/AdminLedgerPage" } "400": { description: Cursor is malformed } "403": { description: ANALYST role cannot access credit ledger records } "404": { description: User was not found } /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" responses: "200": description: Operators ordered by creation time content: application/json: schema: { $ref: "#/components/schemas/AdminOperatorPage" } "403": { description: INSUFFICIENT_PERMISSION; SUPER_ADMIN is required } "400": { description: Cursor or limit 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" responses: "200": description: Recent audit events content: application/json: schema: { $ref: "#/components/schemas/AdminAuditPage" } "400": { description: 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. 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 } 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: 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" } 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, type, amount, balanceAfter, reasonCode, createdAt] properties: entryId: { 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 } 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 } 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 } temperature: { type: number, minimum: 0, maximum: 1, default: 0.2 } stream: { type: boolean, default: false } 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 }