API

API keys, abilities, rate limits, pagination and error shapes.

Authentication

Every request carries an API key as a bearer token. Keys are created in the account area under Settings → API keys, with the New key button. The key is shown once, on creation, and only a hash is stored, so copy it there and then.

A key looks like 12|kX9fQ2...: a number, a pipe, then a long random string. Send all of it, number and pipe included, after Bearer.

curl https://ydomain.com/api/v1/domains \
  -H "Authorization: Bearer 12|kX9fQ2..." \
  -H "Accept: application/json"

A missing or unknown key gives 401:

{
  "message": "Unauthenticated."
}

Abilities

A key holds an explicit list of abilities. Every endpoint declares the one it needs, so a read-only key can never write, whatever it is pointed at.

Ability Grants
domains:read Read listings
domains:write Create, update and delete listings
leads:read Read leads and their message threads
leads:write Change lead status and reply to a buyer
webhooks:manage List, create and delete webhook endpoints

GET /me works with any valid key and reports the abilities the current key holds. Using an endpoint outside them gives 403:

{
  "message": "Invalid ability provided."
}

Give a key only what it needs. A script that imports listings does not need leads:read, and a reporting job needs nothing beyond the two read abilities.

Expiry and revoking

A key can be given an expiry when it is created; after that moment it is refused like an unknown key. Keys can be revoked one by one under Settings → API keys, or all at once under Settings → Security. Changing your password revokes every key as well.

Rate limits

Every response carries the usual headers:

X-RateLimit-Limit: 120
X-RateLimit-Remaining: 117

Over the limit you get 429 with a Retry-After header in seconds:

{
  "message": "Too Many Attempts."
}

Back off for that long rather than retrying immediately; retries inside the window count against the limit too.

Pagination

List endpoints are paginated. per_page defaults to 25 and is capped at 100.

curl "https://ydomain.com/api/v1/domains?per_page=2&page=2" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Accept: application/json"
{
  "data": [
    { "domain": "example.net" },
    { "domain": "example.org" }
  ],
  "links": {
    "first": "https://ydomain.com/api/v1/domains?page=1",
    "last": "https://ydomain.com/api/v1/domains?page=74",
    "prev": "https://ydomain.com/api/v1/domains?page=1",
    "next": "https://ydomain.com/api/v1/domains?page=3"
  },
  "meta": {
    "current_page": 2,
    "from": 3,
    "last_page": 74,
    "per_page": 2,
    "to": 4,
    "total": 148
  }
}

Walk a list by following links.next until it is null, rather than counting pages yourself: a listing added while you are paging shifts the offsets.

Errors

Status Means
401 No key, an unknown key, or an expired one
403 The key lacks the ability, or the record belongs to someone else
404 No such record on your account
422 The body failed validation
429 Rate limited; wait for Retry-After
5xx Our side. Retry with backoff; a POST is not automatically idempotent

A 422 names every field that failed:

{
  "message": "The extension field is required.",
  "errors": {
    "extension": ["The extension field is required."],
    "price": ["The price field must be a number."]
  }
}

Note that a record belonging to another account gives 404, not 403: the API does not confirm that an id exists elsewhere.