Documentation menu

Management API

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

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

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

Reference

Group Pages
Identity, organizations, applications, ingest 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
The AI assistant assistant.md
Documentation docs.md

Tokens

Two kinds, both Authorization: Bearer:

Kind Format Acts as Reaches
Personal spm_pat_… its owner every organization that owner is a member of, with that organization's role permissions
Organization spm_org_… the organization exactly the organization it was minted 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 ingest keys and members

Deleting an organization or an application, changing members, and minting further tokens are never available to a token, at any scope: those need a console session with a fresh passkey.

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

Application references

{app} accepts either form:

/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=:

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:

{ "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.

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:

{ "error": "This token is missing the keys.read permission", "code": "forbidden" }
Code Status Meaning
bad_request 400 The query or body is invalid; error names the field
unauthorized 401 Missing, malformed, unknown, revoked or expired token
insufficient_scope 403 The token's scope is too narrow for this endpoint
forbidden 403 The bearer lacks the permission, or the module is off
not_found 404 No such organization, application or resource for this token
method_not_allowed 405 Wrong method; the Allow header lists the right ones
conflict 409 A slug or key already exists, or the resource is already in that state
payload_too_large 413 Request body over 256 KB
rate_limited 429 Over the per-token budget; see Retry-After
not_available 501 The module is not built into this deployment
internal_error 500 Quote 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:

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. An ingest key value exists only in the response that minted 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.
  • Delete organizations or applications, change members, or mint tokens.
  • Author or send campaigns. A send needs a second operator's approval and a fresh passkey; campaigns are read-only here.