User API: Authentication and Access Tokens
Your program turns the API client's id and secret into an access token and sends that token on every request. This step covers both, and what to do when a token ends or the client is suspended.
How it works
The User API uses the OAuth 2.0 client credentials grant. A token lasts one hour, and there is no refresh token: when a token runs out, ask for a new one the same way. The token is bound to your account and to the client's permissions and limits. Without a plan that includes API access, the token request is refused.
Token requests go to https://api.tradesteward.com/oauth/token, and API requests under /rest/v1 on the same host.
Getting an access token
Send POST /oauth/token as a form, with Content-Type: application/x-www-form-urlencoded. A JSON body is not read at this address.
| Name | Type | Required | Meaning |
|---|---|---|---|
grant_type | form field | Yes | Always client_credentials. |
Authorization | header | One of the two methods | HTTP Basic: Basic and the base64 of client_id:client_secret, each URL-encoded before joining, as the samples do. |
client_id, client_secret | form fields | One of the two methods | Both values in the form body instead. Sending both methods answers invalid_request. |
resource | form field | No | Binds the token to one address: https://api.tradesteward.com/rest/v1 for your own code. The only other accepted value, the assistants' address ending in /mcp, makes a token every /rest/v1 request refuses. Left out, the token works at both. Any other value answers invalid_target. |
There is no scope field: the permissions you chose for the client decide what it may do (see Permissions and Limits, and Safeguards for the three only API clients can hold). Scopes exist only for AI assistants (see below).
Example
POST /oauth/token HTTP/1.1
Host: api.tradesteward.com
Authorization: Basic dHNrX2FiYzEyMzp0c3NfZGVmNDU2
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
With curl:
curl -u "$TS_CLIENT_ID:$TS_CLIENT_SECRET" -d grant_type=client_credentials https://api.tradesteward.com/oauth/token
A successful answer is 200 in the OAuth token shape, not the API envelope:
{
"access_token": "tsa_9f1c...64 hex characters...",
"token_type": "Bearer",
"expires_in": 3600
}
access_token starts with tsa_: treat it as opaque and never log it. token_type is always Bearer, and expires_in is 3600 seconds. No refresh_token is returned for an API client.
When the token request is refused
A refusal is JSON with error and error_description. Build on error.
| HTTP | error | Meaning and what to do |
|---|---|---|
| 400 | invalid_request | A field is missing or malformed, or both authentication methods were sent. |
| 400 | unsupported_grant_type | Send client_credentials. |
| 400 | invalid_target | resource is neither address. Send the /rest/v1 one or leave it out. |
| 401 | invalid_client | The id or secret is wrong, the id is not an API client, or the client is suspended (see Suspension). With Basic authentication the answer carries WWW-Authenticate: Basic realm="tradeSteward". |
| 400 | invalid_grant | The secret is right but the client cannot be used: its connection was revoked or suspended, it has no active connection, or your plan no longer includes API access. The description says which. Fix the cause; the next token request then succeeds. |
| 429 | slow_down | Too many token requests, or too many failed ones. Wait the Retry-After seconds. The answer also carries the X-RateLimit-* headers. |
| 503 | temporarily_unavailable | A temporary problem, or the API is switched off for a moment. Retry after Retry-After when given, otherwise shortly. |
| 405 | invalid_request | Not a POST. The Allow header says POST. |
The token endpoint's rate limit
Token requests are limited per client and per account, so more clients do not multiply the allowance, and failed ones per client and address. A request counts once its secret checks out, so one refused for a suspended client or with invalid_grant counts too: retrying such a refusal in a loop reaches slow_down. Keep one token and replace it when it expires, never ask for one on every request, and you never meet these limits.
Sending the token
Put the token in the Authorization header of every API request:
GET /rest/v1/me HTTP/1.1
Host: api.tradesteward.com
Authorization: Bearer tsa_9f1c...
Only that header is read, never the address or a cookie, which is why the event streams cannot be opened from a browser page. A token bound to /rest/v1 is refused at any other address. Every answer carries an X-Request-Id header, the same value as requestId in its body: keep it when you report a problem.
When a request is refused for its token
A missing, malformed, unknown, revoked or expired token answers 401 with the API envelope, error.code invalid_token, and a bearer challenge:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://api.tradesteward.com/.well-known/oauth-protected-resource/rest/v1", error="invalid_token"
Content-Type: application/json
{"ok": false, "data": null, "error": {"code": "invalid_token", "message": "Missing or invalid access token.", "requestId": "..."}, "apiVersion": "rest-v1", "requestId": "..."}
The challenge names the resource metadata document. Its error="invalid_token" part is absent when no token was sent.
- On
401 invalid_token, ask for a new token and send the request again. Expiry is the usual cause; a token ended early by a rotated secret, a suspended or deleted client or a change to your sign-in settings answers the same. If the new token request is refused too, itserrorsays why. 403 entitlement_requiredwith "Your plan no longer includes API access." means the token is fine and the plan is not. Nothing in your program fixes that.- Repeated failed authentications from one address turn the 401 into
429 rate_limitedwithRetry-After. Retrying a bad token in a loop meets this limit; asking for a new token on a 401 does not.
Token lifetime and renewal
- A token lasts one hour from issue and cannot be refreshed or extended. Ask for a new one before the hour ends, or on
401 invalid_token. - A new token does not end the old one: a client may hold several, each for its hour.
- An event stream never outlives its token: one opened late in a token's life ends with the close reason
token_expired. Open the next with a new token. - A one-time chart link made with a token ends when the token does, if that comes first.
Ending a token early
Your program can end one of its own tokens with POST /oauth/revoke: a form with the field token and the same client authentication as a token request. The answer is 200 with an empty JSON object whether or not the token was live, so probing learns nothing; the token answers 401 invalid_token from then on. For a well-formed token of your client, a wrong or missing secret answers 401 invalid_client, so whoever holds a leaked access token cannot revoke it for you. No token or client_id answers 400 invalid_request. A revoke that ends nothing (a malformed, unknown, revoked or expired token) counts toward the failed-request limit, so a loop of them answers 429 slow_down. Revoking live tokens is limited too.
Rotating a client's secret, suspending it or deleting it ends all its tokens at once; see Create an API Client.
Suspension and re-enabling
A suspended API client cannot get a token: the request answers 401 invalid_client, described "This API client is suspended. Re-enable it on the Connected Apps page." Its tokens ended when it was suspended, so its requests answer 401 invalid_token. A client is suspended in two ways:
- You suspend it with Suspend on its card on Connected Apps. Programs using it stop until you re-enable it.
- Your password, passkey or two-factor settings change. Every API client of the account is suspended and every token ended, as a safety stop: whoever changed them may not be you. The card reads Suspended after a change to your sign-in methods, and Recent activity records why.
Select Re-enable on the card to bring a client back. For a client that may turn bots on or edit live bots, tradeSteward asks you to confirm it is you first. Re-enabling keeps the secret, so your program needs no change: it asks for a new token and carries on.
When your plan loses API access, every connection is suspended for that reason and token requests answer invalid_grant. Once a plan with API access is back, the next token request lifts that suspension by itself and answers a token, with nothing to re-enable.
The notices you receive
Three notices are required, arrive as an alert on the website and by email, and cannot be turned off: API Client Created, API Client Secret Replaced when a secret is rotated, and API Client Re-enabled when a suspended client is switched back on. If you did not do it yourself, suspend or delete the client on Connected Apps and review your account security. The other API notices (an approval needed, an action completed, a bot changed by a connected app) have the usual choices on the Notifications settings page.
How AI assistants authenticate
AI assistants connect to https://api.tradesteward.com/mcp using the authorization code grant with PKCE S256:
- Server metadata:
https://api.tradesteward.com/.well-known/oauth-authorization-server. Protected resource metadata:/.well-known/oauth-protected-resource/mcp(and/rest/v1) on the same host. A401from either resource names its document inWWW-Authenticate. - Authorization endpoint:
https://www.tradesteward.com/oauth/authorize, where you sign in and choose what the connection may do. Token and revocation endpoints:/oauth/tokenand/oauth/revoke, as above. - Register with a client id metadata document (a URL), or
POST /oauth/register. Agents useauthorization_codeandrefresh_tokengrants with response typecode. - Scopes
bots:read,bots:write,bots:control,backtick:read,backtick:writeandbacktick:run. The consent page shows the matching permissions.bots:controlcovers enabling bots, editing live bots and resuming trading, whilebots:writecovers creating bots, editing idle bots, disabling bots and pausing all trading. Enabling or editing live bots, pausing all trading and resuming trading require your approval each time. Disabling bots can execute immediately when the connection has permission. - An authorization code is redeemed once, within 60 seconds. A refresh token lasts 30 days and rotates on every use; a rotated one presented again 30 seconds to 7 days after its rotation ends the whole connection, and later is refused as expired. The access token lasts one hour.
- At
/mcp, a token that fails its lookup, or is bound to another address, counts toward a limit for the address it came from; past it the answer is429 rate_limitedwithRetry-After. No token, or an expired one, never counts. Requests from one address to/mcpare limited too, except from the hosted assistants' own servers.
For DCR, POST JSON to /oauth/register with redirect_uris and optional client_name. Public agents set token_endpoint_auth_method to none; confidential clients use client_secret_basic (default) or client_secret_post. 201 returns the client id, metadata and any secret. Callbacks require HTTPS, or HTTP on loopback. API/MCP access and consent are required. A registration that is never connected is removed after 30 days without a connection, and opening the consent page restarts that count. The consent page then says "This app could not be verified." (invalid_client). Remove the unconnected server from your assistant and add it again to register a new client.
To set up each assistant step by step, see Connect tradeSteward to Claude, ChatGPT or Codex.