Skip to content

Security

The contract defines exactly two security schemes, and both carry the same secret:

SchemeHeaderNotes
ApiKeyHeaderX-API-KeyRecommended.
BearerAuthAuthorization: BearerEquivalent. Use it when a client library insists on a bearer token.

Either form resolves to the same credential. GET /v1/products is the only endpoint that accepts an unauthenticated call.

There is no IP allowlist. The per-IP rate limit is a ceiling shared by everything behind one address — it bounds abuse, it does not grant or deny access. Treat the key as the entire authority boundary for your account.

A Zelnum API key is ck_ followed by 48 characters: 51 in total. Anything shorter is not a key, which is a useful check when a secret manager mangles a value.

DoDon’t
Keep it in a secret manager or an environment variablePut it in a repository, a config file users can read, or a CI log
Send it from your serverPut it in browser JavaScript, a mobile app bundle, or a public client of any kind
Read api_key_id when you need to identify the keyPaste the key itself into a support request
Redact it in logsLog full request headers

A key in a front-end bundle is not a secret. If an integration needs to call the API from a browser, the browser should call your own server and your server should hold the key — the per-IP ceiling on unauthenticated-looking traffic will not save you.

The database stores a SHA-256 hash and a 9-character display prefix, never the value itself, so a leaked dump does not hand anyone a usable token. The flip side is that no endpoint can return your key: if you lose it, you rotate.

Terminal window
curl -X GET "https://api.zelnum.com/api/v1/api-key" \
-H "X-API-Key: YOUR_KEY_HERE"

The response carries api_key_id, masked_key, last_used and created_at — for the most recently issued active key on the account. The endpoint reports one key, so read it as “the key I would be using right now”, not as an inventory. To see every key, use the Console.

last_used is the fastest way to confirm a rotation took effect, with one caveat: it is written at most once every five minutes per key, so a key that is being used heavily can still read as untouched for up to five minutes after you switch to it. Do not conclude a deployment is broken from a stale last_used alone.

An account can hold several active keys at once, and the Console manages them individually:

Console actionEffect
CreateAdds a key. Every existing key keeps working.
RegenerateDeactivates that key and issues a replacement with the same name. The original stops working immediately.
DeleteRemoves a key outright.

That gives you two rotation shapes, and the choice is whether you can afford an overlap window.

Create a second key — the staged rotation, and the one to prefer:

  1. Create the new key in the Console. Nothing else changes; the old key still authenticates.
  2. Stage the new value everywhere it is needed — application servers, workers, cron jobs, scheduled scripts — without activating it yet.
  3. Cut over the deployments. Verify with GET /v1/api-key until it reports the new api_key_id, allowing for the five-minute last_used granularity.
  4. Delete the old key once nothing is calling with it. This is the step that actually removes the credential — a rotation is not finished until it is done.

Regenerate in place — when the old value must die now:

Use this when a key was exposed and any continued validity is a liability.

  1. Accept the gap. The old key stops working the instant you regenerate, so every component still holding it returns 401 unauthorized until it is redeployed. There is no overlap, by design.
  2. Regenerate in the Console and copy the plaintext value out of the banner immediately — it is shown there and nowhere else, ever again.
  3. Deploy the new value, then confirm nothing is still logging 401.

Every endpoint is HTTPS. There is no plaintext listener, and no endpoint accepts a downgrade, so there is no configuration in which a key travels unencrypted.

If you build a proxy or gateway in front of the API, terminate TLS at the edge and forward the X-API-Key header — do not rewrite it, and do not add it to inbound request logs.

The API’s inputs are phone numbers and email addresses — personal data in most jurisdictions. Two consequences:

  • Send only what you need. A check requires the identifier and nothing else. Do not add a user id, an internal reference, or free-text metadata to a request body: it is not stored as a field, it only widens the exposure of your own logs.
  • Know the retention windows. Realtime logs and idempotency replay results are retained for 30 days by default, and bulk result files for 7 days (result_retention_days). After those windows the records are gone, so export anything you are required to keep.

Treat the number as sensitive in your own systems too. A detection result tells an observer whether a specific person is registered on a specific platform, and the pair of identifier and verdict is more revealing than either half.

Protecting yourself against double charging

Section titled “Protecting yourself against double charging”

This is a security property as much as a billing one: Idempotency-Key makes a retry safe, so a network fault or an impatient operator cannot turn one charge into two. Never generate a fresh key to retry a request whose outcome you did not observe — see Idempotency & retries.

  • Status & support — what to include when you escalate, without leaking the key
  • Error codes — unauthorized and account_banned in context