Skip to content
Cirrus home
Sign in

Reference

Partner apps (OAuth)

Apps that act for other users: register, exchange tokens, disconnect.

List your partner apps #

GET /v1/partner-apps app session

The partner apps the signed-in user registered, oldest first, disabled ones included (disabled: true).

More about this

Client secrets are never shown again.

Errors

HTTPCodeWhen
401unauthorized

No credential, or the credential is invalid, expired or revoked.

403forbidden

The credential may not use this route (missing scope, app-session-only route, IP not allowed).

429rate_limited

Too many requests for this credential (or too many failed attempts); retry after the Retry-After header (seconds; error.retry_after_ms has the exact wait).

503service_unavailable

A dependency is temporarily unavailable, or the feature is off on this server; retry later.

All fields (request and response schema)

Loading fields…

GET
curl "https://cirrus.pocketful.in/v1/partner-apps" \
  -H "Authorization: token $CIRRUS_API_KEY"
Response · 200
{
  "status": "success",
  "data": [
    {
      "client_id": "cp_NyW7jEES7vNrNQLnkQVu",
      "owner": "user-01M3PGPS02F2C5YKT5ZPGX3G7Z",
      "name": "Charts Co",
      "verified": false,
      "logo_url": "https://partner.example/logo.png",
      "redirect_uris": [
        "https://partner.example/callback"
      ],
      "scopes": [
        "read",
        "orders"
      ],
      "created_at": "2026-09-29T12:02:21.867180+00:00",
      "disabled": false
    }
  ],
  "error": null
}

Register a partner app #

POST /v1/partner-apps app session

Registers 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://, or http://localhost / http://127.0.0.1 for 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

  • logo_url string | null

    HTTPS URL of the app's logo.

Errors

HTTPCodeWhen
400malformed_json

The body is not JSON, or does not fit the request shape (unknown or missing fields, wrong types).

401unauthorized

No credential, or the credential is invalid, expired or revoked.

403own_session_required

Credentials can only be created from the user's own signed-in session.

403forbidden

The credential may not use this route (missing scope, app-session-only route, IP not allowed).

422invalid_request

The request is well-formed but not valid; field names the input.

422invalid_scope

OAuth: a requested scope is unknown or not allowed for the app.

429rate_limited

Too many requests for this credential (or too many failed attempts); retry after the Retry-After header (seconds; error.retry_after_ms has the exact wait).

503service_unavailable

A dependency is temporarily unavailable, or the feature is off on this server; retry later.

All fields (request and response schema)

Loading fields…

POST
curl -X POST "https://cirrus.pocketful.in/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"
  }'
Response · 201
{
  "status": "success",
  "data": {
    "client_id": "cp_NyW7jEES7vNrNQLnkQVu",
    "owner": "user-01M3PGPS02F2C5YKT5ZPGX3G7Z",
    "name": "Charts Co",
    "verified": false,
    "logo_url": "https://partner.example/logo.png",
    "redirect_uris": [
      "https://partner.example/callback"
    ],
    "scopes": [
      "read",
      "orders"
    ],
    "created_at": "2026-09-29T12:02:21.867180+00:00",
    "disabled": false,
    "client_secret": "cps_HGHO7T2LTif6fzJQekv73YfbEqDF3gxxxiuB447BQgenTFyH",
    "note": "Store the client secret now: it is not shown again."
  },
  "error": null
}

Set a partner app's webhook #

PATCH /v1/partner-apps/{client_id} app session

Sets, 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; null removes 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

    false turns deliveries off; true turns them back on (also after the webhook was turned off for failing).

Errors

HTTPCodeWhen
400malformed_json

The body is not JSON, or does not fit the request shape (unknown or missing fields, wrong types).

401unauthorized

No credential, or the credential is invalid, expired or revoked.

403own_session_required

Credentials can only be created from the user's own signed-in session.

403forbidden

The credential may not use this route (missing scope, app-session-only route, IP not allowed).

404unknown_app

Unknown or disabled partner app.

409conflict

A partner app's webhook was changed at the same time as this request; retry.

422invalid_request

The request is well-formed but not valid; field names the input.

429rate_limited

Too many requests for this credential (or too many failed attempts); retry after the Retry-After header (seconds; error.retry_after_ms has the exact wait).

503service_unavailable

A dependency is temporarily unavailable, or the feature is off on this server; retry later.

All fields (request and response schema)

Loading fields…

PATCH
curl -X PATCH "https://cirrus.pocketful.in/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"
    ]
  }'
Response · 200
{
  "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 #

DELETE /v1/partner-apps/{client_id} app session

Disables 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

HTTPCodeWhen
401unauthorized

No credential, or the credential is invalid, expired or revoked.

403forbidden

The credential may not use this route (missing scope, app-session-only route, IP not allowed).

404unknown_app

Unknown or disabled partner app.

429rate_limited

Too many requests for this credential (or too many failed attempts); retry after the Retry-After header (seconds; error.retry_after_ms has the exact wait).

503service_unavailable

A dependency is temporarily unavailable, or the feature is off on this server; retry later.

All fields (request and response schema)

Loading fields…

DELETE
curl -X DELETE "https://cirrus.pocketful.in/v1/partner-apps/<client_id>" \
  -H "Authorization: token $CIRRUS_API_KEY"
Response · 200
{
  "status": "success",
  "data": {
    "client_id": "cp_NyW7jEES7vNrNQLnkQVu",
    "disabled": true
  },
  "error": null
}

Rotate a partner app's client secret #

POST /v1/partner-apps/{client_id}/rotate-secret app session

Issues 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

HTTPCodeWhen
401unauthorized

No credential, or the credential is invalid, expired or revoked.

403own_session_required

Credentials can only be created from the user's own signed-in session.

404unknown_app

Unknown or disabled partner app.

429rate_limited

Too many requests for this credential (or too many failed attempts); retry after the Retry-After header (seconds; error.retry_after_ms has the exact wait).

503service_unavailable

A dependency is temporarily unavailable, or the feature is off on this server; retry later.

All fields (request and response schema)

Loading fields…

POST
curl -X POST "https://cirrus.pocketful.in/v1/partner-apps/<client_id>/rotate-secret" \
  -H "Authorization: token $CIRRUS_API_KEY"
Response · 200
{
  "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 #

POST /v1/partner-apps/{client_id}/rotate-webhook-secret app session

Issues 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

HTTPCodeWhen
401unauthorized

No credential, or the credential is invalid, expired or revoked.

403own_session_required

Credentials can only be created from the user's own signed-in session.

404unknown_app

Unknown or disabled partner app.

409conflict

A partner app's webhook was changed at the same time as this request; retry.

422invalid_request

The request is well-formed but not valid; field names the input.

429rate_limited

Too many requests for this credential (or too many failed attempts); retry after the Retry-After header (seconds; error.retry_after_ms has the exact wait).

503service_unavailable

A dependency is temporarily unavailable, or the feature is off on this server; retry later.

All fields (request and response schema)

Loading fields…

POST
curl -X POST "https://cirrus.pocketful.in/v1/partner-apps/<client_id>/rotate-webhook-secret" \
  -H "Authorization: token $CIRRUS_API_KEY"
Response · 200
{
  "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 #

POST /v1/oauth/token no auth

The 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.

  • grant_type=authorization_code: exchanges the code from the redirect (single use, valid 60 seconds) with the same redirect_uri and the PKCE code_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_code or refresh_token; missing or anything else is unsupported_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 same redirect_uri the code was issued for.

  • code_verifier string | null

    authorization_code: the PKCE verifier (43-128 characters of A-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).

Errors

HTTPCodeWhen
400invalid_request

Unreadable body or a missing field (code, redirect_uri, code_verifier, refresh_token).

400unsupported_grant_type

grant_type missing or not authorization_code / refresh_token.

400invalid_grant

The code is invalid, expired or already used, was issued to another client or redirect_uri, or code_verifier does not match; the refresh token is invalid, already used, expired, or the user revoked the connection.

401invalid_client

No client credentials, an unknown or disabled app, or a wrong secret.

429slow_down

Too many failed attempts from this source; retry after Retry-After seconds.

503temporarily_unavailable

Partner access is off on this server or its store is temporarily unavailable; retry.

All fields (request and response schema)

Loading fields…

POST
curl -X POST "https://cirrus.pocketful.in/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"
  }'
Response · 200
{
  "access_token": "pa_skuF0MRH41ytp5qLfnrdbtWjm4mXpHG8VsfirtfNG9T25WFF",
  "token_type": "Bearer",
  "expires_in": 86400,
  "refresh_token": "pr_X0I0VMDKFSkae48wpL4Yd6PSF7mLh12LbmwW3vHeZpcmgMf83zEOgHlj",
  "scope": "read orders"
}

List connected partner apps #

GET /v1/connections app session

The partner apps the user has granted access to (live connections only).

Errors

HTTPCodeWhen
401unauthorized

No credential, or the credential is invalid, expired or revoked.

403forbidden

The credential may not use this route (missing scope, app-session-only route, IP not allowed).

429rate_limited

Too many requests for this credential (or too many failed attempts); retry after the Retry-After header (seconds; error.retry_after_ms has the exact wait).

503service_unavailable

A dependency is temporarily unavailable, or the feature is off on this server; retry later.

All fields (request and response schema)

Loading fields…

GET
curl "https://cirrus.pocketful.in/v1/connections" \
  -H "Authorization: token $CIRRUS_API_KEY"
Response · 200
{
  "status": "success",
  "data": [
    {
      "client_id": "cp_Ay2T4dfVLByf9ct7XzAH",
      "app_name": "Charts Co",
      "verified": false,
      "scopes": [
        "read",
        "orders"
      ],
      "connected_at": "2026-09-29T12:02:21.837659+00:00",
      "updated_at": "2026-09-29T12:02:21.837659+00:00"
    }
  ],
  "error": null
}

Disconnect a partner app #

DELETE /v1/connections/{client_id} app session

Revokes 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

HTTPCodeWhen
401unauthorized

No credential, or the credential is invalid, expired or revoked.

403forbidden

The credential may not use this route (missing scope, app-session-only route, IP not allowed).

404connection_not_found

No connected partner app with this client id.

429rate_limited

Too many requests for this credential (or too many failed attempts); retry after the Retry-After header (seconds; error.retry_after_ms has the exact wait).

503service_unavailable

A dependency is temporarily unavailable, or the feature is off on this server; retry later.

All fields (request and response schema)

Loading fields…

DELETE
curl -X DELETE "https://cirrus.pocketful.in/v1/connections/<client_id>" \
  -H "Authorization: token $CIRRUS_API_KEY"
Response · 200
{
  "status": "success",
  "data": {
    "client_id": "cp_Ay2T4dfVLByf9ct7XzAH",
    "revoked": true
  },
  "error": null
}