OAuth
The MCP server accepts two kinds of credential.
| API key | OAuth 2.1 | |
|---|---|---|
| Who creates it | The merchant, in the dashboard | The client, automatically |
| What the merchant does | Copies a key into the client | Clicks Approve on a consent page |
| Who holds the secret | Whoever you paste it into | Nobody — the client holds a rotating token |
| Revoking | Delete the key | Settings → MCP → Disconnect |
| Best for | Agents you build, CLIs, scripts, CI | Consumer assistants: ChatGPT, Claude, anything with a "connect an app" button |
If you're wiring up your own agent, an API key is simpler and this page is optional. If you're connecting a product that expects to run an authorization flow, this is the path it will take on its own.
What the server implements
| Grant | Authorization code, public client (token_endpoint_auth_method: none) |
| PKCE | Required, S256 only — plain is rejected |
| Client registration | Dynamic (RFC 7591) — clients register themselves, no manual setup |
| Discovery | RFC 9728 protected-resource metadata + RFC 8414 authorization-server metadata |
| Refresh tokens | Rotated on every use, with replay detection |
| Revocation | RFC 7009, plus a merchant-facing disconnect in the dashboard |
| Scopes | mcp:read, mcp:write |
resource parameter | Accepted and ignored (RFC 8707) — this server's audience is fixed |
A client that speaks MCP authorization needs none of the URLs below: it reads the WWW-Authenticate header on a 401 from /mcp and discovers the rest.
Endpoints
| Purpose | Method and path |
|---|---|
| Protected-resource metadata | GET https://api.taketheme.com/.well-known/oauth-protected-resource |
| Authorization-server metadata | GET https://api.taketheme.com/.well-known/oauth-authorization-server |
| Client registration | POST https://api.taketheme.com/api/v1/oauth/register |
| Authorization (browser page) | GET https://dashboard.taketheme.com/oauth/authorize |
| Token | POST https://api.taketheme.com/api/v1/oauth/token |
| Revocation | POST https://api.taketheme.com/api/v1/oauth/revoke |
The protected-resource document is also served at /.well-known/oauth-protected-resource/mcp, for clients that insert the resource path after the well-known segment.
The flow
-
The client
POSTs to/mcpwithout a credential and gets401with:WWW-Authenticate: Bearer resource_metadata="https://api.taketheme.com/.well-known/oauth-protected-resource" -
It fetches that document, follows
authorization_serversto the authorization-server metadata, and registers itself atregistration_endpoint. Registration returns aclient_id; there is no client secret. -
It opens the
authorization_endpointin the merchant's browser withclient_id,redirect_uri,code_challenge,code_challenge_method=S256,scope, andstate. -
The merchant lands on the consent page. If they aren't signed in they sign in first and come back. The page names the client, the store, and the scopes being asked for, and does nothing until they choose. Deny returns
error=access_deniedto the client. -
On Approve, the browser returns to the client's
redirect_uriwith a single-usecode(10-minute lifetime). -
The client exchanges the code at the token endpoint with its
code_verifierand receives an access token and a refresh token.
Nothing is redirected anywhere until the redirect_uri has been confirmed against the registered client, so a tampered authorization request fails on the consent page rather than bouncing the merchant onward.
Scopes
Two scopes exist. Requesting anything else fails the authorization request.
| Scope | Grants |
|---|---|
mcp:read | READ on PRODUCTS, ORDERS, CATEGORIES, CUSTOMERS, STORE_SETTINGS, THEME, BLOGS, ANALYTICS |
mcp:write | The above plus WRITE, UPDATE, and DELETE on the same resources |
Omitting scope from the authorization request grants mcp:read. A read-only connection cannot use the builder tools — every one of them stages a change and needs mcp:write. If an assistant connects and then reports that it can read your store but not edit it, this is why: reconnect and approve write access.
Scope maps onto the same resource + action model as API key scopes, and is checked per tool call, not at the endpoint.
get_review_summary is not reachable over OAuthThe tool requires the REVIEWS scope, which neither mcp:read nor mcp:write grants. Calling it on an OAuth connection returns permission_denied. Use an API key for review data until this is corrected.
Tokens
Access token — a JWT, valid one hour, audience /mcp. It is accepted on the MCP endpoint and nowhere else: presenting one to a REST route returns 403, so a leaked token can't be walked over to the rest of the API.
Refresh token — opaque, prefixed tt_rt_, and rotated on every use. Presenting an already-rotated token is treated as theft: the whole token family is revoked immediately and the client has to re-authorize. Store only the newest one you were handed.
Tokens carry the store the merchant had active when they approved. As with API keys, storeId is never a tool argument — no prompt can point a connection at another merchant's data.
Revoking access
The merchant can disconnect any client from Settings → MCP, which kills the whole token family at once.
Clients should revoke on sign-out (RFC 7009):
curl -X POST https://api.taketheme.com/api/v1/oauth/revoke \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "token=tt_rt_YOUR_REFRESH_TOKEN" \
-d "token_type_hint=refresh_token" \
-d "client_id=YOUR_CLIENT_ID"
Revoking a refresh token drops its family. Revoking an access token denylists it for whatever is left of its hour. As the RFC requires, the endpoint answers 200 even for a token it couldn't find.
Rate limits
The OAuth endpoints are limited per IP, separately from the API's limits:
| Endpoint | Limit |
|---|---|
/oauth/register | 5 per hour |
/oauth/authorize | 10 per minute |
/oauth/token | 30 per minute |
/oauth/revoke | 20 per minute |
Registration is the tight one, and hosted assistants register from shared egress addresses. If a connector fails to set itself up with 429, wait an hour and retry.
Doing it by hand
Useful for testing a client, or for a native app that wants the flow without a library.
Register:
curl -X POST https://api.taketheme.com/api/v1/oauth/register \
-H "Content-Type: application/json" \
-d '{
"client_name": "My Agent",
"redirect_uris": ["http://127.0.0.1:8976/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"token_endpoint_auth_method": "none"
}'
Redirect URIs must be https:, a custom scheme (myapp://callback), or loopback http: (localhost, 127.0.0.1, [::1]). Up to 10 per client.
Build the PKCE pair and send the merchant to consent:
VERIFIER=$(openssl rand -base64 60 | tr -d '\n=+/' | cut -c1-64)
CHALLENGE=$(printf '%s' "$VERIFIER" | openssl dgst -sha256 -binary | openssl base64 | tr '+/' '-_' | tr -d '=')
echo "https://dashboard.taketheme.com/oauth/authorize?client_id=$CLIENT_ID\
&redirect_uri=http://127.0.0.1:8976/callback\
&code_challenge=$CHALLENGE&code_challenge_method=S256\
&scope=mcp:read%20mcp:write&state=$(openssl rand -hex 16)"
Exchange the code:
curl -X POST https://api.taketheme.com/api/v1/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "client_id=$CLIENT_ID" \
-d "code=$CODE" \
-d "redirect_uri=http://127.0.0.1:8976/callback" \
-d "code_verifier=$VERIFIER"
{
"access_token": "eyJhbGciOi...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "tt_rt_...",
"scope": "mcp:read mcp:write"
}
Then call the MCP endpoint with Authorization: Bearer <access_token> exactly as you would with an API key.
Refresh:
curl -X POST https://api.taketheme.com/api/v1/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=refresh_token" \
-d "client_id=$CLIENT_ID" \
-d "refresh_token=$REFRESH_TOKEN"
Errors
| Code | Meaning |
|---|---|
UNAUTHORIZED_CLIENT | client_id was never registered |
INVALID_GRANT | Code reused, expired, or issued to another client; redirect_uri mismatch; PKCE verification failed; refresh token invalid or replayed |
INVALID_REQUEST | code_challenge missing, or code_challenge_method isn't S256 |
INVALID_SCOPE | A scope other than mcp:read / mcp:write was requested |
STORE_REQUIRED | The approving merchant has no active store selected |
INVALID_AUDIENCE | An OAuth token was presented somewhere other than /mcp |
TOKEN_REVOKED | The token was revoked before it expired |
JWT_EXPIRED | Access token past its hour — refresh it |
Next
- MCP Server — the endpoint, the transport, and connecting each client
- Tool Reference — every tool and the scope it needs
- Scopes Reference — the resource + action model these scopes map onto