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

CredentialStarts withLifetimeUse
API tokenpe_Until revokedBearer token for /api/mcp
OAuth access tokenpeo_1 hourBearer token for /api/mcp
OAuth refresh tokenpeor_90 days from issue, replaced on every useTrade for a new token pair
Authorization codeNo prefix10 minutes, one useTrade for the first token pair
OAuth client idpec_Does not expireIdentifies a registered client

Send either token the same way:

Header
Authorization: Bearer pe_exampleEXAMPLEexampleEXAMPLEexampleEXAMPLE0

Production 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):

Response header
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.

Request
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"]
  }'
201 Created
{
  "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"
}
FieldRules
redirect_urisRequired. 1 to 10 URIs, each up to 2,000 characters, from the allowed list below.
client_nameOptional. Shown on the approval screen. Up to 100 characters; defaults to "An AI assistant".
grant_typesOptional. Only authorization_code and refresh_token are accepted.

Allowed redirect URIs

  • Loopback over http on any port: http://localhost, http://127.0.0.1 or http://[::1] (RFC 8252). Use this for desktop and command-line clients.
  • Claude's callback: https://claude.ai/api/mcp/auth_callback or https://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

Node: make the PKCE pair
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");
Authorization URL
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
ParameterRules
response_typeRequired. code.
client_idRequired. A registered client id.
redirect_uriRequired. One of the client's registered URIs.
code_challengeRequired. 43 characters, base64url SHA-256 of the verifier.
code_challenge_methodRequired. S256.
scopeOptional. read or read estimate. Absent means read.
stateOptional, and recommended. Up to 1,000 characters, returned unchanged.
resourceOptional. 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:

Approved
http://127.0.0.1:8765/callback?code=<code>&state=<your state>&iss=https%3A%2F%2Fproduction-engine.com

A 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

Request
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
200 OK
{
  "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

Request
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_exampleEXAMPLEexample000

Every 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

ScopeGrants
readEvery read-only tool. Always included.
estimateAdds 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 scope only as read, or left out. Anything else returns invalid_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.