API endpoints
Open a takedown case, get one case, and list your cases. Every field, status code and header.
All paths are relative to https://openbait.com/api/v1/ext. Every request needs Authorization: Bearer <key> (Authentication).
The Case object
Every endpoint returns cases in this shape.
{
"id": "d3a9m7f05n8u7r1m4r20",
"url": "https://example-copy.shop/checkout",
"domain": "example-copy.shop",
"status": "opened",
"createdAt": "2026-10-22T12:00:00Z",
"updatedAt": "2026-10-22T12:05:00Z",
"offlineAt": null,
"consoleUrl": "https://openbait.com/console/takedown/d3a9m7f05n8u7r1m4r20",
"blocklistOnly": false
}| Field | Type | Description |
|---|---|---|
id | string | The case ID. Use it with GET /cases/{id}; webhooks call it caseId |
url | string | The URL the case was opened for |
domain | string | The site's domain name, as plain text |
status | string | Where the case stands. One of the values in Case statuses |
createdAt | string (RFC 3339) | When the case was opened |
updatedAt | string (RFC 3339) | When the case last changed |
offlineAt | string (RFC 3339) or null | When the site was last seen going offline. null while it is online, including after it comes back (re-emerged) |
consoleUrl | string | The case page in the console, for people on your team |
blocklistOnly | boolean | true when the case was opened after the month's takedowns were used up: the site was added to the browser warning lists only, and no takedown was used. A takedown can be added on the case page in the console. false for a case with a takedown |
Case statuses
| Status | What it means |
|---|---|
opened | The case is open and the takedown is under way. The site is still online |
blocked | The site has been added to the blocklists that Chrome, Safari and Firefox use, so visitors see a warning when they open it. The site is still online and the takedown continues |
down | The site stopped loading. We keep checking it for a week in case it comes back |
watching | The site can't be reached, but its domain is still registered and could come back, so we keep checking it |
resolved | The site stayed offline for a week, the page was removed, or the case was closed. If a site that stayed offline for a week comes back, the case becomes re-emerged |
domain-removed | The domain has been deleted, so this site can't come back. The case is closed |
re-emerged | The site is loading again after being offline. The case page in the console shows whether you need to confirm it again |
A case usually moves opened → blocked → down → resolved, but it can skip steps. Only domain-removed is final: a site resolved after a week offline that comes back online becomes re-emerged; a closed case stays resolved. New statuses may be added in /v1, so handle an unknown value as "still in progress".
Open a case
POST /casesOpens a takedown case for a URL, the same as reporting a fake site in the console and confirming it. A new case uses one of the month's takedowns.
Request headers
| Header | Required | Description |
|---|---|---|
Authorization | Yes | Bearer <key> |
Content-Type | Yes | application/json |
Idempotency-Key | No | 1 to 255 characters. Makes retries safe; see Idempotency |
Request body
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
url | string | Yes | The page to take down. http or https, at most 2,048 bytes | "https://example-copy.shop/checkout" |
note | string | No | A note for your team, kept with the case. At most 1,000 characters | "Seen in a customer complaint, ticket 4521" |
The URL must be a public website. These are refused with 400 invalid_request, and the message says which:
| Reason | message |
|---|---|
| An IP address instead of a domain name | url must use a domain name, not an IP address |
| An address on a private or internal network | url points to a private or internal address |
| One of your own domains | url is on one of your own domains |
| A well-known platform (a social network, a marketplace and the like) | url is on a well-known platform and cannot be taken down as a fake site |
| A domain that doesn't exist | url's domain does not exist |
| Nothing a takedown can be filed against | url has nothing a takedown can be filed against |
curl -X POST https://openbait.com/api/v1/ext/cases \
-H "Authorization: Bearer $OPENBAIT_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: ticket-4521" \
-d '{"url": "https://example-copy.shop/checkout", "note": "Ticket 4521"}'Responses
| Status | Body | When |
|---|---|---|
201 Created | Case | A new case was opened. One takedown used |
200 OK | Case | Your organization already has an open case for this site. You get that case; no takedown is used |
400 Bad Request | Error invalid_request | A field is missing or not valid; the message names it |
402 Payment Required | Error quota_exhausted | This month's takedowns are used up. To take the site down now, report the URL in the console, then add a $199 takedown on its case page. Or wait for next month |
409 Conflict | Error idempotency_conflict | A request with this Idempotency-Key is still being processed, or the key was already used with a different body. The message says which; see Idempotency |
A 201 or 200 case with blocklistOnly: true was added to the browser warning lists only and used none of the month's takedowns (for example, an open case the console opened after the takedowns ran out).
Any endpoint can also answer 401, 403, 413, 429 or 500, and 404 not_found for an unknown path or 405 invalid_request for a method a path doesn't take. A request that takes longer than 30 seconds can get 504 Gateway Timeout without a JSON body, so check the status code and Content-Type before you parse the body. See Errors.
Idempotency
Send an Idempotency-Key so that a retry after a timeout can't open a second case.
- The key is remembered for 24 hours, per organization.
- Same key and same body: you get the first response again, with the same status code and an
Idempotent-Replayed: trueheader. Nothing new happens. - While the first request with a key is still being processed, another request with the same key gets
409 idempotency_conflict(message:a request with this Idempotency-Key is still being processed). Wait a few seconds and retry with the same key. - Same key and a different body:
409 idempotency_conflict(message:this Idempotency-Key was used with a different request body). Use a new key only for a new request. - A
5xxresponse is not kept: a retry with the same key is processed again. - Use something unique to the request, such as your ticket ID or a UUID.
Even without the header, sending the same URL twice doesn't use two takedowns: the second call returns 200 with the open case.
Get a case
GET /cases/{id}| Path parameter | Type | Description |
|---|---|---|
id | string | The case ID |
curl https://openbait.com/api/v1/ext/cases/d3a9m7f05n8u7r1m4r20 \
-H "Authorization: Bearer $OPENBAIT_API_KEY"| Status | Body | When |
|---|---|---|
200 OK | Case | |
404 Not Found | Error not_found | No case with this ID in your organization |
List cases
GET /cases?status=&limit=&cursor=Newest first.
| Query parameter | Type | Required | Description |
|---|---|---|---|
status | string | No | Only cases with this status, for example down. One of the Case statuses (awaiting_confirmation is a webhook-only value and is refused here) |
limit | integer | No | 1 to 100. Default 20 |
cursor | string | No | The nextCursor from the previous page. Leave it out for the first page |
curl "https://openbait.com/api/v1/ext/cases?status=down&limit=50" \
-H "Authorization: Bearer $OPENBAIT_API_KEY"{
"data": [
{
"id": "d3a9m7f05n8u7r1m4r20",
"url": "https://example-copy.shop/checkout",
"domain": "example-copy.shop",
"status": "down",
"createdAt": "2026-10-22T12:00:00Z",
"updatedAt": "2026-10-23T08:40:00Z",
"offlineAt": "2026-10-23T08:40:00Z",
"consoleUrl": "https://openbait.com/console/takedown/d3a9m7f05n8u7r1m4r20",
"blocklistOnly": false
}
],
"nextCursor": "eyJ0IjoiMjAyNi0xMC0yMlQxMjowMDowMFoifQ"
}| Field | Type | Description |
|---|---|---|
data | array of Case | This page of cases |
nextCursor | string or null | Pass it as cursor to get the next page. null on the last page |
The cursor is opaque: don't build or change one yourself. To poll for changes, a webhook is better than listing over and over.