Revised

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.

json
{
  "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.codeStatusWhen
invalid_request400A 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.
unauthorized401Missing, malformed, unknown or revoked key. Carries WWW-Authenticate: Bearer realm="revised".
upgrade_required402The 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.
forbidden403The 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_found404No such listing or hold — or one you cannot see, which includes a listing newer than your plan’s access delay.
method_not_allowed405The endpoint does not implement that method. Carries an Allow header.
rate_limited429Over the per-minute ceiling. Carries Retry-After. See Rate limits.
server_error500A 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_codeStatusMeaningWhat to do
QUOTA_EXHAUSTED402The 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_PRO402A free plan against a featured listing.Filter with tier=open, or upgrade.
HELD_BY_OTHER403Another 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_FOUND404No such listing, or one that is not publicly revealable (draft, blocked, delisted).Drop the id.
SIGN_IN_REQUIRED401No authenticated account behind the call.Send a valid key.
REVEAL_FAILED500An 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_codeStatusMeaningWhat to do
REVEAL_REQUIRED403You 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_OTHER403Somebody else’s hold is running. Carries expires_at.Try again after expires_at.
HOLD_CAP_REACHED403Every hold this plan allows is already running. Carries limit.End one with DELETE /api/v1/holds/{id} first.
HOLD_COOLDOWN403Your 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_PRO402The plan allows no holds at all.Upgrade, or work without holds.
ALREADY_EXTENDED400A hold extends once, and this one has.Nothing. It runs to expires_at and then ends.
HOLD_OVER400That hold has already ended — expired, released or registered.Nothing to extend or release. Your cooldown on the listing is running.
HOLD_NOT_FOUND404No 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_FOUND404No listing with that id, or one that has left the directory.Drop the id.
SIGN_IN_REQUIRED401No authenticated account behind the call.Send a valid key.

Extra fields

A refusal adds only the fields its code needs.

FieldAppears onWhat it is
resets_atQUOTA_EXHAUSTEDWhen the monthly reveal allowance rolls over.
expires_atHELD_BY_OTHERWhen the listing frees up. Never who holds it.
available_atHOLD_COOLDOWNWhen your own cooldown on that listing ends.
limitHOLD_CAP_REACHEDThe plan’s ceiling on concurrent holds.

Reading a refusal

python
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']}")
Output
text
00aLaXEG: held until 2026-09-24T09:15:00.000Z, skipping

What 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.

bash
curl "https://getrevised.com/api/v1/domains?spam=clean" \
  -H "Authorization: Bearer $REVISED_API_KEY"
Response
json
{
  "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 category code is refused, not answered with an empty page. There is no /api/v1/categories endpoint; read codes off the category field 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

StatusRetry?
400No. Fix the request.
401No. Fix the key.
402Only after resets_at, or after the plan changes.
403Not immediately. expires_at or available_at says when, or free a hold slot first.
404No.
405No. Read the Allow header.
429Yes, after Retry-After seconds.
500Yes, 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.

Type to search…

↑↓ navigate openesc close