Skip to Content
Your subscription data now goes where you need it: the Curobi Developer API feeds your warehouse, ERP or CRM, and webhooks tell you the moment something changes — read the API docs →
Developer APIAuthentication & Scopes

Authentication & scopes

Every call to the Curobi Admin API carries a secret key as a bearer token.

Authorization: Bearer curobi_sk_live_7f3aQ2…

There is no other credential type on this surface. A publishable key (curobi_pk_…) belongs to the browser-safe Storefront and Customer APIs and is refused here with invalid_credential — the message does not say which kind was expected, because naming it would confirm to a probe that the key is real.

Minting a key

Keys are created in your Curobi admin on the API keys page.

Name it after the system that will use it

“Netsuite nightly sync” tells you what breaks when you revoke it. “Key 1” does not.

Grant only the scopes that system needs

Scopes are checked per endpoint. A BI pull that only reads subscriptions has no reason to hold read:customers, and no reason at all to hold write:subscriptions.

Copy the key immediately

It is shown once. Curobi stores only sha256(pepper + token), so nobody — including us — can recover the original. Lost a key? Revoke it and mint a new one.

Set an expiry if the key is for a contractor or a one-off migration

An expired key fails with credential_expired instead of quietly outliving the engagement.

A secret key is a full-power read credential for your subscription data, and one holding write:subscriptions can cancel any contract on the shop. Keep it in a secrets manager or an environment variable — never in client-side JavaScript, a mobile app bundle, a theme file, or a committed .env. Anything a browser can read, a customer can read.

What gets checked, and in what order

Six facts are checked on every single request, in this order. The order matters: the auth ladder runs before an endpoint looks at its own scope, so a downgraded shop with a fully-scoped key sees plan_upgrade_required rather than insufficient_scope.

#CheckFailure codeStatus
1A Authorization: Bearer header is presentmissing_credential401
2The key resolves, and is neither revoked nor expiredinvalid_credential · credential_revoked · credential_expired401
3Curobi is still installed on the shopshop_unavailable401
4The rate-limit bucket has a token leftrate_limited429
5The shop’s plan includes the APIplan_upgrade_required403
6The key holds the scope this endpoint requiresinsufficient_scope403

The plan is re-read on every request, cached for at most 60 seconds. A shop that downgrades from Pro loses API access within the minute, not at the next key rotation. Plan and scope are separate facts and neither substitutes for the other.

Scopes

Exact string matching. No wildcards, no hierarchy, and write: does not imply read:.

read:subscriptions read:plans read:bundles read:mixmatch read:gifts read:winback read:notifications read:analytics read:customers manage:webhooks write:subscriptions ← the only write scope that exists
ScopeUnlocks
read:subscriptionsGET /subscriptions, GET /subscriptions/{contract_gid}
read:plansGET /plans
read:bundlesGET /bundles, GET /bundles/{id}/boxes
read:mixmatchGET /mix-match, GET /mix-match/{id}
read:giftsGET /gifts
read:winbackGET /winback/campaigns, GET /winback/campaigns/{id}/sends
read:notificationsGET /notifications
read:analyticsGET /analytics/mrr, /churn, /cohorts, /recovery
read:customersCustomer names, email addresses and gift messages on the resources above
manage:webhooksThe whole /webhooks/* family
write:subscriptionsPause, resume, cancel, skip, reschedule and line edits on /subscriptions/{contract_gid}/…

write:subscriptions is off by default

It is the one scope the minting form leaves unticked, and ticking it raises a warning. Nobody ticks a box they were never shown, so a write scope that arrived on the sensible default would land on every key minted for a reporting job — the same reasoning that keeps read:customers out of the defaults, applied to the ability to end a contract.

Two consequences worth holding on to:

  • write: does not imply read:. A key holding only write:subscriptions can pause, resume, cancel, skip, reschedule and edit lines, and cannot read a single contract. That is why a write answers with a receipt rather than the resource.
  • Every subscription write requires an Idempotency-Key. Not honoured-if-present — required. A re-sent skip would move the next charge a second interval and leave nothing behind to say so.

The remaining write scopes — write:plans, write:bundles, write:mixmatch, write:gifts and write:winback — are published vocabulary, not working endpoints. They are not offered when you mint a key, because a checkbox for an endpoint that does not exist would mint keys whose stated power is fiction.

read:customers is separate on purpose

It is the only scope that returns Protected Customer Data: customer names, email addresses, and gift messages.

A reporting pull can page through every subscription, gift and notification without it — those resources are keyed by Shopify customer GID, which identifies a customer without describing them. Keep read:customers off the BI key, and the warehouse gets the full picture with none of the liability.

Without read:customers, PCD fields come back as null rather than as a "[redacted]" placeholder. That’s deliberate: paired with the scopes you know your own key holds, you can tell “there is no value” from “the value is withheld”.

Gift claim tokens are never serialized, at any scope. A claim token is a bearer credential for the recipient’s claim page — returning it would turn a merchant’s read key into the ability to accept or decline gifts on a customer’s behalf.

Revoking

Revoke a key from the API keys page at any time. The next request with it fails with credential_revoked; there is no grace period.

Uninstalling Curobi revokes every key for the shop immediately. An uninstalled merchant can’t reach the admin to revoke their own keys, so a key that outlived an uninstall would be a live credential nobody could turn off. Uninstalling also disables every webhook endpoint — revoking credentials stops other people calling us, disabling endpoints stops us calling other people.

Key hygiene

  • One key per integration. Shared keys can’t be revoked without collateral damage, and “last used” tells you nothing when four systems share one credential.
  • Rotate by overlap: mint the new key, deploy it, confirm traffic on it, then revoke the old one. Revoking first means downtime.
  • The API keys page shows each key’s prefix and last used time, so you can find the dead one before you revoke the live one. Last-used is written at most once a minute per key.
Last updated on