v1
Self-service
One-time token

Project Onboarding

Register a new partner project with a single API call. Your account manager issues a one-time enrollment token; your bootstrap script exchanges it for OAuth credentials and a webhook signing secret in one round-trip.

Flow at a glance
Two HTTP calls. No dashboard logins. Credentials returned exactly once.
1. Admin issues enrollment token (TTL 24h, single-use).
2. Partner: POST /api/v1/enroll → receives client_id, client_secret, webhook signing metadata (JWKS URL — no shared secret).
3. Partner stores OAuth secret, verifies test webhook against JWKS, then POST /api/v1/enroll/confirm.
4. Project is
active
— proceed via standard OAuth2 client_credentials flow.

Step 1 — Register

Authorization is the one-time token, sent as a Bearer header. Body fields:

curl -X POST https://sunrift-hub.com/api/v1/enroll \
  -H "Authorization: Bearer ent_<ONE_TIME_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Sunrift.kz",
    "webhook_url": "https://sunrift.kz/api/hub/webhook",
    "contact_email": "ops@sunrift.kz",
    "requested_scopes": ["qr:create","identity:read","identity:write","ledger:read","webhooks:receive"],
    "payment_code": "SR",
    "payment_purpose_template": "SR-{{ID}}-K00",
    "environment": "production"
  }'

Response (HTTP 201):

{
  "ok": true,
  "data": {
    "project": { "id": "uuid", "slug": "sunrift-kz", "name": "Sunrift.kz" },
    "oauth": {
      "client_id": "ck_live_xxx",
      "client_secret": "cs_live_xxx",          // shown ONCE — store immediately
      "environment": "production",
      "scopes": ["qr:create","identity:read","identity:write","ledger:read","webhooks:receive"],
      "token_endpoint": "https://sunrift-hub.com/oauth/token"
    },
    "webhook": {
      "url": "https://sunrift.kz/api/hub/webhook",
      "signature_alg": "ed25519",
      "signature_header": "X-Hub-Signature",
      "signature_kid_header": "X-Hub-Signature-Kid",
      "signature_timestamp_header": "X-Hub-Signature-Timestamp",
      "signed_message_format": "${X-Hub-Signature-Timestamp}.${raw_body}",
      "delivery_id_header": "X-Hub-Delivery",
      "event_header": "X-Hub-Event",
      "verification": {
        "jwks_url": "https://sunrift-hub.com/api/public/jwks.json",
        "notes": "Fetch JWKS (cache 5 min), pick JWK by kid, verify Ed25519(public_key, `${ts}.${raw_body}`, base64url_decode(sig)). Reject if |now-ts| > 5 min."
      }
    },
    "defaults": { "bank_account_id": "uuid|null", "qr_template_id": "uuid|null" },
    "next_step": "POST /api/v1/enroll/confirm with HTTP Basic auth (client_id:client_secret)..."
  }
}
Important: client_secret is returned exactly once — persist it into your secret store immediately. The Hub keeps only a bcrypt hash and cannot recover it later.
Webhook signatures are Ed25519 over ${ts}.${raw_body}; verify them with the public keys served at /api/public/jwks.json (cache 5 min, pick the JWK whose kid matches X-Hub-Signature-Kid). No shared secret to store, lose, or rotate.

Step 2 — Confirm

Once your service has stored the credentials and (optionally) verified an inbound test webhook, mark the project as active using HTTP Basic auth with the new credentials:

curl -X POST https://sunrift-hub.com/api/v1/enroll/confirm \
  -u "ck_live_xxx:cs_live_xxx"

Step 3 — Use the API

From now on, follow the standard OAuth2 client_credentials flow. Tokens are short-lived (~1h); cache them and refresh on 401.

curl -X POST https://sunrift-hub.com/oauth/token \
  -u "ck_live_xxx:cs_live_xxx" \
  -d "grant_type=client_credentials&scope=qr:create identity:write"

Error responses