URL: /docs/guides/revealing-and-holds

---
title: Reveal a name, then hold it
description: Turn a masked listing into a registrable name, reserve it inside the directory, and handle every refusal the two calls can return.
---

Two write calls exist on this API. `POST /api/v1/domains/{id}/reveal` turns a masked `domain` into a real name. `POST /api/v1/domains/{id}/hold` reserves the listing inside Revised while you make up your mind. They run in that order — a hold is refused until the same account has revealed the same listing.

## Reveal

<Info>
  Reveal is the **only** call on this API that can spend. Listing never charges for the rows it matched, so you can filter and rank as much as you like before you spend anything.
</Info>

```bash
curl -s -X POST https://getrevised.com/api/v1/domains/00aLaXEG/reveal \
  -H "Authorization: Bearer $REVISED_KEY"
```

It returns the listing in the same shape `GET /api/v1/domains/{id}` does, plus `revealed_at`. The name below is a fabricated illustration — `regular` names are never published, here or anywhere else.

```json Example response (trimmed)
{
  "id": "00aLaXEG",
  "status": "active",
  "domain": "greenvalleyfarms.com",
  "tier": "regular",
  "name_disclosed": true,
  "hold_state": "none",
  "revealed_at": "2026-09-22T03:15:02.117Z",
  "availability_checked_at": "2026-09-21T18:40:11.002Z"
}
```

### When it is free

Both of these still write a ledger entry, so the reveal is recorded either way — which matters, because that record is what a hold checks:

- The listing is on the `open` tier. Its name is published on the Revised site anyway.
- Your plan has no reveal ceiling.

### It is idempotent

A repeat call returns the original `revealed_at`, costs nothing, and is the safe way to re-read a name you have already bought. You do not need to cache names defensively to avoid double-charging.

```python
def reveal(session, listing_id):
    response = session.post(
        f"https://getrevised.com/api/v1/domains/{listing_id}/reveal", timeout=30
    )
    if response.status_code >= 400:
        error = response.json()["error"]
        raise RuntimeError(f"{response.status_code} {error.get('reveal_code')}: {error['message']}")
    return response.json()
```

### Reveal refusals

Branch on `error.reveal_code`, not on the HTTP status — several codes share a status.

| `reveal_code` | Status | What to do |
| --- | --- | --- |
| `QUOTA_EXHAUSTED` | 402 | The month's allowance is gone. The error carries `resets_at`. Stop revealing until then, or upgrade. |
| `FEATURED_REQUIRES_PRO` | 402 | A free plan cannot reveal a featured listing. Skip the row or upgrade. |
| `HELD_BY_OTHER` | 403 | Another account holds it. The error carries `expires_at` — never who holds it. Retry after that, or move on. |
| `NOT_FOUND` | 404 | The listing has left the directory. Drop it from your working set. |
| `REVEAL_FAILED` | — | Transient. Retry once with backoff, then treat as a skip. |
| `SIGN_IN_REQUIRED` | 401 | The key did not resolve to an account. |

A `QUOTA_EXHAUSTED` response is worth handling properly rather than retrying:

```python
import requests

try:
    row = reveal(session, listing_id)
except RuntimeError as exc:
    if "QUOTA_EXHAUSTED" in str(exc):
        me = session.get("https://getrevised.com/api/v1/me", timeout=30).json()
        print("out of reveals until", me["reveals"]["resets_at"])
    raise
```

## Hold

A hold takes the listing off the shelf for every other account for a plan-dependent window, and masks the name for them while it runs.

<Warning>
  A hold reserves the listing **inside Revised and nothing else**. Registrability on the open internet is not guaranteed — somebody who has never heard of Revised can register the name while you hold it. A hold buys you research time, not the domain.
</Warning>

```bash
curl -s -X POST https://getrevised.com/api/v1/domains/00aLaXEG/hold \
  -H "Authorization: Bearer $REVISED_KEY"
```

```json Example response
{
  "hold": {
    "id": "hold_7f3a91c2",
    "listing_id": "00aLaXEG",
    "held_at": "2026-09-22T03:16:40.000Z",
    "expires_at": "2026-09-24T03:16:40.000Z",
    "can_extend": true,
    "extend_hours": 48
  },
  "holds": { "plan": "pro", "active": 1, "limit": 5, "hold_hours": 48 }
}
```

Taking a hold you already have is not a second hold — the existing one comes back unchanged.

The `holds` block reports this account's quota: how many holds are running, the plan's ceiling, and how long a new hold runs for. Those numbers are plan-dependent — read them from the response rather than assuming the ones above.

Holds are **account-scoped, not key-scoped**. Every key on the account sees, extends and can end the same holds. Separate keys for separate systems separate the rate-limit bookkeeping, not the holds.

### Hold refusals

Branch on `error.hold_code`. Three of these are completely different next actions and they are easy to conflate:

| `hold_code` | What happened | What to do |
| --- | --- | --- |
| `REVEAL_REQUIRED` | This account has not revealed this listing | Call reveal first, then retry the hold. |
| `HELD_BY_OTHER` | Somebody else holds it | The error carries `expires_at`. The listing is free the moment their window closes. |
| `HOLD_COOLDOWN` | **Your own** hold on this listing ended within the last 30 days | The error carries `available_at`. Another account's ended hold never blocks you — only yours does. |
| `HOLD_CAP_REACHED` | You are at the plan's ceiling | The error carries `limit`. End a hold you no longer need. |
| `HOLDS_REQUIRE_PRO` | The plan does not include holds | Upgrade, or work without them. |
| `ALREADY_EXTENDED` | The one permitted extension has been taken | Nothing. Let it run out or release it. |
| `HOLD_NOT_FOUND`, `HOLD_OVER` | The hold id is unknown, or has already ended | Re-read `GET /api/v1/holds`. |
| `NOT_FOUND` | The listing has left the directory | Drop it. |

### Extend, once

```bash
curl -s -X POST https://getrevised.com/api/v1/holds/hold_7f3a91c2/extend \
  -H "Authorization: Bearer $REVISED_KEY"
```

The window extends from where it was going to end, not from now, so extending early loses you nothing. A second attempt is refused with `ALREADY_EXTENDED`.

### End a hold

`DELETE /api/v1/holds/{id}` takes an `outcome`, defaulting to `released`:

```bash
curl -s -X DELETE https://getrevised.com/api/v1/holds/hold_7f3a91c2 \
  -H "Authorization: Bearer $REVISED_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "outcome": "registered" }'
```

```json Example response
{
  "ended_at": "2026-09-22T09:02:11.000Z",
  "outcome": "registered",
  "registration": "taken",
  "holds": { "plan": "pro", "active": 0, "limit": 5, "hold_hours": 48 }
}
```

- `released` gives the listing straight back to everyone else.
- `registered` records that you registered the name yourself. That triggers an availability re-check and retires the listing if the name now resolves. The response's `registration` field reports what the re-check found: `taken` means the name now resolves; `unknown` means no answer, **which is not the same as available**.
- `expired` is not accepted. That outcome belongs to the clock.

Either way the listing is available to other accounts immediately, and you cannot hold that same listing again for 30 days.

## Review your holds

```bash
curl -s https://getrevised.com/api/v1/holds \
  -H "Authorization: Bearer $REVISED_KEY" | jq
```

```json Example response (trimmed)
{
  "active": [
    {
      "id": "hold_7f3a91c2",
      "listing_id": "00aLaXEG",
      "domain": "greenvalleyfarms.com",
      "held_at": "2026-09-22T03:16:40.000Z",
      "expires_at": "2026-09-24T03:16:40.000Z",
      "can_extend": true,
      "extend_hours": 48
    }
  ],
  "ended": [
    {
      "id": "hold_4b18de07",
      "listing_id": "X00w46xl",
      "ended_at": "2026-09-14T22:05:00.000Z",
      "outcome": "expired",
      "expires_at": "2026-09-14T22:05:00.000Z",
      "held_at": "2026-09-12T22:05:00.000Z",
      "can_extend": false
    }
  ],
  "holds": { "plan": "pro", "active": 1, "limit": 5, "hold_hours": 48 }
}
```

Active holds carry `domain`, because a hold requires a reveal of the same listing by the same account. Ended holds carry their `outcome` — `expired`, `released` or `registered`.

<Note>
  Taking, extending or ending a hold does **not** move a listing's `updated_at`. An incremental `since` poll will not re-deliver a row whose hold state changed. Read the listing directly, or call `GET /api/v1/holds` for your own.
</Note>

## The full flow

<Steps>
  <Step title="Filter and rank">
    Page `GET /api/v1/domains`. Free, and it never charges for the rows it matched. See [Find domains](/docs/guides/finding-domains).
  </Step>
  <Step title="Reveal the shortlist, not the page">
    One reveal per listing you are seriously considering. Repeat calls are free, so you never need to cache the name to protect your quota.
  </Step>
  <Step title="Hold what you want to research">
    Refused with `REVEAL_REQUIRED` until step 2 has run for that listing on this account.
  </Step>
  <Step title="Confirm availability at a registrar">
    `availability_checked_at` is a stamp, not a live check, and a hold does not reserve the name outside Revised.
  </Step>
  <Step title="End the hold with the truth">
    `registered` if you took the name, `released` if you did not. The 30-day cooldown on your own re-hold starts either way.
  </Step>
</Steps>
