Errors
The error envelope, every status code, and the reveal_code and hold_code a refused write adds.
Every refusal on this API — including a 405 and a 429 — comes back as the same JSON envelope with an HTTP status code. Nothing here ever answers an API path with an HTML page.
{
"error": {
"code": "upgrade_required",
"message": "Your reveals for this month are used up.",
"resets_at": "2026-10-01T00:00:00.000Z",
"reveal_code": "QUOTA_EXHAUSTED"
}
}error.code and error.message are always present. message is human-readable and may change. code is the stable machine token: branch on it, not on the prose.
error.code
The HTTP-level reason. These eight are the complete set, and they are enumerated in the OpenAPI document so a generated client types them as a union.
error.code | Status | When |
|---|---|---|
invalid_request | 400 | A documented parameter carried a value the API cannot honour, or a cursor it did not issue. The message names the parameter and what it accepts. |
unauthorized | 401 | Missing, malformed, unknown or revoked key. Carries WWW-Authenticate: Bearer realm="revised". |
upgrade_required | 402 | The plan does not include what was asked for: the since feed, a reveal past the monthly ceiling, a reveal of a featured listing on free, or a hold on a plan with none. |
forbidden | 403 | The write is refused by the current state of the world, not by the plan: the listing is held by another account, your hold cap is full, your own cooldown is running, or you have not revealed the listing yet. All four become possible again with time. |
not_found | 404 | No such listing or hold — or one you cannot see, which includes a listing newer than your plan’s access delay. |
method_not_allowed | 405 | The endpoint does not implement that method. Carries an Allow header. |
rate_limited | 429 | Over the per-minute ceiling. Carries Retry-After. See Rate limits. |
server_error | 500 | A fault on our side. Always reportable; safe to retry with backoff. |
Refused writes carry a second code
error.code says which HTTP category a refusal falls into. On a refused reveal or hold, that is not enough to decide what to do next — REVEAL_REQUIRED, HELD_BY_OTHER and HOLD_COOLDOWN are three completely different next actions behind the same status. So a refused write adds a specific code, and that is the one to branch on.
reveal_code
On POST /api/v1/domains/{id}/reveal.
reveal_code | Status | Meaning | What to do |
|---|---|---|---|
QUOTA_EXHAUSTED | 402 | The month’s reveal allowance is gone. Carries resets_at. | Wait until resets_at, or keep working from the metrics, which cost nothing. Retrying before then will not work. |
FEATURED_REQUIRES_PRO | 402 | A free plan against a featured listing. | Filter with tier=open, or upgrade. |
HELD_BY_OTHER | 403 | Another account is holding the listing. Carries expires_at — never who holds it. | Come back after expires_at. Only a first reveal is refused; a reveal already granted re-opens regardless. |
NOT_FOUND | 404 | No such listing, or one that is not publicly revealable (draft, blocked, delisted). | Drop the id. |
SIGN_IN_REQUIRED | 401 | No authenticated account behind the call. | Send a valid key. |
REVEAL_FAILED | 500 | An unexpected server-side failure. | The one reveal code that means “try again”. |
hold_code
On POST /api/v1/domains/{id}/hold, POST /api/v1/holds/{id}/extend and DELETE /api/v1/holds/{id}.
hold_code | Status | Meaning | What to do |
|---|---|---|---|
REVEAL_REQUIRED | 403 | You have not revealed this listing. A hold is a reservation of something you can already see. | Call POST /api/v1/domains/{id}/reveal first. A name disclosed by an unlimited plan is not a reveal on record. |
HELD_BY_OTHER | 403 | Somebody else’s hold is running. Carries expires_at. | Try again after expires_at. |
HOLD_CAP_REACHED | 403 | Every hold this plan allows is already running. Carries limit. | End one with DELETE /api/v1/holds/{id} first. |
HOLD_COOLDOWN | 403 | Your own hold on that listing ended within the last 30 days. Carries available_at. | Wait until available_at. Anybody else may hold it right now — somebody else’s ended hold never produces this code. |
HOLDS_REQUIRE_PRO | 402 | The plan allows no holds at all. | Upgrade, or work without holds. |
ALREADY_EXTENDED | 400 | A hold extends once, and this one has. | Nothing. It runs to expires_at and then ends. |
HOLD_OVER | 400 | That hold has already ended — expired, released or registered. | Nothing to extend or release. Your cooldown on the listing is running. |
HOLD_NOT_FOUND | 404 | No hold with that id, or not this account’s. | One code for both deliberately: distinguishing them would tell a stranger that a hold id exists. |
NOT_FOUND | 404 | No listing with that id, or one that has left the directory. | Drop the id. |
SIGN_IN_REQUIRED | 401 | No authenticated account behind the call. | Send a valid key. |
Extra fields
A refusal adds only the fields its code needs.
| Field | Appears on | What it is |
|---|---|---|
resets_at | QUOTA_EXHAUSTED | When the monthly reveal allowance rolls over. |
expires_at | HELD_BY_OTHER | When the listing frees up. Never who holds it. |
available_at | HOLD_COOLDOWN | When your own cooldown on that listing ends. |
limit | HOLD_CAP_REACHED | The plan’s ceiling on concurrent holds. |
Reading a refusal
import os, requests
BASE = "https://getrevised.com/api/v1"
AUTH = {"Authorization": f"Bearer {os.environ['REVISED_API_KEY']}"}
def reveal(listing_id: str) -> dict | None:
res = requests.post(f"{BASE}/domains/{listing_id}/reveal", headers=AUTH, timeout=30)
if res.ok:
return res.json()
err = res.json()["error"]
code = err.get("reveal_code", err["code"])
if code == "QUOTA_EXHAUSTED":
raise SystemExit(f"out of reveals until {err['resets_at']}")
if code == "HELD_BY_OTHER":
print(f"{listing_id}: held until {err['expires_at']}, skipping")
return None
if code == "NOT_FOUND":
print(f"{listing_id}: gone from the directory")
return None
raise RuntimeError(f"{res.status_code} {code}: {err['message']}")00aLaXEG: held until 2026-09-24T09:15:00.000Z, skippingWhat is refused and what is ignored
A parameter this API documents, carrying a value it cannot honour, is a 400. The reason is that a silently dropped filter always returns more rows than you asked for — a bigger, plausible-looking page instead of an error, with no way to find out.
curl "https://getrevised.com/api/v1/domains?spam=clean" \
-H "Authorization: Bearer $REVISED_API_KEY"{
"error": {
"code": "invalid_request",
"message": "`spam` must be one of: low, moderate."
}
}Parameters this API does not document are ignored, so analytics and cache-busting keys are safe to leave on the query string.
Two refusals worth knowing about specifically:
- An unknown
categorycode is refused, not answered with an empty page. There is no/api/v1/categoriesendpoint; read codes off thecategoryfield of a page of results. See Filters. - A cursor this endpoint did not issue is a
400— never an empty page and never a silent resume from the middle of the feed. See Pagination.
Methods and CORS
A method an endpoint does not implement is 405 method_not_allowed with an Allow header, in this same JSON envelope. Nothing here answers an API path with an HTML page.
Cross-origin calls work. Every response carries Access-Control-Allow-Origin: *, OPTIONS answers the preflight, and the rate-limit headers are exposed to script.
Retrying
| Status | Retry? |
|---|---|
| 400 | No. Fix the request. |
| 401 | No. Fix the key. |
| 402 | Only after resets_at, or after the plan changes. |
| 403 | Not immediately. expires_at or available_at says when, or free a hold slot first. |
| 404 | No. |
| 405 | No. Read the Allow header. |
| 429 | Yes, after Retry-After seconds. |
| 500 | Yes, with backoff. |
Reveals are idempotent, so a reveal whose response you lost to a network failure is safe to repeat: the repeat returns the original revealed_at and costs nothing. Holds are not idempotent — check GET /api/v1/holds before re-sending one.