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.
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
| Environment | Base URL |
|---|---|
| Production | https://api-prod.useknit.io |
| Development / sandbox | https://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
Exchange the client ID and secret for a bearer token using the standard
client_credentials grant.
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_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.
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.
| Scope | Grants |
|---|---|
collections:read | View and list collections |
collections:write | Create, update, and delete collections |
payouts:read | View and list payouts |
payouts:write | Create payouts |
wallets:read | View and list API wallets and their transactions |
wallets:write | Create, update, and delete API wallets |
payout-wallets:read | View API account balances |
payout-wallets:write | Create API accounts |
managed-signing:read | View managed signing wallets, policies, requests, and audit events |
managed-signing:write | Create and approve managed signing wallets, policies, and requests |
blockchain-notifications:read | View address notification subscriptions |
blockchain-notifications:write | Create, update, and delete address notification subscriptions |
custody:read | View custody owners, wallets, policies, signing requests, and operations |
custody:write | Create 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:writecan also callGET /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:
{
"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.
{
"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
| Status | Message | What it means |
|---|---|---|
401 | Authentication required. Provide a Bearer token. | No Authorization header was sent |
401 | Invalid or expired OAuth token | The token is malformed, revoked, or past expires_in |
401 | OAuth client not found or has been revoked | The client behind the token no longer exists |
401 | IP address not whitelisted | The caller's IP is not on the business allow list |
401 | Insufficient 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.