How a request proves who sent it. An OAuth access token is the preferred method and an API key the older one; both go in the Authorization header as a Bearer token.
Every integration: a server acting as its own backend user, or an app acting for a person. Lasts four hours; the refresh token renews it
API key, older
Authorization: Bearer YOUR_API_KEY
Existing integrations. Works until it is deleted, and can be limited to a list of IP addresses
Session cookie
The browser's cookie, plus X-Csrf-Token on writes
A script on the site's own pages, with someone signed in
Basic
Authorization: Basic and username:password in base64
Nothing that lasts: it puts a real password in a script
A Bearer value that starts with CDNA-A- is read as an OAuth token, and any other as an API key; keys have no prefix. The platform uses the first credential it finds and does not fall back, so a bad Authorization header fails even when a valid session cookie is sent too. Every credential acts as its user, with that user's permissions: the API refuses what the user could not do in the admin.
Integrations between servers
Create a backend user for the integration, in a group with only the permissions it needs.
Register an OAuth client for it, with a redirect URI you control and the refresh grant allowed.
Sign in once as that user through the authorization request with the offline scope, and exchange the code for tokens.
Store the refresh token as a secret. Before each access token expires, refresh it, and store the refresh_token from every answer: the same one comes back until its fiftieth use, which returns a new one.
A refresh token lasts 60 days. An integration that stops for longer signs in again.
When a credential is refused
The request sends
Status
Answer
No credential
401
An empty body and the header x-reason: not logged in
An unknown API key
403
Authentication failed
An expired or unknown OAuth token
403
Token doesn't exist or is not valid
bearer in lower case, or another scheme such as Token
403
Authorization method not supported; use Bearer or Basic
A key from an address it is not limited to
403
IP … not permitted for this API key
A session write without the CSRF token
403
Missing or invalid CSRF token
A 401 means no credential was found, and a 403 that one was found and refused. An expired access token is a 403, not a 401, so a client that refreshes only on 401 never refreshes: treat this 403 as the signal too.
CSRF
The token is checked when someone is signed in, the method is POST, PUT, PATCH or DELETE, and the request has no Authorization header. So it applies to writes made with the session cookie, and never to OAuth tokens or API keys. Send the session's token, _csrfToken in templates, as the X-Csrf-Token header or a _csrf form field. The cart and form submissions do not check it.
Members and backend users
A credential is a user, and the two kinds of user get different answers. A backend user, which every OAuth integration and API key should be, reads what the admin shows. A site member signed in on the site may use only the routes made for visitors: the cart, checkout, payment methods, form submission, sign-in, registration and password reset, and reading their own account. Anything else answers a member with 404 by default, so a member's refusal looks like a route that does not exist. Test with the kind of user production uses.
The site’s OAuth settings, as RFC 8414 metadata: its endpoints, grants, scopes and PKCE method. registration_endpoint appears only when the site allows clients to register themselves.
Access
Anyone. The route takes no credentials, by design.
Returns
The metadata as JSON, outside the API’s envelope.
Example
Read the OAuth configuration
GET /.well-known/oauth-authorization-server HTTP/1.1
Host: hub.coredna.com
Accept: application/json
Not called by your code: send the person’s browser here. They sign in on the site and consent, and the site redirects to redirect_uri with code and state. Check that state is the one you sent.
Access
Anyone. The route takes no credentials, by design.
Returns
A redirect to redirect_uri with code, state and iss. The code works once.
Parameters
response_typequeryrequired
string
code.
client_idqueryrequired
string
Your OAuth client’s id.
redirect_uriqueryrequired
string
One of the client’s registered redirect URIs.
scopequery
string
offline to get a refresh token. Also profile; mcp is for MCP clients.
statequeryrequired
string
A random value you check on the way back.
code_challengequeryrequired
string
The base64url SHA-256 of a random code_verifier you keep.
code_challenge_methodqueryrequired
string
S256.
Example
Ask a person to sign in
GET /oauth/authorize?response_type=code&client_id=YOUR_CLIENT_ID&redirect_uri=https://app.example.org/callback&scope=offline&state=RANDOM_STATE&code_challenge=BASE64URL_SHA256_OF_VERIFIER&code_challenge_method=S256 HTTP/1.1
Host: hub.coredna.com
Accept: application/json
Turns the code from the redirect into an access token and, with the offline scope, a refresh token. The client sends its id and secret in the body.
Access
Anyone. The route takes no credentials, by design.
Returns
The tokens as JSON, outside the API’s envelope. The access token lasts expires_in seconds: four hours.
Parameters
grant_typebodyrequired
string
authorization_code.
codebodyrequired
string
The code from the redirect.
redirect_uribodyrequired
string
The same redirect URI as in the authorization request.
client_idbodyrequired
string
Your OAuth client’s id.
client_secretbody
string
The client’s secret, for a client that has one.
code_verifierbodyrequired
string
The verifier behind the code_challenge.
Send client_id and client_secret in the body. An Authorization: Basic header is read as a user’s username and password and refused, although the metadata lists client_secret_basic. A code works once: a second exchange is refused with invalid_grant and revokes the access token the first one issued, so never retry it blindly.
Example
Exchange the code for tokens
POST /oauth/token HTTP/1.1
Host: hub.coredna.com
Accept: application/json
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=THE_CODE&redirect_uri=https://app.example.org/callback&client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET&code_verifier=THE_VERIFIER
Gets a new access token with the refresh token. The same refresh token comes back until its fiftieth use, which returns a new one, so store the one in every answer.
Access
Anyone. The route takes no credentials, by design.
Returns
A new access token and the refresh token to keep, as for the code exchange.
Parameters
grant_typebodyrequired
string
refresh_token.
refresh_tokenbodyrequired
string
The refresh token.
client_idbodyrequired
string
The client the refresh token was issued to.
client_secretbody
string
The client’s secret, for a client that has one.
Example
Refresh an access token
POST /oauth/token HTTP/1.1
Host: hub.coredna.com
Accept: application/json
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token&refresh_token=THE_REFRESH_TOKEN&client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET
Registers an OAuth client without the admin, as RFC 7591 describes, for tools that register themselves, such as MCP clients. It works only when the site allows it; most clients are registered in the admin.
Access
Anyone. The route takes no credentials, by design.
Returns
201, with the metadata, a client_id, and a client_secret for a client that has one.
Parameters
redirect_urisbodyrequired
list
The client’s redirect URIs.
grant_typesbody
list
authorization_code (the default) and refresh_token.
token_endpoint_auth_methodbody
string
client_secret_post, client_secret_basic (the default), or none for a public client with no secret.
client_namebody
string
A name shown when a person consents.
scopebody
string
The scopes it may ask for, within those the site allows.
Answers {"result":"success"} while the caller is signed in, and 401 when not. A page on the site calls it to keep a session alive, or to find out it has ended.
Access
A signed-in member, for their own records, and admin users.
Example
Check that a session is signed in
GET /cdna-api/keep-alive HTTP/1.1
Host: hub.coredna.com
Authorization: Bearer YOUR_ACCESS_TOKEN
Accept: application/json