Control API

Everything you can do in the dashboard — create an app, mint its keys, set a webhook secret — you can do over HTTP. An agent can run the whole loop headlessly: create an app, get a key, send events, read the numbers back.

The dg_live_ token

The control plane uses a third credential class, separate from the data-plane keys. A dg_live_ token is org-scoped: it manages every app in one organization. It is not an sk_live_ key and does not work on ingest; an sk_live_ key does not work here.

You mint the first one in the dashboard, at Organization → Control API keys. It is shown in full exactly once. Store it in a secret; there is no way to read it back. dg_test_ is reserved for a future test mode.

curl -X POST https://api.datagauge.dev/v1/apps \
  -H "Authorization: Bearer $DATAGAUGE_CONTROL_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"My SaaS"}'

Endpoints

MethodPathScopeReturns
POST/v1/appsapps:write201 app + sk_key (once)
GET/v1/appsapps:read200 app list
GET/v1/apps/{id}apps:read200 app
PATCH/v1/apps/{id}apps:write200 app
POST/v1/apps/{id}/keyskeys:write201 {key_id, key} (once)
GET/v1/apps/{id}/keyskeys:read200 masked keys
DELETE/v1/apps/{id}/keys/{key_id}keys:write204 (revoke)
PUT/v1/apps/{id}/webhooks/{provider}webhooks:write204

Rotating a key is POST a new one, then DELETE the old. The webhook endpoint is write-only: provider is stripe or polar, and the secret is never read back, here or anywhere.

Scopes

A token carries an explicit scope set, fixed when you mint it. Scopes never imply one another — apps:write does not grant keys:write. The full set:

ScopeGrants
apps:readlist and read apps
apps:writecreate apps and change their settings
keys:readlist an app's ingest keys (masked)
keys:writemint and revoke ingest keys
webhooks:writeset Stripe / Polar signing secrets
metrics:readread /v1/metrics/* for any app in the org

You can only mint a token carrying scopes your own role already holds. A wrong scope returns 403.

Key material is shown once

POST /v1/apps returns an sk_key and POST /v1/apps/{id}/keys returns a key. That is the only time you ever see the full value. Every read path returns a masked form. Lose it and the only fix is to mint a replacement and revoke the old one.

404, not 403

An app that belongs to another org, an id that never existed, and a malformed id all return the same 404 — as does a key id that belongs to a different app. This is deliberate: a 403 would confirm the id is real, which turns the API into an existence oracle. If you are sure an id is right, check that your token belongs to the org that owns the app.

The whole control plane, with request and response schemas, is in /openapi.json (OpenAPI 3.1).