Machine credentials
A machine credential authenticates a program rather than a person. Use one when your own code talks to the MCP server: a scheduled job, a backend service, or an agent runtime you host yourself. There is no browser login and no consent screen - your service exchanges a client_id and client_secret for an access token whenever it needs one.
If instead a person is connecting Claude, ChatGPT or another MCP host, use the browser flow described in Connecting. Machine credentials are not for that, and MCP hosts cannot use them.
Getting a credential
Machine credentials are issued per contract by your account manager. You receive a client_id and a client_secret; the secret is shown once, at creation, and cannot be recovered afterwards - store it in whatever your service already uses for secrets.
Ask for one credential per system rather than one per person. Usage is attributed per credential, so separate credentials for, say, your nightly job and your production API let you see the two apart in your usage breakdown, cap them separately, and revoke one without disturbing the other.
Requesting a token
Exchange the credential at the token endpoint with HTTP Basic authentication:
POST https://dataflow.modularfinance.com/oauth/token
| Parameter | Value |
|---|---|
grant_type | client_credentials |
resource | https://dataflow.modularfinance.com/mcp |
scope | Optional. Defaults to everything your credential allows. |
curl -sS -u "$CLIENT_ID:$CLIENT_SECRET" \
-d grant_type=client_credentials \
-d resource=https://dataflow.modularfinance.com/mcp \
https://dataflow.modularfinance.com/oauth/tokenThe response is a standard OAuth token response:
{
"access_token": "eyJhbGciOiJFZERTQSIsImtpZCI6…",
"token_type": "Bearer",
"expires_in": 900,
"scope": "dataflow:entities.search dataflow:ownership.read",
"resource": "https://dataflow.modularfinance.com/mcp"
}
resource is required. It identifies the MCP server the token is for (RFC 8707) and must match https://dataflow.modularfinance.com/mcp exactly. Most OAuth client libraries do not send it unless told to; if you get invalid_target, this is why.
Using the token
Send the token as a bearer on the MCP endpoint. Any MCP client library works - point it at the endpoint and give it a static bearer token instead of an OAuth flow. Over plain HTTP the call looks like this:
curl -sS https://dataflow.modularfinance.com/mcp \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call",
"params":{"name":"search_securities","arguments":{"query":"Volvo"}}}'Do not send an Origin header. The MCP endpoint accepts it only from browser origins we have allowlisted, and rejects anything else with 403 origin not allowed. Server-side HTTP clients do not set it; some browser and edge runtimes do.
Token lifetime and rate limits
Tokens last 15 minutes and there is no refresh token: when one expires, request another the same way. Cache the token for its lifetime and re-request on expiry or on a 401.
- The token endpoint accepts 60 requests per minute per IP. A service that fetches a fresh token for every tool call will hit this.
- The MCP endpoint accepts 120 requests per minute per credential.
Both return 429 when exceeded. Back off and retry.
Scopes
Without a scope parameter you get everything the credential is allowed and your contract entitles you to. Pass scope to request less - useful when a particular service should only reach part of the data:
scope=dataflow:ownership.read dataflow:short.read
Granted scopes are always intersected with your organization's entitlement at the moment the token is minted, so the scope in the response is what you actually hold. If a scope is added to or removed from your contract, the next token reflects it - nothing to reconnect. Asking only for scopes your organization does not have returns invalid_scope. See OAuth & scopes for the full list.
Rotating the secret
Ask your account manager to rotate. You receive the new secret immediately, and the secret it replaces keeps working for a 7-day overlap so you can deploy without an outage. After the overlap it stops.
If a secret has leaked, say so when you ask. The old secret can be retired the moment the new one is issued, and a single compromised secret can be revoked on its own - your credential keeps working on the replacement, and the client_id does not change.
Revoking
Any of these can be arranged through your account manager, from narrowest to broadest:
| Action | Effect |
|---|---|
| Revoke one secret | That secret stops authenticating. The credential keeps working on its others. |
| Revoke one token | A specific issued access token stops working immediately. |
| Revoke the credential | No new tokens are issued. Tokens already issued keep working until they expire, at most 15 minutes. |
Errors
| Error | Meaning |
|---|---|
invalid_client | Wrong client_id or client_secret, a revoked credential, or the wrong authentication method - Basic and form-body are fixed per credential, and only the registered one is accepted. |
invalid_target | resource is missing or does not match the MCP endpoint exactly. |
invalid_scope | The requested scope is outside what your credential or your contract allows. |
unauthorized_client | MCP is not enabled for your organization, or the credential is not a machine credential. |
401 with invalid_token on /mcp | The token expired or was revoked. Request a new one. |
forbidden_scope | The token does not carry the scope that tool requires. |
quota_exceeded | The monthly credit cap is reached; see Credits & pricing. |
403 origin not allowed | Your client sent an Origin header. Remove it. |
