Skip to main content

Errors and refusals

The shape​

Every answer that is not a card has ok: false and an error object:

{
"ok": false,
"error": {
"code": "unknown_key",
"message": "That key is not one of ours.",
"docs": "https://www.sniffopotamus.dev/partners/errors#unknown_key"
}
}
FieldWhat it is
error.codeA short, stable code from the list below. Write your code against this.
error.messageA plain-English sentence for your logs. It may be reworded, so never match on it.
error.docsA link to this page's explanation of the code.
error.resets_atover_allowance only: when your monthly allowance comes back, in UTC.
statusin_review and not_found only: the same word as error.code, kept so code written for the first version still works.

Every refusal is sent with Cache-Control: no-store. Never keep a refusal as though it were a card.

Answers from before 29 September 2026​

The first version of the door sent error as a plain word ("error": "unknown_key"), and sent in_review and not_found with only a status field. This reads both:

function refusalCode(body) {
if (body && typeof body.error === "object") return body.error.code;
return body?.error ?? body?.status ?? "unavailable";
}

Every code​

CodeHTTPCosts a call?Retry?In short
no_key401NoNo, fix itNo key was sent
unknown_key401NoNo, fix itThe key is not one of ours
revoked401NoNo, fix itThe key was stopped
suspended403NoNo, contact usYour business record is suspended
not_entitled403NoNo, contact usThe partner API is not switched on for your business
over_allowance429NoWhen the month turns overThis month's cards are used up
rate_limited429NoAfter Retry-AfterA ceiling was reached
invalid_id400NoNo, fix itThat is not a fragrance id
in_review200NoLaterThe perfume is held back while it is checked
not_found404NoNoWe hold no public perfume with that id
card_failed500NoYes, shortlyWe could not build the card
unavailable503NoYes, shortlySomething on our side could not be checked
use_key_tool410NoNoThe admin screen no longer creates keys
keys_by_code409NoNoNew keys are handed out by Sniffopotamus for now

Only a card that is served costs a call, so no refusal ever does.

no_key​

401. The request had no Authorization: Bearer <key> header, or it was not in that form.

What to do: send the header exactly as Authorization: Bearer sniffo_pk_…. Check that your server actually read the secret: an empty environment variable sends Bearer with nothing after it. Each of these counts toward the failed attempts allowed from your network address (30 a minute).

unknown_key​

401. The key is not one we issued: a typo, a missing character, a space or a line break copied with it, or a key from another service.

What to do: copy the key again into your server's secret. Do not retry in a loop: each attempt counts toward the 30 failed attempts a minute your network address is allowed, and after that every request from that address waits, even ones with a good key. If you have lost the key, create a new one in the Builder Console; we store only a fingerprint of it and cannot show it again.

revoked​

401. The key was stopped, by your business's owner or by us. A stopped key is refused from its very next request.

What to do: use your current key, or have the owner create a new one in the Builder Console and put it in your server's secret. See Getting started.

suspended​

403. Your business record is suspended.

What to do: email sniff@sniffopotamus.com. Meanwhile keep serving stored copies up to 7 days old, as on Limits and caching.

not_entitled​

403. Your key is valid, but the partner API is not switched on for your business. A key on its own never grants access; the switch does. Today that switch sits on the owner's own Sniffopotamus account, so this can also mean the key belongs to a business whose owner's account has changed.

What to do: email us.

over_allowance​

429. Your business has used this month's allowance of cards. It comes back at 00:00 UTC on the first day of next month, which error.resets_at also gives.

What to do: stop asking for cards until then and keep serving your stored copies up to 7 days old. whoami and capabilities are free, so you can check where you stand. If this happens every month, your allowance is too small; tell us.

rate_limited​

429. One of the ceilings was reached:

CeilingLimitCounted per
Failed key attempts (no_key, unknown_key)30 a minuteyour network address
Card requests600 a minuteyour business
Card requests50,000 in any 24 hours (a sliding window, not a calendar day)your business
whoami and capabilities together60 a minuteyour business
Requests carrying one key1,200 a minutethat key

The last is set above anything your business's own ceilings let you reach, so you only meet it if a copy of your key is being used somewhere else. Rotate the key if you do.

What to do: wait the number of seconds in the Retry-After header, then carry on. The X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset headers say where you stand. For the 24-hour ceiling, Retry-After counts down to when enough of the requests from 24 hours earlier have fallen out of the window; it is not "tomorrow". If you meet the minute ceiling in normal use, you are probably asking for the same card again and again: keep a stored copy. See Limits and caching.

invalid_id​

400. The id in the address is not a fragrance id. A fragrance id is a UUID, such as 03c188b7-74aa-469d-ae40-caa8c45673b7: the last part of the perfume's address on sniffopotamus.com.

What to do: check where your code gets the id from. This is free.

in_review​

200, not an error status. We hold this perfume, but it is held back while something in it is checked, so none of its data is given out. It is the same answer our own app gives.

{
"ok": false,
"status": "in_review",
"error": {
"code": "in_review",
"message": "This fragrance is still being checked, so its card is not shown yet.",
"docs": "https://www.sniffopotamus.dev/partners/errors#in_review"
}
}

What to do: drop any stored copy you hold for this id at once, and show your own page without the card. Ask again another day. Free.

Today every live perfume in the catalogue is public (checked 29 September 2026), so no id answers in_review at the moment. Your code must still handle it, because the catalogue team can hold a perfume back at any time.

not_found​

404. We hold no public perfume with that id. The id may be wrong, or the perfume may have been removed or joined into another record.

What to do: drop any stored copy at once, show your page without the card, and flag the link on your side for a person to check. Do not ask again for the same id in a loop. Free.

card_failed​

500. Your key was fine, but we could not build the card. Ours, not yours.

What to do: keep serving a stored copy up to 7 days old if you have one, and try again in a minute or so. Nothing was charged.

unavailable​

503. We could not check your key, or could not count the call against your allowance, because something on our side did not answer. Ours, not yours. It is never a sign that your key is wrong.

What to do: retry after a short wait, backing off if it keeps happening, and keep serving stored copies up to 7 days old meanwhile. Nothing was charged. If it lasts more than a few minutes, capabilities tells you whether card reads are switched on for accounts.

use_key_tool​

410. The admin screen no longer creates or replaces keys. New keys are created by Sniffopotamus with its own key tool and stored in a secure keychain on our Mac, so a key is never shown in a web browser.

What to do: nothing on your side. This answer is for our own team, and a partner's server should not meet it. If you need a new key, email sniff@sniffopotamus.com and we will arrange it with you.

keys_by_code​

409. New keys are handed out by Sniffopotamus for now, so the owner screen cannot create one for you.

What to do: email sniff@sniffopotamus.com to ask for a key. You can still stop a key at once from the owner screen if you think it has been seen by someone it should not have been.

Testing each answer​

Use these to see each answer from your own server:

To seeAsk for
A card03c188b7-74aa-469d-ae40-caa8c45673b7 (Casafutura)
not_found00000000-0000-4000-8000-000000000000, a well-formed id we do not hold
invalid_idnot-a-fragrance-id
no_keyany card, with no Authorization header
unknown_keyany card, with Authorization: Bearer sniffo_pk_test
in_reviewno id answers it today (see in_review); test your handling with a stub that returns the example above

Keep the unknown_key and no_key tests to a handful: they count toward your address's 30 failed attempts a minute.

Not partner codes​

Two answers come from the web server rather than the partner door, and have no error.code:

  • 404 with a web page: the address is wrong. Check the path, for example /api/partner/v1/fragrances/<id>/card.
  • 405: only GET is answered. Do not POST.

Telling us about a problem​

Email sniff@sniffopotamus.com with the time (with its time zone), the address you called, the HTTP status and the error.code. Never send your key.

Back to SniffopotamusReturn to the app