SaaS Pro Max
Sign inStart free
Documentation menu

Management API

One bearer token reads and writes everything the console shows: applications, public and secret keys, analytics, events, funnels, people, errors, metrics, feature flags, revenue, destinations, campaigns, and Ask.

Base URL: https://saaspro.dev/api/v1. Machine-readable description: openapi.json, also served live at GET /api/v1/openapi.json without authentication.

Shell
curl -H "Authorization: Bearer spm_pat_xxxxxxxx" \
  https://saaspro.dev/api/v1/orgs

Reference#

Group Pages
Full feature discovery and approved commands agent-access.md
Identity, organizations, applications, public and secret keys applications.md
Analytics, events, funnels, goals analytics.md
People and segments people.md
Error tracking errors.md
Metrics, uptime, alerts, incidents metrics.md
Feature flags flags.md
Revenue revenue.md
Ad destinations destinations.md
Campaigns communications.md
Ask assistant.md
Documentation docs.md

Tokens#

Two kinds, both Authorization: Bearer:

KindFormatActs asReaches
Personalspm_pat_…its ownerevery organization that owner is a member of, with that organization's role permissions
Organizationspm_org_…the organizationexactly the organization it was created in, with the developer role

Create one in the console: Organization → Access tokens, or Account → Personal access tokens. The value is shown exactly once; only a sha256 hash, a 12-character prefix and the last four characters are stored.

A personal token stops working the moment its owner loses the membership, and either kind stops the moment it is revoked or its expiry passes.

Scopes#

A scope caps a token; it never widens it. The effective permission set is always the intersection of the bearer's role permissions and the token's scopes.

Scope Grants
read every .read permission the bearer holds
write read, plus creating and editing module data
admin write, plus public and secret keys and members

Protected actions, including deletion, membership and token management, use the shared command API. They require a member token with the appropriate scope and an exact, one-time approval from a fresh passkey session. Approval never expands the member's or token's permissions.

A token can never be more capable than the member who created it — an operator who cannot rotate keys cannot create an admin token that can.

Application references#

{app} accepts either form:

Text
/api/v1/apps/1f0c…/events                     # application id
/api/v1/apps/saaspromax/saaspro-dev/events    # orgSlug/appSlug

GET /orgs/{org}/apps returns both. {org} accepts a slug or an id.

Environments#

App-scoped reads take ?env=production|staging|development (an environment id also works). Production is the default. An unknown value is a 400, never a silent fallback.

Ranges#

Analytics and revenue endpoints take ?range=:

Text
24h  7d  30d  90d  12m  custom:2026-08-01..2026-08-31

Days are bucketed in the application's timezone, not yours and not the server's, so a "day" means the same thing here as it does in the console. Metrics use their own vocabulary: 1h, 6h, 24h, 7d, 30d, 90d.

Pagination#

List endpoints answer:

JSON
{ "data": [ … ], "nextCursor": "eyJrIjoi…" }

A cursor is opaque — pass it back unchanged as cursor (or before, where the endpoint says so) and stop when nextCursor is null. Never construct one. limit is 1–200 and defaults per endpoint.

Shell
curl -H "Authorization: Bearer $SPM_TOKEN" \
  "https://saaspro.dev/api/v1/apps/acme/site/events?limit=50&cursor=$CURSOR"

Errors#

Every failure is the same shape:

JSON
{ "error": "This token is missing the keys.read permission", "code": "forbidden" }
CodeStatusMeaning
bad_request400The query or body is invalid; error names the field
unauthorized401Missing, malformed, unknown, revoked or expired token
insufficient_scope403The token's scope is too narrow for this endpoint
forbidden403The bearer lacks the permission, or the module is off
not_found404No such organization, application or resource for this token
method_not_allowed405Wrong method; the Allow header lists the right ones
conflict409A slug or key already exists, or the resource is already in that state
payload_too_large413Request body over 256 KB
rate_limited429Over the per-token budget; see Retry-After
not_available501The module is not built into this deployment
internal_error500Quote the X-Request-Id when reporting it

An organization or application the token cannot reach answers 404, not 403: membership is never leaked through a different status code.

Rate limits#

120 requests per minute, per token. Every response carries:

Text
RateLimit-Limit: 120
RateLimit-Remaining: 118
RateLimit-Reset: 47

and a 429 additionally carries Retry-After in seconds. The budget is per token, so a noisy script does not starve an interactive session.

Request ids#

Every response carries X-Request-Id. Send your own to have it echoed back — useful for stitching a client trace to a platform log.

Auditing#

Every write through the API lands in the append-only audit ledger as an action by the person the token belongs to, with metadata.via = "management-api" and the token's id. A revoked token's blast radius is therefore always answerable.

What the API deliberately does not do#

  • Return a secret twice. A public or secret key value exists only in the response that created it. GET /apps/{app}/keys returns prefixes.
  • Show raw personal data without the permission. Emails and names are masked unless the bearer holds people.pii.read.
  • Bypass human identity or domain approvals. Protected actions use the command approval flow. Campaigns retain their second-operator requirements.