Files
OSGAccountServer/docs/openapi.yaml
T
Rocky 75c046d91d Show latest credit ledger in admin
Expose a paginated global ledger timeline and load it automatically so operators can see recent credit activity without first locating a user.
2026-08-19 18:05:54 +08:00

1447 lines
51 KiB
YAML

openapi: 3.1.0
info:
title: OSG Account Server API
version: 0.1.0
description: |
Account, credit, referral, integrity, and managed AI APIs for OSGKeyboard.
Local features and user-owned API keys remain independent of this service.
servers:
- url: https://account.osglab.com
security:
- bearerAuth: []
paths:
/health:
get:
security: []
summary: Compatibility health check
responses:
"200": { $ref: "#/components/responses/HealthUp" }
/health/live:
get:
security: []
summary: Process liveness
responses:
"200": { $ref: "#/components/responses/HealthUp" }
/health/ready:
get:
security: []
summary: Database and migration readiness
responses:
"200": { $ref: "#/components/responses/HealthUp" }
"503":
description: Database is unavailable or migrations failed.
/v1/auth/apple:
post:
security: []
summary: Exchange Sign in with Apple credentials for an OSG session
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/AppleSignInRequest" }
responses:
"200":
description: Authenticated session
content:
application/json:
schema: { $ref: "#/components/schemas/SessionTokenEnvelope" }
default: { $ref: "#/components/responses/Error" }
/v1/auth/refresh:
post:
security: []
summary: Rotate a refresh token
requestBody:
required: true
content:
application/json:
schema:
type: object
additionalProperties: false
required: [refreshToken]
properties:
refreshToken: { type: string, minLength: 32 }
responses:
"200":
description: Rotated session
content:
application/json:
schema: { $ref: "#/components/schemas/SessionTokenEnvelope" }
default: { $ref: "#/components/responses/Error" }
/v1/auth/logout:
post:
summary: Revoke the current session family
responses:
"204": { description: Session revoked }
default: { $ref: "#/components/responses/Error" }
/v1/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/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/Limit"
responses:
"200":
description: Latest 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 }
/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, 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 }
createdAt: { type: string, format: date-time }
AdminLedgerPage:
type: object
additionalProperties: false
required: [items]
properties:
items:
type: array
items: { $ref: "#/components/schemas/AdminLedgerEntry" }
nextCursor: { type: ["string", "null"] }
AdminGrantResponse:
type: object
additionalProperties: false
required: [transactionId, balanceAfter]
properties:
transactionId: { type: string, format: uuid }
balanceAfter: { type: integer, format: int64, minimum: 0 }
AdminAudit:
type: object
additionalProperties: false
required: [auditId, operatorName, action, targetType, targetId, result, createdAt]
properties:
auditId: { type: string, format: uuid }
operatorName: { type: string }
action: { type: string }
targetType: { type: string }
targetId: { type: string }
requestId: { type: ["string", "null"] }
result: { type: string, enum: [success, rejected] }
createdAt: { type: string, format: date-time }
AdminAuditPage:
type: object
additionalProperties: false
required: [items]
properties:
items:
type: array
items: { $ref: "#/components/schemas/AdminAudit" }
nextCursor: { type: ["string", "null"] }
AdminOperator:
type: object
additionalProperties: false
required:
- operatorId
- username
- role
- enabled
- failedLoginCount
- createdAt
- updatedAt
properties:
operatorId: { type: string, format: uuid }
username:
type: string
pattern: "^[a-z0-9][a-z0-9._@-]{2,63}$"
role: { type: string, enum: [SUPER_ADMIN, SUPPORT, ANALYST] }
enabled: { type: boolean }
failedLoginCount: { type: integer, minimum: 0 }
lockedUntil: { type: ["string", "null"], format: date-time }
lastLoginAt: { type: ["string", "null"], format: date-time }
createdAt: { type: string, format: date-time }
updatedAt: { type: string, format: date-time }
AdminOperatorPage:
type: object
additionalProperties: false
required: [items]
properties:
items:
type: array
items: { $ref: "#/components/schemas/AdminOperator" }
nextCursor: { type: ["string", "null"] }
AdminOperatorCreateRequest:
type: object
additionalProperties: false
required: [username, password, role]
properties:
username:
type: string
minLength: 3
maxLength: 64
pattern: "^[A-Za-z0-9][A-Za-z0-9._@-]{2,63}$"
password: { type: string, minLength: 12, maxLength: 1024 }
role: { type: string, enum: [SUPER_ADMIN, SUPPORT, ANALYST] }
AdminOperatorPasswordRequest:
type: object
additionalProperties: false
required: [password]
properties:
password: { type: string, minLength: 12, maxLength: 1024 }
AdminOperatorProvisioning:
type: object
additionalProperties: false
required: [operatorId, totpSecret, otpauthUri]
properties:
operatorId: { type: string, format: uuid }
operator:
oneOf:
- $ref: "#/components/schemas/AdminOperator"
- type: "null"
totpSecret:
type: string
pattern: "^[A-Z2-7]{32}$"
description: 160-bit Base32 secret returned once; never persisted in plaintext.
otpauthUri:
type: string
pattern: "^otpauth://totp/"
description: Provisioning URI returned once; never persisted.
AppleSignInRequest:
type: object
additionalProperties: false
required: [identityToken, authorizationCode, nonce]
properties:
identityToken:
type: string
description: Identity token returned by Sign in with Apple.
authorizationCode:
type: string
description: Single-use authorization code returned by Sign in with Apple.
nonce:
type: string
description: Raw nonce whose lowercase SHA-256 hex digest was sent to Apple.
displayName:
type: ["string", "null"]
maxLength: 128
description: Optional first-authorization Apple name used only to seed the nickname.
deviceCheckToken:
type: ["string", "null"]
description: Ephemeral DeviceCheck token; never persisted in plaintext.
appAttest:
oneOf:
- $ref: "#/components/schemas/AppAttestAssertion"
- type: "null"
AppAttestAssertion:
type: object
description: |
For Apple sign-in, generate the assertion with `clientDataHash` equal to
SHA-256 of the exact UTF-8 payload below, including the final line feed:
```
osg-app-attest-v1
purpose=apple-sign-in
challenge=<challenge>
identity_token_sha256=<identity-token-digest>
authorization_code_sha256=<authorization-code-digest>
nonce_sha256=<raw-nonce-digest>
```
`challenge` is the Base64URL value returned by `/v1/integrity/challenges`.
Each digest is SHA-256 of the corresponding UTF-8 request value, encoded
as unpadded Base64URL. The server reconstructs this payload and never
trusts a client-supplied hash.
additionalProperties: false
required: [keyId, challengeId, challenge, assertion]
properties:
keyId: { type: string, maxLength: 128 }
challengeId: { type: string, format: uuid }
challenge: { type: string, description: Base64URL challenge returned by the server }
assertion: { type: string, contentEncoding: base64 }
SessionTokenResponse:
type: object
required: [accountId, tokenType, accessToken, accessTokenExpiresAtEpochSeconds, refreshToken, refreshTokenExpiresAtEpochSeconds]
properties:
accountId: { type: string, format: uuid }
tokenType: { type: string, const: Bearer }
accessToken: { type: string }
accessTokenExpiresAtEpochSeconds: { type: integer, format: int64 }
refreshToken: { type: string }
refreshTokenExpiresAtEpochSeconds: { type: integer, format: int64 }
SessionTokenEnvelope:
type: object
additionalProperties: false
required: [data]
properties:
data: { $ref: "#/components/schemas/SessionTokenResponse" }
Account:
type: object
required: [id, createdAtEpochSeconds]
properties:
id: { type: string, format: uuid }
createdAtEpochSeconds: { type: integer, format: int64 }
displayName:
type: ["string", "null"]
maxLength: 64
AccountEnvelope:
type: object
additionalProperties: false
required: [data]
properties:
data: { $ref: "#/components/schemas/Account" }
UpdateAccountProfileRequest:
type: object
additionalProperties: false
required: [displayName]
properties:
displayName:
type: string
minLength: 1
maxLength: 64
DeleteAccountRequest:
type: object
additionalProperties: false
required: [identityToken, authorizationCode, nonce]
properties:
identityToken: { type: string, maxLength: 16384 }
authorizationCode: { type: string, maxLength: 4096 }
nonce: { type: string, maxLength: 512 }
CreditAccount:
type: object
additionalProperties: true
required: [userId, balance, lifetimeUsed]
properties:
userId: { type: string, format: uuid }
balance: { type: integer, format: int64, minimum: 0 }
lifetimeUsed:
type: integer
format: int64
minimum: 0
description: Settled usage minus credits returned by refunds
LedgerEntry:
type: object
additionalProperties: true
required: [id, amountDelta, balanceAfter]
properties:
id: { type: string, format: uuid }
amountDelta: { type: integer, format: int64 }
balanceAfter: { type: integer, format: int64, minimum: 0 }
StoreKitProduct:
type: object
additionalProperties: false
required: [productId, credits]
properties:
productId:
type: string
enum: [500tks, 1500tks, 3000tks]
credits: { type: integer, format: int64, minimum: 1 }
StoreKitTransactionRequest:
type: object
additionalProperties: false
required: [signedTransaction]
properties:
signedTransaction:
type: string
minLength: 100
maxLength: 32768
description: StoreKit 2 VerificationResult.jwsRepresentation
StoreKitPurchase:
type: object
additionalProperties: false
required: [transactionId, productId, creditsGranted, balanceAfter, replayed]
properties:
transactionId: { type: string, pattern: "^[0-9]{1,64}$" }
productId: { type: string, minLength: 3, maxLength: 128 }
creditsGranted: { type: integer, format: int64, minimum: 1 }
balanceAfter: { type: integer, format: int64, minimum: 0 }
replayed: { type: boolean }
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 }