Skip to main content

OAuth

The MCP server accepts two kinds of credential.

API keyOAuth 2.1
Who creates itThe merchant, in the dashboardThe client, automatically
What the merchant doesCopies a key into the clientClicks Approve on a consent page
Who holds the secretWhoever you paste it intoNobody — the client holds a rotating token
RevokingDelete the keySettings → MCP → Disconnect
Best forAgents you build, CLIs, scripts, CIConsumer 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

GrantAuthorization code, public client (token_endpoint_auth_method: none)
PKCERequired, S256 only — plain is rejected
Client registrationDynamic (RFC 7591) — clients register themselves, no manual setup
DiscoveryRFC 9728 protected-resource metadata + RFC 8414 authorization-server metadata
Refresh tokensRotated on every use, with replay detection
RevocationRFC 7009, plus a merchant-facing disconnect in the dashboard
Scopesmcp:read, mcp:write
resource parameterAccepted 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

PurposeMethod and path
Protected-resource metadataGET https://api.taketheme.com/.well-known/oauth-protected-resource
Authorization-server metadataGET https://api.taketheme.com/.well-known/oauth-authorization-server
Client registrationPOST https://api.taketheme.com/api/v1/oauth/register
Authorization (browser page)GET https://dashboard.taketheme.com/oauth/authorize
TokenPOST https://api.taketheme.com/api/v1/oauth/token
RevocationPOST 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

  1. The client POSTs to /mcp without a credential and gets 401 with:

    WWW-Authenticate: Bearer resource_metadata="https://api.taketheme.com/.well-known/oauth-protected-resource"
  2. It fetches that document, follows authorization_servers to the authorization-server metadata, and registers itself at registration_endpoint. Registration returns a client_id; there is no client secret.

  3. It opens the authorization_endpoint in the merchant's browser with client_id, redirect_uri, code_challenge, code_challenge_method=S256, scope, and state.

  4. 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_denied to the client.

  5. On Approve, the browser returns to the client's redirect_uri with a single-use code (10-minute lifetime).

  6. The client exchanges the code at the token endpoint with its code_verifier and 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.

ScopeGrants
mcp:readREAD on PRODUCTS, ORDERS, CATEGORIES, CUSTOMERS, STORE_SETTINGS, THEME, BLOGS, ANALYTICS
mcp:writeThe 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 OAuth

The 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:

EndpointLimit
/oauth/register5 per hour
/oauth/authorize10 per minute
/oauth/token30 per minute
/oauth/revoke20 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

CodeMeaning
UNAUTHORIZED_CLIENTclient_id was never registered
INVALID_GRANTCode reused, expired, or issued to another client; redirect_uri mismatch; PKCE verification failed; refresh token invalid or replayed
INVALID_REQUESTcode_challenge missing, or code_challenge_method isn't S256
INVALID_SCOPEA scope other than mcp:read / mcp:write was requested
STORE_REQUIREDThe approving merchant has no active store selected
INVALID_AUDIENCEAn OAuth token was presented somewhere other than /mcp
TOKEN_REVOKEDThe token was revoked before it expired
JWT_EXPIREDAccess token past its hour — refresh it

Next