Get wallet balance
GET /v1/balance
GET https://api.k-otp.dev/v1/balanceOverview
Returns the current balance of the credit wallet the authenticated app spends from: the organization wallet shared by every app of the organization (walletScope: organization), or a legacy per-app wallet (walletScope: app). balance is a credit quantity (not money) and currency is CREDIT. Reading the balance never affects OTP issue status.
Test mode. A sk_test_ key never reads the real wallet: it gets a fixed simulated balance (walletScope: test, walletId: test:<appId>, balance: 1000000, promoBalance: 0, promoNextExpiry: null, mode: test) that test issues never reduce. Scenarios 07/08 of POST /issue simulate the insufficient-credit paths.
Requires an sk_ secret key; pk_ public keys are rejected with 403.
Authentication
| Accepted keys | Required scope | Also accepted |
|---|---|---|
sk_ | otp:balance | * |
Response (200)
Current wallet balance.
| Field | Type | Required | Description |
|---|---|---|---|
appId | string | yes | The calling app. |
walletId | string | yes | org:<organizationId> for the organization wallet; app:<appId> for a legacy per-app wallet; test:<appId> for the simulated wallet of a test-mode key. |
walletScope | "app" | "organization" | "test" | yes | organization when the balance is shared by every app of the organization; test for the simulated wallet of a test-mode key. |
organizationId | string | Owning organization, present when walletScope is organization. | |
balance | number | yes | Current total balance as a credit quantity (not money): purchased plus promotional credits. |
currency | string | yes | Unit of balance; currently always CREDIT. |
updatedAt | string | yes | When the wallet last changed (RFC 3339). |
promoBalance | integer | yes | Unused, unexpired promotional (free) credits included in balance. Purchased credits are roughly balance - promoBalance. Computed from the promotional lot summary, which can lag recent sends by about 10 minutes; never more than balance. Always 0 for a legacy per-app wallet. |
promoNextExpiry | null | object | yes | The promotional credits that expire first, or null when there are none. |
mode | "live" | "test" | Mode of the API key that made the request. A test key gets a fixed simulated balance (walletScope: 'test', walletId: 'test:<appId>') that never changes; the real wallet is never read. |
Error responses
Every error uses the { defined, code, status, message, data? } envelope. See Errors.
| Status | code | Description |
|---|---|---|
| 400 | BAD_REQUEST | the request failed input validation. message describes the first failing constraint. |
| 401 | UNAUTHORIZED | the Authorization: Bearer credential is missing, malformed, inactive, or rejected by introspection. |
| 403 | FORBIDDEN | the credential lacks otp:balance (or an accepted alias), or a pk_ public key was used. Public keys are limited to issue/verify. |
| 409 | CONFLICT | the request conflicts with the current state of the resource. |
| 429 | TOO_MANY_REQUESTS | only for test-mode (sk_test_) keys, whose reads are metered (data.limit: perKey per minute or perKeyDaily per day, data.policy: platform). Wait data.retryAfterMs milliseconds (Retry-After in seconds). Live keys are never rate limited on reads. |
| 500 | INTERNAL_SERVER_ERROR | unexpected server failure. Undefined errors (defined: false) use the same envelope. |
| 503 | SERVICE_UNAVAILABLE | credential introspection or a downstream dependency is temporarily unavailable. Retry with backoff. |
Example request
curl "https://api.k-otp.dev/v1/balance" \
-H "Authorization: Bearer $KOTP_SECRET_KEY"