K-OTP

Get wallet balance

GET /v1/balance

GET https://api.k-otp.dev/v1/balance

Overview

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 keysRequired scopeAlso accepted
sk_otp:balance*

Response (200)

Current wallet balance.

FieldTypeRequiredDescription
appIdstringyesThe calling app.
walletIdstringyesorg:<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"yesorganization when the balance is shared by every app of the organization; test for the simulated wallet of a test-mode key.
organizationIdstringOwning organization, present when walletScope is organization.
balancenumberyesCurrent total balance as a credit quantity (not money): purchased plus promotional credits.
currencystringyesUnit of balance; currently always CREDIT.
updatedAtstringyesWhen the wallet last changed (RFC 3339).
promoBalanceintegeryesUnused, 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.
promoNextExpirynull | objectyesThe 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.

StatuscodeDescription
400BAD_REQUESTthe request failed input validation. message describes the first failing constraint.
401UNAUTHORIZEDthe Authorization: Bearer credential is missing, malformed, inactive, or rejected by introspection.
403FORBIDDENthe credential lacks otp:balance (or an accepted alias), or a pk_ public key was used. Public keys are limited to issue/verify.
409CONFLICTthe request conflicts with the current state of the resource.
429TOO_MANY_REQUESTSonly 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.
500INTERNAL_SERVER_ERRORunexpected server failure. Undefined errors (defined: false) use the same envelope.
503SERVICE_UNAVAILABLEcredential 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"

On this page