API keys
Every Qariyb integration is issued a matched pair of keys from a single organization: a publishable key for the browser and a secret key for your server.
- Publishable key (
pk_live_…) authenticates the public widget API (/api/v1/public/*) — safe to embed in the browser, like Stripe's publishable keys. Sent in theX-Widget-Keyheader. - Secret key (
sk_live_…) authenticates the REST API (/api/v1/*) — server-side only, keep it private. Sent in theAuthorization: Bearerheader (orX-Secret-Key).
Both keys are scoped to a single organization and can only access that organization's events, checkout, and RSVP data.
API keys are managed exclusively from the organizer dashboard under Developer → API keys. There is no programmatic API for creating, rotating, or deleting them yet.
Key format
Each integration has one publishable key and one secret key, created together. Keys are always live. Test mode was removed: there are no pk_test_ / sk_test_ variants, and a key using those prefixes is rejected on format.
| Key | Format | Where it goes |
|---|---|---|
| Publishable | pk_live_ + 32 random chars (40 in total) | X-Widget-Key header on public widget requests |
| Secret | sk_live_ + 48 random chars (56 in total) | Authorization: Bearer header on REST API requests |
The publishable key travels in the X-Widget-Key header on every public request:
curl https://api.qariyb.com/api/v1/public/events/01HWX9KPQR \
-H "X-Widget-Key: pk_live_..." \
-H "Accept: application/json"
The secret key travels in the Authorization header on server-to-server REST API requests:
curl https://api.qariyb.com/api/v1/events \
-H "Authorization: Bearer sk_live_..." \
-H "Accept: application/json"
The secret key is shown once at creation and never again — Qariyb stores only a SHA-256 hash. If lost, rotate the key to issue a new secret. The publishable key can be revealed again at any time.
Managing keys
Keys live under Developer → API keys in the organizer dashboard. Each key carries:
- Name
name- Type
- string
- Description
Human-readable label shown in the dashboard.
- Name
description- Type
- string?
- Description
Optional free-text description.
- Name
is_active- Type
- boolean
- Description
When false, every request using the key returns
602 API_KEY_INACTIVE. Toggle from the dashboard to disable a key without deleting it.
- Name
restrict_domains- Type
- boolean
- Description
When
true, publishable-key requests are rejected unless theOrigin(orReferer) host matchesallowed_domains. Whenfalse, no origin check runs.
- Name
allowed_domains- Type
- string[] | null
- Description
List of allowed hostnames. Wildcards supported:
*.example.commatchesapp.example.comandexample.com. Hostnames are case-insensitive and the request port is stripped before matching.
- Name
rate_limit_per_minute- Type
- integer
- Description
Per-key read rate limit (default
120). The checkout/RSVP write limit is exactly half this value, rounded — 60/min at the default.
- Name
expires_at- Type
- datetime?
- Description
Optional expiration. When in the past, the key returns
603 API_KEY_EXPIRED.
- Name
last_used_at- Type
- datetime?
- Description
Updated by the
ValidateApiKey/ValidateSecretKeymiddleware on every successful request (recorded after the response is sent, so it never adds request latency).
- Name
total_requests- Type
- integer
- Description
Incremented on every successful request.
- Name
checkout_count- Type
- integer
- Description
Incremented only on
/checkout/*requests.
Domain restrictions
Domain restrictions apply to the publishable key, since it is the key exposed in the browser. When restrict_domains is enabled, the request must include either an Origin or Referer header whose host matches an entry in allowed_domains. Examples:
| Pattern | Matches | Does not match |
|---|---|---|
example.com | example.com | app.example.com, evil.com |
*.example.com | app.example.com, staging.example.com, example.com | evil-example.com |
localhost | localhost | localhost.evil.com |
A failing match returns 604 API_KEY_ORIGIN_DENIED with HTTP 403.
Rotation
Rotate a key when:
- The secret may have been exposed.
- A team member with access leaves.
- An embedding partner is removed.
Rotation invalidates the previous secret immediately and issues a new one. The publishable key remains the same, so existing widgets keep working — only the server-side secret needs to be updated.
Errors
See the error reference for the full code table. Common failures on the public widget API:
| Code | HTTP | Cause |
|---|---|---|
600 API_KEY_MISSING | 401 | No X-Widget-Key header or widget_key query parameter |
601 API_KEY_INVALID | 401 | Key not found, or not in pk_live_… format |
602 API_KEY_INACTIVE | 401 | is_active = false |
603 API_KEY_EXPIRED | 401 | expires_at in the past |
604 API_KEY_ORIGIN_DENIED | 403 | Origin not in allowed_domains |
605 API_KEY_RATE_LIMITED | 429 | Exceeded rate_limit_per_minute (or its checkout sub-limit) |
The secret key used by the server-to-server REST API reports its own failures as
606 SECRET_KEY_MISSING and 607 SECRET_KEY_INVALID. Unlike the publishable
key, it does not distinguish unknown from revoked from expired — that
distinction would let the endpoint be used to probe which secret keys exist.