Skip to main content

API Overview

Everything on the Kryptos API shares one base URL, one authentication model and one workspace model. Response and pagination shapes vary by domain — those variations are documented here once, so the endpoint pages can stay focused on parameters and fields.

Base URL: https://api-v2.kryptos.io

Every endpoint lives under /v1, and the path you call is the path the resource lives at — there is no service name in the URL:

https://api-v2.kryptos.io/v1/holdings
https://api-v2.kryptos.io/v1/transactions
https://api-v2.kryptos.io/v1/integrations

Authentication​

Send an OAuth 2.0 access token as a bearer token:

curl -X GET "https://api-v2.kryptos.io/v1/holdings" \
-H "Authorization: Bearer ACCESS_TOKEN"

That single header is all you need. Earlier versions of this API also required X-Client-Id and X-Client-Secret on every data call; they are no longer part of the data-call contract. Access tokens are validated against the grant that issued them, so nothing else is required.

Client credentials remain the credential where they belong: the Kryptos Connect link-token and token-exchange endpoints, which authenticate your application rather than a user. Keep those server-side.

Get an access token by either route:

RouteUse whenGuide
Authorization Code + PKCEYour users sign in with their own Kryptos accountsOAuth 2.0
Kryptos ConnectYou embed the widget and your users link accounts through itConnect overview

API keys​

Enterprise customers can call the API with a long-lived key instead of an access token:

curl -X GET "https://api-v2.kryptos.io/v1/users/me" \
-H "x-api-key: kryptos_live_xxxx"
API keys reach /v1/users/* only

Every other endpoint documented here accepts a bearer token and not x-api-key — sending a key to /v1/holdings returns 401 unauthorized. Use an access token for portfolio, transaction and integration data. Wider API-key coverage is on the roadmap; contact [email protected] if you need it.

API keys carry the same scopes as access tokens and are bound to one workspace when created. See API Key Authentication.

Workspaces​

All portfolio data belongs to a workspace, not directly to a user. How the API resolves which workspace you mean depends on your credential:

  • Access tokens issued through Kryptos Connect or OAuth are bound to one workspace. Omit the workspace parameter — it is already in the token. Passing a different workspace id is rejected with 403 forbidden.
  • API keys are bound to one workspace too, chosen when the key is created. Omit the workspace parameter — it is already on the key. Passing a different workspace id is rejected with 403 forbidden. Keys minted before workspace binding carry none; those resolve the workspace from ?wid=WORKSPACE_ID instead, and are rejected outright once binding is enforced.

Scopes​

Each endpoint requires one scope, listed on its page as Required Permission. The full vocabulary:

ScopeCovers
portfolios:readHoldings, calculated balances, DeFi, NFTs, portfolios
transactions:readTransactions, ledgers, spam
integrations:readIntegrations
contacts:readContacts, counterparties
users:readUser profile
workspace:readWorkspace and its ingestion limits
—Providers, assets, labels — shared reference data, not workspace-specific

OAuth flows additionally use the standard OIDC scopes openid, profile, email and offline_access. The full vocabulary includes write scopes and further resources — see Available Scopes — but the endpoints documented here are read-only, so the five above are all you need to request.

contacts:read is not in the default client scope set — your client must be registered with it, not just request it. See Scopes.

A token can never hold more than the granting member's role allows — an editor consenting to a scope their role excludes receives a grant without it. Read the scope value returned with the token rather than assuming you got what you asked for.

Response shapes​

Three shapes are in use. Which one you get depends on the domain, so check the endpoint page before writing a parser.

1. Wrapped — { success, data }

Integrations, providers, portfolios, user profile, assets, labels and spam:

{
"success": true,
"data": { "...": "..." }
}

Paginated variants nest the collection under a named key beside pagination:

{
"success": true,
"data": {
"integrations": [],
"pagination": { "page": 1, "limit": 50, "total": 120, "totalPages": 3, "hasMore": true }
}
}

2. Flattened — { success, ...result }

Holdings, calculated balances and DeFi holdings put their pagination fields at the top level, next to success, rather than inside data:

{
"success": true,
"data": [],
"totalCount": 42,
"offset": 0,
"limit": 50,
"hasMore": false,
"totalValue": 250000
}

3. Bare — { data, meta }

Transactions and ledgers return no success field at all, and nest their errors:

{
"data": [],
"meta": { "limit": 50, "offset": 0, "hasMore": true, "total": 1284 }
}

Pagination​

StyleUsed byRequestResponse
Page numberIntegrations, providers, portfolios?page=1&limit=50 (limit 1–100)pagination: { page, limit, total, totalPages, hasMore }
Page number, in metaContacts, counterparties?page=1&limit=50meta: { total, page, limit } — no totalPages/hasMore
Offset, flattenedHoldings, DeFi, calculated balances?offset=0&limit=50 (limit 1–1000)top-level totalCount, offset, limit, hasMore
Offset, in metaTransactions, ledgers?offset=0&limit=50 (limit 1–200)meta: { limit, offset, hasMore, total }
Totals onlyNFTs, NFT collections?offset=0&limit=50 (limit 1–1000)top-level totalCount only — no offset/limit/hasMore echoed

Three details worth hard-coding into a client:

  • Page-number endpoints clamp limit to 100 silently rather than erroring.
  • GET /v1/ledgers returns meta without hasMore.
  • The NFT endpoints echo neither offset, limit nor hasMore.

Wherever hasMore is absent, compare offset + data.length against the total. Note also that contacts and counterparties return a fourth shape — { success, data, meta: { total, page, limit } }, with the collection beside success but the pagination nested under meta — and that limit defaults differ per endpoint: 50 on most, 20 on contacts and spam.

Errors​

Authentication and authorization failures use the OAuth 2.0 error format, with no success field:

{
"error": "insufficient_scope",
"error_description": "Missing: portfolios:read"
}
StatuserrorMeaning
401unauthorizedNo credentials, or an invalid/expired token
403insufficient_scopeValid token, but it lacks the scope this endpoint needs
403forbiddenNot a member of the workspace, or the token is bound to a different one
400bad_requestWorkspace id required but not supplied

Application errors carry a machine-readable code. Integrations and portfolios return them wrapped:

{
"success": false,
"error": "Integration not found",
"code": "NOT_FOUND",
"details": {}
}
CodeStatusMeaning
VALIDATION_ERROR400Request failed schema validation; details.issues lists each field
NOT_FOUND404Resource does not exist in this workspace
EXTERNAL_SERVICE_ERROR502An upstream exchange, chain or price provider failed
RATE_LIMIT_EXCEEDED429Too many requests
INTERNAL_ERROR500Unexpected server error

Transactions and ledgers nest the same information instead:

{
"error": { "code": "NOT_FOUND", "message": "Transaction not found" }
}
CodeStatusMeaning
NOT_FOUND404Transaction or ledger does not exist in this workspace
INTERNAL_ERROR500Unexpected server error

See Error Handling for worked examples.

Timestamps​

Transaction and ledger timestamps are Unix milliseconds on both request and response — they are the values you filter on with startTime and endTime. Record metadata such as createdAt, updatedAt, addedOn and lastSyncedAt is ISO 8601.

Next​