Authentication

Authentication

Every /api/v1 endpoint is authenticated with an OAuth 2.0 client credentials access token. Send it as a bearer token on each request.

Authenticated request
curl https://api-prod.useknit.io/api/v1/payouts \
  -H "Authorization: Bearer $KNIT_ACCESS_TOKEN" \
  -H "Accept: application/json"
⚠️

API keys are no longer accepted. The X-API-KEY header is not a valid credential on any /api/v1 endpoint — requests that rely on it are rejected with 401. If you are still sending one, migrate to OAuth client credentials.

Environments

EnvironmentBase URL
Productionhttps://api-prod.useknit.io
Development / sandboxhttps://api-dev.useknit.io

Credentials are issued per environment and are not interchangeable.

Create an OAuth client

In the Knit dashboard go to Business → Developer → OAuth Clients and create a client. Choose only the scopes the integration needs — you can create several clients so that, for example, a reporting service holds read scopes only.

Copy the client ID and the plain secret when they are shown. The secret is not retrievable afterwards; if you lose it, rotate the client to issue a new one.

Creating and rotating clients is a dashboard action. There is no public API for provisioning credentials.

Request an access token

POST
https://api-prod.useknit.io/oauth/token

Exchange the client ID and secret for a bearer token using the standard client_credentials grant.

Request a token
curl https://api-prod.useknit.io/oauth/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "client_credentials",
    "client_id": "<client-id>",
    "client_secret": "<client-secret>"
  }'
Token response
{
  "token_type": "Bearer",
  "expires_in": 7200,
  "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIs..."
}
⚠️

Read the lifetime from expires_in on each response rather than hardcoding it. Cache the token until shortly before it expires and request a new one on demand — do not mint a token per API call.

Requesting narrower scopes

Omit the scope parameter and the token is issued with the client's full set of scopes. Pass an explicit space-separated scope to mint a token that is narrower than the client — useful for short-lived tokens handed to a subsystem.

Narrower token
curl https://api-prod.useknit.io/oauth/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "client_credentials",
    "client_id": "<client-id>",
    "client_secret": "<client-secret>",
    "scope": "collections:read payouts:read"
  }'

A token can only ever narrow the client's grant. Requesting a scope the client does not hold fails with 401 OAuth token contains scopes not granted to this client.

Scopes

Scopes follow a resource:action pattern.

ScopeGrants
collections:readView and list collections
collections:writeCreate, update, and delete collections
payouts:readView and list payouts
payouts:writeCreate payouts
wallets:readView and list API wallets and their transactions
wallets:writeCreate, update, and delete API wallets
payout-wallets:readView API account balances
payout-wallets:writeCreate API accounts
managed-signing:readView managed signing wallets, policies, requests, and audit events
managed-signing:writeCreate and approve managed signing wallets, policies, and requests
blockchain-notifications:readView address notification subscriptions
blockchain-notifications:writeCreate, update, and delete address notification subscriptions
custody:readView custody owners, wallets, policies, signing requests, and operations
custody:writeCreate custody resources and confirm or execute operations
*Full access to every resource

Two rules apply when a scope is checked:

  • Write implies read. A client holding payouts:write can also call GET /api/v1/payouts. The reverse is not true.
  • * satisfies everything. Grant it only when a client genuinely needs the whole surface.

A request that reaches an endpoint without a satisfying scope is rejected:

401 — insufficient scope
{
  "statusCode": 401,
  "message": "Insufficient permissions. This action requires scope: payouts:write",
  "data": null,
  "success": false
}

IP allow-listing

Access tokens are only accepted from IP addresses on your business's allow list. Register the outbound IPs of every environment that will call the API — staging included — in the dashboard before going live.

401 — IP not allow-listed
{
  "statusCode": 401,
  "message": "IP address not whitelisted",
  "data": null,
  "success": false
}

If your servers sit behind a NAT gateway or egress proxy, allow-list the gateway's address rather than the instance addresses.

Authentication errors

StatusMessageWhat it means
401Authentication required. Provide a Bearer token.No Authorization header was sent
401Invalid or expired OAuth tokenThe token is malformed, revoked, or past expires_in
401OAuth client not found or has been revokedThe client behind the token no longer exists
401IP address not whitelistedThe caller's IP is not on the business allow list
401Insufficient permissions. This action requires scope: …The token lacks the scope the endpoint requires

Credential hygiene

  • Issue a separate client per application and per environment so a compromised credential can be revoked without a wider outage.
  • Grant the narrowest scope set that works. Prefer several small clients over one * client.
  • Store secrets in a secrets manager, never in source control or client-side code. These credentials are for server-to-server use only.
  • Rotate on a schedule and immediately after any suspected exposure. Rotation issues a new secret without changing the client's scopes.