Developers
Authentication
Every call to the MCP server carries a bearer token. It is either a workspace API token, made by an admin in Production Engine, or an OAuth access token, issued when a person signs in and approves a connection.
Token types
| Credential | Starts with | Lifetime | Use |
|---|---|---|---|
| API token | pe_ | Until revoked | Bearer token for /api/mcp |
| OAuth access token | peo_ | 1 hour | Bearer token for /api/mcp |
| OAuth refresh token | peor_ | 90 days from issue, replaced on every use | Trade for a new token pair |
| Authorization code | No prefix | 10 minutes, one use | Trade for the first token pair |
| OAuth client id | pec_ | Does not expire | Identifies a registered client |
Send either token the same way:
Authorization: Bearer pe_exampleEXAMPLEexampleEXAMPLEexampleEXAMPLE0Production Engine stores only a SHA-256 hash of each token and code, so a lost token cannot be shown again. Make a new one instead.
Who a token acts as
A token acts as the owner or admin who made or approved it, in that person's workspace only. The workspace is read from the token, never from the request. On every call Production Engine checks again that:
- the person is still an active member of the workspace,
- their role is still owner or admin, and
- the workspace's plan is in good standing: active, trialing, or past due within the grace period.
If any check fails, the call gets a 401. Removing someone or lowering their role cuts off their tokens and connections at once, without a separate revoke.
API tokens
API tokens are for MCP clients that take a header instead of a sign-in, such as Claude Code or Cursor. An owner or admin makes one in Company Settings, then Integrations, under API tokens for developers.
- The token is shown once, when it is made.
- Tokens are read-only. They never list the estimate tools.
- A workspace holds at most 25 live tokens. Names are up to 80 characters.
- The list shows each token's first characters, who made it and when it was last used (updated at most every 5 minutes).
- Revoke a token from the same list. It stops working on the next call.
OAuth 2.1
Production Engine is its own OAuth 2.1 authorization server, following the MCP authorization spec. Clients are public: there is no client secret, and PKCE with S256 is required. Claude and ChatGPT run this flow on their own; you only need it to build a client of your own.
1. Discover the server
A call to /api/mcp without a token returns a 401 whose challenge names the protected-resource metadata (RFC 9728):
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="production-engine", resource_metadata="https://production-engine.com/.well-known/oauth-protected-resource/api/mcp"That document names the authorization server, whose metadata (RFC 8414) lists the endpoints below. Both are on the HTTP endpoints page.
2. Register a client
Dynamic client registration (RFC 7591) records a name and redirect URIs. It grants no access by itself.
curl -X POST https://production-engine.com/api/oauth/register \
-H "Content-Type: application/json" \
-d '{
"client_name": "My production tool",
"redirect_uris": ["http://127.0.0.1:8765/callback"]
}'{
"client_id": "pec_exampleEXAMPLEexample000",
"client_id_issued_at": 1790000000,
"client_name": "My production tool",
"redirect_uris": ["http://127.0.0.1:8765/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"token_endpoint_auth_method": "none",
"scope": "read estimate"
}| Field | Rules |
|---|---|
redirect_uris | Required. 1 to 10 URIs, each up to 2,000 characters, from the allowed list below. |
client_name | Optional. Shown on the approval screen. Up to 100 characters; defaults to "An AI assistant". |
grant_types | Optional. Only authorization_code and refresh_token are accepted. |
Allowed redirect URIs
- Loopback over http on any port:
http://localhost,http://127.0.0.1orhttp://[::1](RFC 8252). Use this for desktop and command-line clients. - Claude's callback:
https://claude.ai/api/mcp/auth_callbackorhttps://claude.com/api/mcp/auth_callback, exactly. - ChatGPT's connector callback.
Any other address is refused with invalid_redirect_uri. To connect a hosted web app, contact us.
3. Send the person to approve
import { createHash, randomBytes } from "node:crypto";
// 43 to 128 characters from A-Z a-z 0-9 . _ ~ -
const codeVerifier = randomBytes(32).toString("base64url");
const codeChallenge = createHash("sha256").update(codeVerifier).digest("base64url");https://production-engine.com/oauth/authorize
?response_type=code
&client_id=pec_exampleEXAMPLEexample000
&redirect_uri=http%3A%2F%2F127.0.0.1%3A8765%2Fcallback
&code_challenge=<code_challenge>
&code_challenge_method=S256
&scope=read
&state=<random value>
&resource=https%3A%2F%2Fproduction-engine.com%2Fapi%2Fmcp| Parameter | Rules |
|---|---|
response_type | Required. code. |
client_id | Required. A registered client id. |
redirect_uri | Required. One of the client's registered URIs. |
code_challenge | Required. 43 characters, base64url SHA-256 of the verifier. |
code_challenge_method | Required. S256. |
scope | Optional. read or read estimate. Absent means read. |
state | Optional, and recommended. Up to 1,000 characters, returned unchanged. |
resource | Optional. If sent, it must be https://production-engine.com/api/mcp. |
A parameter may appear only once. The person signs in if needed, sees which workspace the client will read and what it may do, and clicks Allow or Don't allow. An invalid request is shown on Production Engine's own page and never redirected. After a choice, the browser returns to your redirect URI:
http://127.0.0.1:8765/callback?code=<code>&state=<your state>&iss=https%3A%2F%2Fproduction-engine.comA declined request returns error=access_denied with state and iss. Check that iss is https://production-engine.com and that state matches.
4. Trade the code for tokens
curl -X POST https://production-engine.com/api/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d grant_type=authorization_code \
-d code=<code> \
-d client_id=pec_exampleEXAMPLEexample000 \
-d redirect_uri=http://127.0.0.1:8765/callback \
-d code_verifier=<code_verifier> \
-d resource=https://production-engine.com/api/mcp{
"access_token": "peo_exampleEXAMPLEexampleEXAMPLEexampleEXAMPLE0",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "peor_exampleEXAMPLEexampleEXAMPLEexampleEXAMPLE0",
"scope": "read"
}The token endpoint takes a form-encoded or JSON body up to 16 KB. The code works once and for 10 minutes. Presenting a used code again ends the connection it created.
Refresh and rotation
curl -X POST https://production-engine.com/api/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d grant_type=refresh_token \
-d refresh_token=peor_exampleEXAMPLEexampleEXAMPLEexampleEXAMPLE0 \
-d client_id=pec_exampleEXAMPLEexample000Every refresh returns a new access token and a new refresh token, and the refresh token you sent stops working. Store the new one before you use the access token.
Replay guard: if a refresh token that was already exchanged is presented again, Production Engine assumes two parties hold copies of the connection and ends it. The response is invalid_grant, and the person has to connect again. Never retry a refresh with the old token after a network failure without first checking whether the new pair arrived.
A refresh token expires 90 days after it was issued. A refresh also fails if the approving person is no longer an owner or admin, or the plan is no longer in good standing.
Scopes
| Scope | Grants |
|---|---|
read | Every read-only tool. Always included. |
estimate | Adds start_estimate and get_estimate. Starting an estimate writes a draft brief and estimate and uses the workspace's AI allowance. |
- Ask for scopes on the authorization request. A scope Production Engine does not offer is refused.
- The token endpoint accepts
scopeonly asread, or left out. Anything else returnsinvalid_scope. The granted scope comes back in the token response. - ChatGPT connections are always
read, whatever they ask for. - API tokens are always
read. - A connection approved without estimates never gains them. Connect again to add them.
Disconnecting
Owners and admins see every connection in Company Settings, then Integrations, with who connected it and when it was last used. Disconnect ends it at once. There is no public revocation endpoint.