Partner apps (OAuth)
Apps that act for other users: register, exchange tokens, disconnect.
List your partner apps #
/v1/partner-apps app sessionThe partner apps the signed-in user registered, oldest first, disabled ones included (disabled: true).
More about this
Client secrets are never shown again.
Errors
| HTTP | Code | When |
|---|---|---|
| 401 | unauthorized | No credential, or the credential is invalid, expired or revoked. |
| 403 | forbidden | The credential may not use this route (missing scope, app-session-only route, IP not allowed). |
| 429 | rate_limited | Too many requests for this credential (or too many failed attempts); retry after the |
| 503 | service_unavailable | A dependency is temporarily unavailable, or the feature is off on this server; retry later. |
All fields (request and response schema)
Loading fields…
curl "https://api.cirrus.trade/v1/partner-apps" \
-H "Authorization: token $CIRRUS_API_KEY"import os
import requests
response = requests.get(
"https://api.cirrus.trade/v1/partner-apps",
headers={
"Authorization": f"token {os.environ['CIRRUS_API_KEY']}",
},
timeout=10,
)
print(response.status_code, response.json())const response = await fetch('https://api.cirrus.trade/v1/partner-apps', {
method: 'GET',
headers: {
Authorization: `token ${process.env.CIRRUS_API_KEY}`,
},
});
const body = await response.json();
console.log(response.status, body);{
"status": "success",
"data": [
{
"client_id": "cp_FALC8i71s3F8cUwwozJ3",
"owner": "user-01M65VA95TDN35YPSTZ49AQXKA",
"name": "Charts Co",
"verified": false,
"logo_url": "https://partner.example/logo.png",
"redirect_uris": [
"https://partner.example/callback"
],
"scopes": [
"read",
"orders"
],
"created_at": "2026-10-30T07:27:47.011712+00:00",
"disabled": false
}
],
"error": null
}Register a partner app #
/v1/partner-apps app sessionRegisters an OAuth client owned by the signed-in user (at most 10 active apps per user).
More about this
The response carries client_secret once: store it now, it cannot be shown again. New apps are unverified.
Body fields
- name string required
Name shown to users on the consent screen (1-80 characters).
- redirect_uris string[] required
1-10 redirect URIs, matched exactly (no prefixes, no wildcards):
https://, orhttp://localhost/http://127.0.0.1for development; no fragment, at most 512 characters. - scopes string[] required
The most a user can grant the app (at least one).
One of: read, orders, triggers, strategies, strategies_live, strategies_signal, webhooks, notifications, watchlists
- logo_url string | null
HTTPS URL of the app's logo.
Errors
| HTTP | Code | When |
|---|---|---|
| 400 | malformed_json | The body is not JSON, or does not fit the request shape (unknown or missing fields, wrong types). |
| 401 | unauthorized | No credential, or the credential is invalid, expired or revoked. |
| 403 | own_session_required | Credentials can only be created from the user's own signed-in session. |
| 403 | forbidden | The credential may not use this route (missing scope, app-session-only route, IP not allowed). |
| 422 | invalid_request | The request is well-formed but not valid; |
| 422 | invalid_scope | OAuth: a requested scope is unknown or not allowed for the app. |
| 429 | rate_limited | Too many requests for this credential (or too many failed attempts); retry after the |
| 503 | service_unavailable | A dependency is temporarily unavailable, or the feature is off on this server; retry later. |
All fields (request and response schema)
Loading fields…
curl -X POST "https://api.cirrus.trade/v1/partner-apps" \
-H "Authorization: token $CIRRUS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Charts Co",
"redirect_uris": [
"https://partner.example/callback"
],
"scopes": [
"read",
"orders"
],
"logo_url": "https://partner.example/logo.png"
}'import os
import requests
response = requests.post(
"https://api.cirrus.trade/v1/partner-apps",
headers={
"Authorization": f"token {os.environ['CIRRUS_API_KEY']}",
},
json={
"name": "Charts Co",
"redirect_uris": [
"https://partner.example/callback",
],
"scopes": [
"read",
"orders",
],
"logo_url": "https://partner.example/logo.png",
},
timeout=10,
)
print(response.status_code, response.json())const response = await fetch('https://api.cirrus.trade/v1/partner-apps', {
method: 'POST',
headers: {
Authorization: `token ${process.env.CIRRUS_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "Charts Co",
"redirect_uris": [
"https://partner.example/callback"
],
"scopes": [
"read",
"orders"
],
"logo_url": "https://partner.example/logo.png"
}),
});
const body = await response.json();
console.log(response.status, body);{
"status": "success",
"data": {
"client_id": "cp_FALC8i71s3F8cUwwozJ3",
"owner": "user-01M65VA95TDN35YPSTZ49AQXKA",
"name": "Charts Co",
"verified": false,
"logo_url": "https://partner.example/logo.png",
"redirect_uris": [
"https://partner.example/callback"
],
"scopes": [
"read",
"orders"
],
"created_at": "2026-10-30T07:27:47.011712+00:00",
"disabled": false,
"client_secret": "cps_CwtJDmZDiRmYlwiK6Vysz7KlDl8VGnJ6vDK6H5avLE328NAl",
"note": "Store the client secret now: it is not shown again."
},
"error": null
}Set a partner app's webhook #
/v1/partner-apps/{client_id} app sessionSets, changes, turns off or removes the webhook of one of the user's own active apps.
More about this
For every user connected to the app it gets order_update and trade for orders the app placed, account_alert for the user's accounts (also with kind account_removed when an account is no longer linked) and connection_revoked when a connection ends. Each body also carries username (top level): the bodies are PartnerWebhookOrderUpdate, PartnerWebhookTrade, PartnerWebhookAccountAlert (kinds PartnerWebhookAlertKind) and WebhookConnectionRevoked, together PartnerWebhookBody. Signing, retries and turning off after repeated failures are as for a user's webhooks (see webhooks). Deliveries to partner apps start only once the server turns on partner webhooks; until then this route answers 503. The first webhook_url creates the webhook and returns webhook_secret once; webhook_url: null removes it. Own session only.
Parameters
- client_id path · string required
The app's client id (
cp_...).
Body fields
- webhook_url string | null
The receiver: an
https://URL on a public host (private, loopback and link-local addresses are refused). The first one creates the webhook and its secret;nullremoves the webhook. - webhook_events string[] | null
Events to send, at least one of
order_update,trade,account_alert,connection_revoked(all four when a webhook is created without this).One of: order_update, trade, account_alert, connection_revoked
- webhook_enabled boolean | null
falseturns deliveries off;trueturns them back on (also after the webhook was turned off for failing).
Errors
| HTTP | Code | When |
|---|---|---|
| 400 | malformed_json | The body is not JSON, or does not fit the request shape (unknown or missing fields, wrong types). |
| 401 | unauthorized | No credential, or the credential is invalid, expired or revoked. |
| 403 | own_session_required | Credentials can only be created from the user's own signed-in session. |
| 403 | forbidden | The credential may not use this route (missing scope, app-session-only route, IP not allowed). |
| 404 | unknown_app | Unknown or disabled partner app. |
| 409 | conflict | A partner app's webhook was changed at the same time as this request; retry. |
| 422 | invalid_request | The request is well-formed but not valid; |
| 429 | rate_limited | Too many requests for this credential (or too many failed attempts); retry after the |
| 503 | service_unavailable | A dependency is temporarily unavailable, or the feature is off on this server; retry later. |
All fields (request and response schema)
Loading fields…
curl -X PATCH "https://api.cirrus.trade/v1/partner-apps/<client_id>" \
-H "Authorization: token $CIRRUS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"webhook_url": "https://partner.example/hook",
"webhook_events": [
"order_update",
"trade",
"account_alert",
"connection_revoked"
]
}'import os
import requests
response = requests.patch(
"https://api.cirrus.trade/v1/partner-apps/<client_id>",
headers={
"Authorization": f"token {os.environ['CIRRUS_API_KEY']}",
},
json={
"webhook_url": "https://partner.example/hook",
"webhook_events": [
"order_update",
"trade",
"account_alert",
"connection_revoked",
],
},
timeout=10,
)
print(response.status_code, response.json())const response = await fetch('https://api.cirrus.trade/v1/partner-apps/<client_id>', {
method: 'PATCH',
headers: {
Authorization: `token ${process.env.CIRRUS_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"webhook_url": "https://partner.example/hook",
"webhook_events": [
"order_update",
"trade",
"account_alert",
"connection_revoked"
]
}),
});
const body = await response.json();
console.log(response.status, body);{
"status": "success",
"data": {
"client_id": "cp_nTYrRysBvVEizO0aMNCX",
"owner": "user-01M4Z65SRSKA2K7SGT33094P5V",
"name": "Charts Co",
"verified": false,
"logo_url": "https://partner.example/logo.png",
"redirect_uris": [
"https://partner.example/callback"
],
"scopes": [
"read",
"orders"
],
"created_at": "2026-10-15T07:07:11.535867+00:00",
"disabled": false,
"webhook": {
"url": "https://partner.example/hook",
"events": [
"order_update",
"trade",
"account_alert",
"connection_revoked"
],
"enabled": true,
"disabled_reason": null,
"consecutive_failures": 0,
"last_success_at": null,
"last_failure_at": null,
"previous_secret_expires_at": null,
"secret_rotated_at": null
},
"webhook_secret": "whsec_…",
"note": "Store the webhook secret now: it is not shown again."
},
"error": null
}Disable a partner app #
/v1/partner-apps/{client_id} app sessionDisables one of the user's own apps: every token issued to it stops working at once, for every user who connected it.
More about this
Cannot be undone.
Parameters
- client_id path · string required
The app's client id (
cp_...).
Errors
| HTTP | Code | When |
|---|---|---|
| 401 | unauthorized | No credential, or the credential is invalid, expired or revoked. |
| 403 | forbidden | The credential may not use this route (missing scope, app-session-only route, IP not allowed). |
| 404 | unknown_app | Unknown or disabled partner app. |
| 429 | rate_limited | Too many requests for this credential (or too many failed attempts); retry after the |
| 503 | service_unavailable | A dependency is temporarily unavailable, or the feature is off on this server; retry later. |
All fields (request and response schema)
Loading fields…
curl -X DELETE "https://api.cirrus.trade/v1/partner-apps/<client_id>" \
-H "Authorization: token $CIRRUS_API_KEY"import os
import requests
response = requests.delete(
"https://api.cirrus.trade/v1/partner-apps/<client_id>",
headers={
"Authorization": f"token {os.environ['CIRRUS_API_KEY']}",
},
timeout=10,
)
print(response.status_code, response.json())const response = await fetch('https://api.cirrus.trade/v1/partner-apps/<client_id>', {
method: 'DELETE',
headers: {
Authorization: `token ${process.env.CIRRUS_API_KEY}`,
},
});
const body = await response.json();
console.log(response.status, body);{
"status": "success",
"data": {
"client_id": "cp_FALC8i71s3F8cUwwozJ3",
"disabled": true
},
"error": null
}Rotate a partner app's client secret #
/v1/partner-apps/{client_id}/rotate-secret app sessionIssues a new client secret (shown once).
More about this
The previous secret keeps working until previous_secret_expires_at (24 hours), so the partner can roll the new one out without downtime; rotating again replaces it. Own session only.
Parameters
- client_id path · string required
The app's client id (
cp_...).
Errors
| HTTP | Code | When |
|---|---|---|
| 401 | unauthorized | No credential, or the credential is invalid, expired or revoked. |
| 403 | own_session_required | Credentials can only be created from the user's own signed-in session. |
| 404 | unknown_app | Unknown or disabled partner app. |
| 429 | rate_limited | Too many requests for this credential (or too many failed attempts); retry after the |
| 503 | service_unavailable | A dependency is temporarily unavailable, or the feature is off on this server; retry later. |
All fields (request and response schema)
Loading fields…
curl -X POST "https://api.cirrus.trade/v1/partner-apps/<client_id>/rotate-secret" \
-H "Authorization: token $CIRRUS_API_KEY"import os
import requests
response = requests.post(
"https://api.cirrus.trade/v1/partner-apps/<client_id>/rotate-secret",
headers={
"Authorization": f"token {os.environ['CIRRUS_API_KEY']}",
},
timeout=10,
)
print(response.status_code, response.json())const response = await fetch('https://api.cirrus.trade/v1/partner-apps/<client_id>/rotate-secret', {
method: 'POST',
headers: {
Authorization: `token ${process.env.CIRRUS_API_KEY}`,
},
});
const body = await response.json();
console.log(response.status, body);{
"status": "success",
"data": {
"client_id": "cp_thoAw2SKE8Q8mv2sno0m",
"client_secret": "cps_eu0PNR6P8S3lBc6aiWLr7fHtwZjdBiNSF9jmPF6zd9G1zosO",
"previous_secret_expires_at": "2026-09-30T12:02:21Z"
},
"error": null
}Rotate a partner app's webhook secret #
/v1/partner-apps/{client_id}/rotate-webhook-secret app sessionIssues a new webhook signing secret (shown once).
More about this
Deliveries are signed with both the new and the previous secret until previous_secret_expires_at (24 hours), so the receiver can switch without downtime. Own session only. Answers 503 until the server turns on partner webhooks.
Parameters
- client_id path · string required
The app's client id (
cp_...).
Errors
| HTTP | Code | When |
|---|---|---|
| 401 | unauthorized | No credential, or the credential is invalid, expired or revoked. |
| 403 | own_session_required | Credentials can only be created from the user's own signed-in session. |
| 404 | unknown_app | Unknown or disabled partner app. |
| 409 | conflict | A partner app's webhook was changed at the same time as this request; retry. |
| 422 | invalid_request | The request is well-formed but not valid; |
| 429 | rate_limited | Too many requests for this credential (or too many failed attempts); retry after the |
| 503 | service_unavailable | A dependency is temporarily unavailable, or the feature is off on this server; retry later. |
All fields (request and response schema)
Loading fields…
curl -X POST "https://api.cirrus.trade/v1/partner-apps/<client_id>/rotate-webhook-secret" \
-H "Authorization: token $CIRRUS_API_KEY"import os
import requests
response = requests.post(
"https://api.cirrus.trade/v1/partner-apps/<client_id>/rotate-webhook-secret",
headers={
"Authorization": f"token {os.environ['CIRRUS_API_KEY']}",
},
timeout=10,
)
print(response.status_code, response.json())const response = await fetch('https://api.cirrus.trade/v1/partner-apps/<client_id>/rotate-webhook-secret', {
method: 'POST',
headers: {
Authorization: `token ${process.env.CIRRUS_API_KEY}`,
},
});
const body = await response.json();
console.log(response.status, body);{
"status": "success",
"data": {
"client_id": "cp_nTYrRysBvVEizO0aMNCX",
"webhook_secret": "whsec_…",
"previous_secret_expires_at": "2026-10-16T07:07:11.542Z"
},
"error": null
}Exchange a code or refresh token for tokens #
/v1/oauth/token no authThe partner's server-to-server token endpoint (RFC 6749).
More about this
No API credential: the client authenticates with client_id + client_secret in the body or with HTTP Basic; a client registered with POST /v1/oauth/register is public and sends only client_id (its code is bound by PKCE).
grant_type=authorization_code: exchanges the code from the redirect (single use, valid 60 seconds) with the sameredirect_uriand the PKCEcode_verifier.grant_type=refresh_token: rotates the refresh token. Each refresh token works once; presenting a used one again revokes the whole connection (a theft signal), except within 30 seconds of its rotation, which returns the same new pair (so concurrent refreshes are safe).
Access tokens last one day (expires_in: 86400), refresh tokens 90 days. Responses (success and error) are not the usual envelope: success is the RFC 6749 token response, errors are {"error", "error_description"}. Every response carries Cache-Control: no-store. After too many failed client or grant checks from one source (counted with failed API-key attempts), requests get 429 slow_down with Retry-After until the window is over.
Parameters
- Authorization header · string
Optional HTTP Basic client authentication:
Basic base64(client_id:client_secret).
Body fields
- grant_type string required
authorization_codeorrefresh_token; missing or anything else isunsupported_grant_type.One of: authorization_code, refresh_token
- code string | null
authorization_code: the code from the redirect (oc_...). - redirect_uri string | null
authorization_code: the sameredirect_urithe code was issued for. - code_verifier string | null
authorization_code: the PKCE verifier (43-128 characters ofA-Z a-z 0-9 - . _ ~) whose S256 challenge was sent to authorize. - refresh_token string | null
refresh_token: the latest refresh token (pr_...). - client_id string | null
The app's client id (unless sent with HTTP Basic).
- client_secret string | null
The app's client secret (unless sent with HTTP Basic). Left out by a dynamically registered client (
token_endpoint_auth_method: none). - resource string | null
RFC 8707 resource (optional): when sent, it must be the one the user authorized (the MCP server URL), else
invalid_grant.
Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | Unreadable body or a missing field ( |
| 400 | unsupported_grant_type |
|
| 400 | invalid_grant | The code is invalid, expired or already used, was issued to another client or |
| 401 | invalid_client | No client credentials, an unknown, disabled or deleted app, a wrong secret, or a secret sent by a public client. |
| 429 | slow_down | Too many failed attempts from this source; retry after |
| 503 | temporarily_unavailable | Partner access is off on this server or its store is temporarily unavailable; retry. |
All fields (request and response schema)
Loading fields…
curl -X POST "https://api.cirrus.trade/v1/oauth/token" \
-H "Content-Type: application/json" \
-d '{
"grant_type": "authorization_code",
"code": "oc_3zb5bRr1InWe3ipR7EXinezj5kpxNWDpk9piRJNB",
"redirect_uri": "https://partner.example/callback",
"code_verifier": "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk",
"client_id": "cp_3Oq3YXcuepv3Qx5Z2iC2",
"client_secret": "cps_dG69y0yLbVBxwXqsWbWbrQk1aP0sEuT7mYw2HcN4vZ8LxJfR"
}'import os
import requests
response = requests.post(
"https://api.cirrus.trade/v1/oauth/token",
json={
"grant_type": "authorization_code",
"code": "oc_3zb5bRr1InWe3ipR7EXinezj5kpxNWDpk9piRJNB",
"redirect_uri": "https://partner.example/callback",
"code_verifier": "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk",
"client_id": "cp_3Oq3YXcuepv3Qx5Z2iC2",
"client_secret": "cps_dG69y0yLbVBxwXqsWbWbrQk1aP0sEuT7mYw2HcN4vZ8LxJfR",
},
timeout=10,
)
print(response.status_code, response.json())const response = await fetch('https://api.cirrus.trade/v1/oauth/token', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"grant_type": "authorization_code",
"code": "oc_3zb5bRr1InWe3ipR7EXinezj5kpxNWDpk9piRJNB",
"redirect_uri": "https://partner.example/callback",
"code_verifier": "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk",
"client_id": "cp_3Oq3YXcuepv3Qx5Z2iC2",
"client_secret": "cps_dG69y0yLbVBxwXqsWbWbrQk1aP0sEuT7mYw2HcN4vZ8LxJfR"
}),
});
const body = await response.json();
console.log(response.status, body);{
"access_token": "pa_skuF0MRH41ytp5qLfnrdbtWjm4mXpHG8VsfirtfNG9T25WFF",
"token_type": "Bearer",
"expires_in": 86400,
"refresh_token": "pr_X0I0VMDKFSkae48wpL4Yd6PSF7mLh12LbmwW3vHeZpcmgMf83zEOgHlj",
"scope": "read orders"
}List connected partner apps #
/v1/connections app sessionThe partner apps the user has granted access to, plus those revoked in the last 30 days (they carry revoked_at; live ones do not).
Errors
| HTTP | Code | When |
|---|---|---|
| 401 | unauthorized | No credential, or the credential is invalid, expired or revoked. |
| 403 | forbidden | The credential may not use this route (missing scope, app-session-only route, IP not allowed). |
| 429 | rate_limited | Too many requests for this credential (or too many failed attempts); retry after the |
| 503 | service_unavailable | A dependency is temporarily unavailable, or the feature is off on this server; retry later. |
All fields (request and response schema)
Loading fields…
curl "https://api.cirrus.trade/v1/connections" \
-H "Authorization: token $CIRRUS_API_KEY"import os
import requests
response = requests.get(
"https://api.cirrus.trade/v1/connections",
headers={
"Authorization": f"token {os.environ['CIRRUS_API_KEY']}",
},
timeout=10,
)
print(response.status_code, response.json())const response = await fetch('https://api.cirrus.trade/v1/connections', {
method: 'GET',
headers: {
Authorization: `token ${process.env.CIRRUS_API_KEY}`,
},
});
const body = await response.json();
console.log(response.status, body);{
"status": "success",
"data": [
{
"client_id": "cp_ZY0ZTp7e7G4JH2oGkHgr",
"app_name": "Charts Co",
"verified": false,
"dynamic": false,
"scopes": [
"read",
"orders"
],
"connected_at": "2026-10-30T14:25:07.285908+00:00",
"updated_at": "2026-10-30T14:25:07.285908+00:00",
"last_used_at": null,
"calls_today": 0
}
],
"error": null
}Disconnect a partner app #
/v1/connections/{client_id} app sessionRevokes the user's connection to the app: every access token dies at once and every refresh token is deleted.
More about this
The partner must ask the user to connect again.
Parameters
- client_id path · string required
The app's client id (
cp_...).
Errors
| HTTP | Code | When |
|---|---|---|
| 401 | unauthorized | No credential, or the credential is invalid, expired or revoked. |
| 403 | forbidden | The credential may not use this route (missing scope, app-session-only route, IP not allowed). |
| 404 | connection_not_found | No connected partner app with this client id. |
| 429 | rate_limited | Too many requests for this credential (or too many failed attempts); retry after the |
| 503 | service_unavailable | A dependency is temporarily unavailable, or the feature is off on this server; retry later. |
All fields (request and response schema)
Loading fields…
curl -X DELETE "https://api.cirrus.trade/v1/connections/<client_id>" \
-H "Authorization: token $CIRRUS_API_KEY"import os
import requests
response = requests.delete(
"https://api.cirrus.trade/v1/connections/<client_id>",
headers={
"Authorization": f"token {os.environ['CIRRUS_API_KEY']}",
},
timeout=10,
)
print(response.status_code, response.json())const response = await fetch('https://api.cirrus.trade/v1/connections/<client_id>', {
method: 'DELETE',
headers: {
Authorization: `token ${process.env.CIRRUS_API_KEY}`,
},
});
const body = await response.json();
console.log(response.status, body);{
"status": "success",
"data": {
"client_id": "cp_Ay2T4dfVLByf9ct7XzAH",
"revoked": true
},
"error": null
}What a connected app left running #
/v1/connections/{client_id}/left app sessionWhat the app client_id created for the signed-in user that is still running: its open orders, active GTTs, open v2 triggers and strategy deployments that are not stopped.
More about this
Works after the connection was revoked: disconnecting an app never cancels what it started. The app session of the owning user only. A client_id the user never connected gives empty lists.
Parameters
- client_id path · string required
The partner app's client id.
Errors
| HTTP | Code | When |
|---|---|---|
| 401 | unauthorized | No credential, or the credential is invalid, expired or revoked. |
| 403 | own_session_required | Credentials can only be created from the user's own signed-in session. |
| 429 | rate_limited | Too many requests for this credential (or too many failed attempts); retry after the |
| 503 | service_unavailable | A dependency is temporarily unavailable, or the feature is off on this server; retry later. |
All fields (request and response schema)
Loading fields…
curl "https://api.cirrus.trade/v1/connections/<client_id>/left" \
-H "Authorization: token $CIRRUS_API_KEY"import os
import requests
response = requests.get(
"https://api.cirrus.trade/v1/connections/<client_id>/left",
headers={
"Authorization": f"token {os.environ['CIRRUS_API_KEY']}",
},
timeout=10,
)
print(response.status_code, response.json())const response = await fetch('https://api.cirrus.trade/v1/connections/<client_id>/left', {
method: 'GET',
headers: {
Authorization: `token ${process.env.CIRRUS_API_KEY}`,
},
});
const body = await response.json();
console.log(response.status, body);{
"status": "success",
"data": {
"orders": [
{
"broker_tag": null,
"order_id": "250101000123",
"parent_tag": null,
"username": "user-01M66MX7P51T9S9V6RA68F15JR",
"account": "P1",
"broker": "paper",
"tradingsymbol": "RELIANCE-EQ",
"side": "BUY",
"order_type": "LIMIT",
"product": "MIS",
"quantity": 5,
"price": 2400,
"trigger_price": null,
"state": "OPEN",
"filled_qty": 0,
"average_price": null,
"status_message": null,
"broker_updated_at": null,
"created_at": "2026-10-30T14:55:03.706582Z",
"status_history": [],
"partner_client_id": "cp_9oN8crnsKGBC0epxXm9q",
"lot_size": 1,
"exchange": "NSE",
"instrument_type": "EQUITY",
"symbol": "RELIANCE",
"expiry": "",
"strike": 0,
"option_type": "",
"ltp": 2500,
"prev_close": 2490,
"instrument_token": "CT:TEST:RELIANCE",
"order_tag": "01M66MX90TMJPA92R4EKMZ55NR"
}
],
"gtt": [],
"triggers": [],
"deployments": []
},
"error": null
}A connection's limits #
/v1/connections/{client_id}/limits app sessionThe limits of the user's live connection to a partner app: what the app may trade and deploy on top of its scopes.
More about this
They bind mcp tokens (AI agents) only; classic partner tokens are not limited by them. When the user never set limits, the defaults are returned with is_default: true.
Parameters
- client_id path · string required
The app's client id.
Errors
| HTTP | Code | When |
|---|---|---|
| 401 | unauthorized | No credential, or the credential is invalid, expired or revoked. |
| 403 | own_session_required | Credentials can only be created from the user's own signed-in session. |
| 403 | forbidden | The credential may not use this route (missing scope, app-session-only route, IP not allowed). |
| 404 | connection_not_found | No connected partner app with this client id. |
| 429 | rate_limited | Too many requests for this credential (or too many failed attempts); retry after the |
| 503 | service_unavailable | A dependency is temporarily unavailable, or the feature is off on this server; retry later. |
All fields (request and response schema)
Loading fields…
curl "https://api.cirrus.trade/v1/connections/<client_id>/limits" \
-H "Authorization: token $CIRRUS_API_KEY"import os
import requests
response = requests.get(
"https://api.cirrus.trade/v1/connections/<client_id>/limits",
headers={
"Authorization": f"token {os.environ['CIRRUS_API_KEY']}",
},
timeout=10,
)
print(response.status_code, response.json())const response = await fetch('https://api.cirrus.trade/v1/connections/<client_id>/limits', {
method: 'GET',
headers: {
Authorization: `token ${process.env.CIRRUS_API_KEY}`,
},
});
const body = await response.json();
console.log(response.status, body);{
"status": "success",
"data": {
"client_id": "cp_uLZ1purc60GwZKfU8e0K",
"limits": {
"trading": true,
"accounts": [],
"paper_only": true,
"max_order_value": null,
"max_orders_per_day": 20,
"live_strategies": false,
"max_deployments": 1
},
"is_default": true,
"applies_to": [
"mcp"
]
},
"error": null
}Change a connection's limits #
/v1/connections/{client_id}/limits app sessionReplaces the limits of the user's live connection to a partner app.
More about this
Takes effect at once, on every server. Every listed account must be the user's; live_strategies needs trading, paper_only off and the strategies_live scope on the connection. max_order_value is in whole rupees. Recorded in activity with the before and after.
Parameters
- client_id path · string required
The app's client id.
Body fields
- trading boolean required
false: a read-only connection. - accounts string[] required
Accounts it may trade or deploy on (none until the user picks).
- paper_only boolean
Act only on paper-broker accounts (orders, GTT, triggers and deployments alike).
- max_order_value integer (int64) | null
Largest order value, in whole rupees.
- max_orders_per_day integer (int32) | null
Orders it may place per IST trading day.
- live_strategies boolean
May deploy live / go live (needs
paper_onlyoff). - max_deployments integer (int32) | null
Most of the user's active deployments it may have started.
Errors
| HTTP | Code | When |
|---|---|---|
| 400 | malformed_json | The body is not JSON, or does not fit the request shape (unknown or missing fields, wrong types). |
| 401 | unauthorized | No credential, or the credential is invalid, expired or revoked. |
| 403 | own_session_required | Credentials can only be created from the user's own signed-in session. |
| 403 | forbidden | The credential may not use this route (missing scope, app-session-only route, IP not allowed). |
| 404 | connection_not_found | No connected partner app with this client id. |
| 422 | invalid_request | The request is well-formed but not valid; |
| 429 | rate_limited | Too many requests for this credential (or too many failed attempts); retry after the |
| 503 | service_unavailable | A dependency is temporarily unavailable, or the feature is off on this server; retry later. |
All fields (request and response schema)
Loading fields…
curl -X PUT "https://api.cirrus.trade/v1/connections/<client_id>/limits" \
-H "Authorization: token $CIRRUS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"trading": true,
"accounts": [
"P1"
],
"paper_only": true,
"max_order_value": 10000,
"max_orders_per_day": 5,
"max_deployments": 1
}'import os
import requests
response = requests.put(
"https://api.cirrus.trade/v1/connections/<client_id>/limits",
headers={
"Authorization": f"token {os.environ['CIRRUS_API_KEY']}",
},
json={
"trading": True,
"accounts": [
"P1",
],
"paper_only": True,
"max_order_value": 10000,
"max_orders_per_day": 5,
"max_deployments": 1,
},
timeout=10,
)
print(response.status_code, response.json())const response = await fetch('https://api.cirrus.trade/v1/connections/<client_id>/limits', {
method: 'PUT',
headers: {
Authorization: `token ${process.env.CIRRUS_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"trading": true,
"accounts": [
"P1"
],
"paper_only": true,
"max_order_value": 10000,
"max_orders_per_day": 5,
"max_deployments": 1
}),
});
const body = await response.json();
console.log(response.status, body);{
"status": "success",
"data": {
"client_id": "cp_uLZ1purc60GwZKfU8e0K",
"limits": {
"trading": true,
"accounts": [
"P1"
],
"paper_only": true,
"max_order_value": 10000,
"max_orders_per_day": 5,
"live_strategies": false,
"max_deployments": 1
},
"is_default": false,
"applies_to": [
"mcp"
]
},
"error": null
}Register an OAuth client (AI agents) #
/v1/oauth/register no authDynamic client registration (RFC 7591) for AI agents connecting through the MCP server.
More about this
No credential needed. The client is public: no secret is issued; it authenticates at POST /v1/oauth/token with client_id and the PKCE code_verifier (token_endpoint_auth_method: none). It is never verified, never listed among anyone's partner apps, and may be granted at most read, orders, triggers, strategies and strategies_live. The answer says what was registered, whatever grant_types, response_types or token_endpoint_auth_method asked for; other client metadata is ignored. Redirect URIs are stored and returned as parsed (scheme and host lower-cased). client_name is printable ASCII; one naming a known AI vendor is refused unless every redirect URI other than loopback http or a known desktop app scheme is that vendor's own. Its access tokens work only through the MCP server.
A client that no user ever connected through is deleted after 24 hours, one no user is still connected through after 30 days; the token endpoint then answers invalid_client and the agent registers again. At most 10 registrations an hour per client IP (an IPv6 caller: per /64), and a cap on all registrations together per hour. Only served where agent access is enabled; elsewhere 404 with no body. Responses are not the usual envelope.
Body fields
- client_name string | null
Name shown to the user on the consent screen (printable ASCII, at most 64 characters); unverified. A known AI vendor's name is refused unless every redirect URI other than loopback http or a known desktop app scheme is that vendor's.
- redirect_uris string[]
1-5 redirect URIs:
https://(any host),http://localhost,http://127.0.0.1orhttp://[::1]on any port (the port is ignored when matching), or a known desktop app scheme. No fragment, at most 512 characters each. - grant_types string[] | null
Ignored: the client is registered for
authorization_codeandrefresh_token. - response_types string[] | null
Ignored: the client is registered for
code. - token_endpoint_auth_method string | null
Ignored: the client is public (
none).
Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | Unreadable body, |
| 404 | — | Agent access is not enabled on this server (no body). |
| 429 | slow_down | Too many registrations from this client IP (IPv6: its /64), or from all callers together this hour; retry after |
| 503 | temporarily_unavailable | The store is temporarily unavailable; retry. |
All fields (request and response schema)
Loading fields…
curl -X POST "https://api.cirrus.trade/v1/oauth/register" \
-H "Content-Type: application/json" \
-d '{
"client_name": "Claude",
"redirect_uris": [
"https://claude.ai/api/mcp/auth_callback"
],
"grant_types": [
"authorization_code",
"refresh_token"
],
"response_types": [
"code"
],
"token_endpoint_auth_method": "none"
}'import os
import requests
response = requests.post(
"https://api.cirrus.trade/v1/oauth/register",
json={
"client_name": "Claude",
"redirect_uris": [
"https://claude.ai/api/mcp/auth_callback",
],
"grant_types": [
"authorization_code",
"refresh_token",
],
"response_types": [
"code",
],
"token_endpoint_auth_method": "none",
},
timeout=10,
)
print(response.status_code, response.json())const response = await fetch('https://api.cirrus.trade/v1/oauth/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"client_name": "Claude",
"redirect_uris": [
"https://claude.ai/api/mcp/auth_callback"
],
"grant_types": [
"authorization_code",
"refresh_token"
],
"response_types": [
"code"
],
"token_endpoint_auth_method": "none"
}),
});
const body = await response.json();
console.log(response.status, body);{
"client_id": "cp_RV0iQ1LU929D23gJ8DkW",
"client_name": "Claude",
"redirect_uris": [
"https://claude.ai/api/mcp/auth_callback"
],
"token_endpoint_auth_method": "none",
"grant_types": [
"authorization_code",
"refresh_token"
],
"response_types": [
"code"
],
"client_id_issued_at": 1793364132
}OAuth authorization server metadata #
/.well-known/oauth-authorization-server no authRFC 8414 metadata for OAuth clients that discover the server themselves (AI agents connecting through the MCP server): where users consent, the token and registration endpoints, PKCE S256 only, and the scopes an agent may be granted.
More about this
No credential needed. Only served where agent access is enabled; elsewhere 404 with no body.
Errors
| HTTP | Code | When |
|---|---|---|
| 404 | — | Agent access is not enabled on this server (no body). |
All fields (request and response schema)
Loading fields…
curl "https://api.cirrus.trade/.well-known/oauth-authorization-server"import os
import requests
response = requests.get(
"https://api.cirrus.trade/.well-known/oauth-authorization-server",
timeout=10,
)
print(response.status_code, response.json())const response = await fetch('https://api.cirrus.trade/.well-known/oauth-authorization-server', {
method: 'GET',
});
const body = await response.json();
console.log(response.status, body);{
"issuer": "https://api.test.example",
"authorization_endpoint": "https://app.test.example/oauth/consent",
"token_endpoint": "https://api.test.example/v1/oauth/token",
"registration_endpoint": "https://api.test.example/v1/oauth/register",
"response_types_supported": [
"code"
],
"grant_types_supported": [
"authorization_code",
"refresh_token"
],
"code_challenge_methods_supported": [
"S256"
],
"token_endpoint_auth_methods_supported": [
"none",
"client_secret_basic",
"client_secret_post"
],
"scopes_supported": [
"read",
"orders",
"triggers",
"strategies",
"strategies_live",
"offline_access"
]
}