Trawl docs
API reference

Trawl API.

AI web extraction as an async job API. Every endpoint, request, and response — generated from the live OpenAPI spec.

Base URL

https://api.trawl.productcraft.co

Auth

Authorization: Bearer …

Spec version

OpenAPI 3.0.0 · v0.1.0

Webhooks

get/v1/workspaces/{workspace_id}/webhooks/event-typesAuth

List the events an endpoint can subscribe to.

Each type with a plain description, its group and an example `data` object (what "Send test" delivers).

Path parameters

workspace_id*string

Response · 200

data*array

Example

Request

GET /v1/workspaces/{workspace_id}/webhooks/event-types
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "type": "job.succeeded",
      "description": "A user signed up or was created by an admin.",
      "category": "Users",
      "example": {}
    }
  ]
}
get/v1/workspaces/{workspace_id}/webhooks/endpointsAuth

List webhook endpoints, oldest first.

At most 10, so this is always one page. Signing secrets are never returned here, only their last 4 characters.

Path parameters

workspace_id*string

Response · 200

data*array
pagination*object

Always one page (an endpoint limit applies).

Example

Request

GET /v1/workspaces/{workspace_id}/webhooks/endpoints
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "id": "00000000-0000-0000-0000-000000000000",
      "scope_type": "workspace",
      "scope_id": "string",
      "url": "https://api.acme.com/webhooks",
      "description": "Production receiver",
      "event_types": [],
      "enabled": false,
      "status": "enabled",
      "disabled_at": "2026-01-01T00:00:00.000Z",
      "disabled_reason": "manual",
      "consecutive_failures": 0,
      "first_failure_at": "2026-01-01T00:00:00.000Z",
      "signing_secret_hint": "x9Q=",
      "prev_signing_secret_active": false,
      "prev_signing_secret_expires_at": "2026-01-01T00:00:00.000Z",
      "legacy_signature": false,
      "created_at": "2026-01-01T00:00:00.000Z",
      "updated_at": "2026-01-01T00:00:00.000Z"
    }
  ],
  "pagination": {}
}
post/v1/workspaces/{workspace_id}/webhooks/endpointsAuth

Add a webhook endpoint. Returns its signing secret once.

Deliveries are signed with the Standard Webhooks headers `webhook-id`, `webhook-timestamp` and `webhook-signature` (`v1,<base64 HMAC-SHA256 of "<id>.<timestamp>.<body>">`); the returned `whsec_…` secret works unchanged with any Standard Webhooks library. Failed deliveries are retried after 1 min, 5 min, 30 min, 2 h, 6 h and 24 h.

Path parameters

workspace_id*string

Headers

Idempotency-Keystring

Opaque client-generated string (1-256 chars, `[A-Za-z0-9_-:]`) that makes a retry of this request safe for 24 hours. The same key with a different body returns 409 `IDEMPOTENCY_KEY_REUSE`.

Request body

url*string · uri

Where to send events: an absolute https:// URL on a public host.

Example: "https://api.acme.com/webhooks"

descriptionstring

Example: "Production receiver"

event_typesarray

Event types from `GET …/webhooks/event-types`. Empty or omitted means every event, including types added later.

Example: ["job.succeeded","job.failed"]

enabledboolean

Create it switched off.

Response · 201

id*string · uuid
scope_type*string

What owns the endpoint: `workspace`, `app` or `community`.

Example: "workspace"

scope_id*string

Id of the owning workspace, app or community.

url*string · uri

Example: "https://api.acme.com/webhooks"

description*string

Example: "Production receiver"

event_types*array

Subscribed event types. Empty means every event, including types added later.

Example: []

enabled*boolean

The on/off switch (`PATCH … { enabled }`).

status*enum (3)

`enabled` receives events. `disabled` was switched off. `auto_disabled` was switched off after 20 failed deliveries in a row or 24 hours of failures; `PATCH { enabled: true }` turns it back on.

disabled_at*string · date-time
disabled_reason*enum (2)
consecutive_failures*number

Failed automatic deliveries in a row; a success resets it.

first_failure_at*string · date-time

Start of the current failure streak.

signing_secret_hint*string

Last 4 characters of the signing secret.

Example: "x9Q="

prev_signing_secret_active*boolean

True while the previous secret still signs too (after a rotation, for its grace period).

prev_signing_secret_expires_at*string · date-time

Until then the previous secret signs too (after a rotation).

legacy_signature*boolean

True for endpoints carried over from the product's previous webhook system: they also receive the old signature header until its sunset date.

created_at*string · date-time
updated_at*string · date-time
signing_secret*string

The signing secret, shown once: pass it unchanged to any Standard Webhooks verifier. Store it now; it cannot be shown again.

Example: "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw"

Example

Request

POST /v1/workspaces/{workspace_id}/webhooks/endpoints
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "url": "https://api.acme.com/webhooks",
  "description": "Production receiver",
  "event_types": [
    "job.succeeded",
    "job.failed"
  ],
  "enabled": false
}

Response

{
  "id": "00000000-0000-0000-0000-000000000000",
  "scope_type": "workspace",
  "scope_id": "string",
  "url": "https://api.acme.com/webhooks",
  "description": "Production receiver",
  "event_types": [],
  "enabled": false,
  "status": "enabled",
  "disabled_at": "2026-01-01T00:00:00.000Z",
  "disabled_reason": "manual",
  "consecutive_failures": 0,
  "first_failure_at": "2026-01-01T00:00:00.000Z",
  "signing_secret_hint": "x9Q=",
  "prev_signing_secret_active": false,
  "prev_signing_secret_expires_at": "2026-01-01T00:00:00.000Z",
  "legacy_signature": false,
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z",
  "signing_secret": "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw"
}
get/v1/workspaces/{workspace_id}/webhooks/endpoints/{endpoint_id}Auth

Get one webhook endpoint (without its secret).

Path parameters

workspace_id*string
endpoint_id*string · uuid

Response · 200

id*string · uuid
scope_type*string

What owns the endpoint: `workspace`, `app` or `community`.

Example: "workspace"

scope_id*string

Id of the owning workspace, app or community.

url*string · uri

Example: "https://api.acme.com/webhooks"

description*string

Example: "Production receiver"

event_types*array

Subscribed event types. Empty means every event, including types added later.

Example: []

enabled*boolean

The on/off switch (`PATCH … { enabled }`).

status*enum (3)

`enabled` receives events. `disabled` was switched off. `auto_disabled` was switched off after 20 failed deliveries in a row or 24 hours of failures; `PATCH { enabled: true }` turns it back on.

disabled_at*string · date-time
disabled_reason*enum (2)
consecutive_failures*number

Failed automatic deliveries in a row; a success resets it.

first_failure_at*string · date-time

Start of the current failure streak.

signing_secret_hint*string

Last 4 characters of the signing secret.

Example: "x9Q="

prev_signing_secret_active*boolean

True while the previous secret still signs too (after a rotation, for its grace period).

prev_signing_secret_expires_at*string · date-time

Until then the previous secret signs too (after a rotation).

legacy_signature*boolean

True for endpoints carried over from the product's previous webhook system: they also receive the old signature header until its sunset date.

created_at*string · date-time
updated_at*string · date-time

Example

Request

GET /v1/workspaces/{workspace_id}/webhooks/endpoints/{endpoint_id}
Authorization: Bearer YOUR_TOKEN

Response

{
  "id": "00000000-0000-0000-0000-000000000000",
  "scope_type": "workspace",
  "scope_id": "string",
  "url": "https://api.acme.com/webhooks",
  "description": "Production receiver",
  "event_types": [],
  "enabled": false,
  "status": "enabled",
  "disabled_at": "2026-01-01T00:00:00.000Z",
  "disabled_reason": "manual",
  "consecutive_failures": 0,
  "first_failure_at": "2026-01-01T00:00:00.000Z",
  "signing_secret_hint": "x9Q=",
  "prev_signing_secret_active": false,
  "prev_signing_secret_expires_at": "2026-01-01T00:00:00.000Z",
  "legacy_signature": false,
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}
patch/v1/workspaces/{workspace_id}/webhooks/endpoints/{endpoint_id}Auth

Change a webhook endpoint: URL, description, events, on/off.

Only the fields sent change. `enabled: true` (or a new URL) also clears an automatic disable. Switching off cancels queued retries. The secret is unchanged; rotate it with `POST …/rotate-secret`.

Path parameters

workspace_id*string
endpoint_id*string · uuid

Headers

Idempotency-Keystring

Opaque client-generated string (1-256 chars, `[A-Za-z0-9_-:]`) that makes a retry of this request safe for 24 hours. The same key with a different body returns 409 `IDEMPOTENCY_KEY_REUSE`.

Request body

urlstring · uri

Example: "https://api.acme.com/webhooks"

descriptionstring
event_typesarray

Replaces the subscription. Event types from `GET …/webhooks/event-types`. Empty or omitted means every event, including types added later.

enabledboolean

Switch it on or off. Switching on also clears an automatic disable.

Response · 200

id*string · uuid
scope_type*string

What owns the endpoint: `workspace`, `app` or `community`.

Example: "workspace"

scope_id*string

Id of the owning workspace, app or community.

url*string · uri

Example: "https://api.acme.com/webhooks"

description*string

Example: "Production receiver"

event_types*array

Subscribed event types. Empty means every event, including types added later.

Example: []

enabled*boolean

The on/off switch (`PATCH … { enabled }`).

status*enum (3)

`enabled` receives events. `disabled` was switched off. `auto_disabled` was switched off after 20 failed deliveries in a row or 24 hours of failures; `PATCH { enabled: true }` turns it back on.

disabled_at*string · date-time
disabled_reason*enum (2)
consecutive_failures*number

Failed automatic deliveries in a row; a success resets it.

first_failure_at*string · date-time

Start of the current failure streak.

signing_secret_hint*string

Last 4 characters of the signing secret.

Example: "x9Q="

prev_signing_secret_active*boolean

True while the previous secret still signs too (after a rotation, for its grace period).

prev_signing_secret_expires_at*string · date-time

Until then the previous secret signs too (after a rotation).

legacy_signature*boolean

True for endpoints carried over from the product's previous webhook system: they also receive the old signature header until its sunset date.

created_at*string · date-time
updated_at*string · date-time

Example

Request

PATCH /v1/workspaces/{workspace_id}/webhooks/endpoints/{endpoint_id}
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "url": "https://api.acme.com/webhooks",
  "description": "string",
  "event_types": [
    "job.succeeded"
  ],
  "enabled": false
}

Response

{
  "id": "00000000-0000-0000-0000-000000000000",
  "scope_type": "workspace",
  "scope_id": "string",
  "url": "https://api.acme.com/webhooks",
  "description": "Production receiver",
  "event_types": [],
  "enabled": false,
  "status": "enabled",
  "disabled_at": "2026-01-01T00:00:00.000Z",
  "disabled_reason": "manual",
  "consecutive_failures": 0,
  "first_failure_at": "2026-01-01T00:00:00.000Z",
  "signing_secret_hint": "x9Q=",
  "prev_signing_secret_active": false,
  "prev_signing_secret_expires_at": "2026-01-01T00:00:00.000Z",
  "legacy_signature": false,
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}
delete/v1/workspaces/{workspace_id}/webhooks/endpoints/{endpoint_id}Auth

Delete a webhook endpoint.

Queued retries are dropped. Its past attempts stay in the event log until the events expire.

Path parameters

workspace_id*string
endpoint_id*string · uuid

Response · 204

Deleted.

post/v1/workspaces/{workspace_id}/webhooks/endpoints/{endpoint_id}/rotate-secretAuth

Rotate the signing secret. The new one is returned once.

For `grace_hours` (default 24) the previous secret keeps signing too: `webhook-signature` then lists one `v1,` signature per secret, so receivers can switch over without dropping a delivery.

Path parameters

workspace_id*string
endpoint_id*string · uuid

Headers

Idempotency-Keystring

Opaque client-generated string (1-256 chars, `[A-Za-z0-9_-:]`) that makes a retry of this request safe for 24 hours. The same key with a different body returns 409 `IDEMPOTENCY_KEY_REUSE`.

Request body

grace_hoursnumber

Hours the previous secret keeps signing too (0 ends it now).

Response · 200

id*string · uuid
scope_type*string

What owns the endpoint: `workspace`, `app` or `community`.

Example: "workspace"

scope_id*string

Id of the owning workspace, app or community.

url*string · uri

Example: "https://api.acme.com/webhooks"

description*string

Example: "Production receiver"

event_types*array

Subscribed event types. Empty means every event, including types added later.

Example: []

enabled*boolean

The on/off switch (`PATCH … { enabled }`).

status*enum (3)

`enabled` receives events. `disabled` was switched off. `auto_disabled` was switched off after 20 failed deliveries in a row or 24 hours of failures; `PATCH { enabled: true }` turns it back on.

disabled_at*string · date-time
disabled_reason*enum (2)
consecutive_failures*number

Failed automatic deliveries in a row; a success resets it.

first_failure_at*string · date-time

Start of the current failure streak.

signing_secret_hint*string

Last 4 characters of the signing secret.

Example: "x9Q="

prev_signing_secret_active*boolean

True while the previous secret still signs too (after a rotation, for its grace period).

prev_signing_secret_expires_at*string · date-time

Until then the previous secret signs too (after a rotation).

legacy_signature*boolean

True for endpoints carried over from the product's previous webhook system: they also receive the old signature header until its sunset date.

created_at*string · date-time
updated_at*string · date-time
signing_secret*string

The signing secret, shown once: pass it unchanged to any Standard Webhooks verifier. Store it now; it cannot be shown again.

Example: "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw"

Example

Request

POST /v1/workspaces/{workspace_id}/webhooks/endpoints/{endpoint_id}/rotate-secret
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "grace_hours": 0
}

Response

{
  "id": "00000000-0000-0000-0000-000000000000",
  "scope_type": "workspace",
  "scope_id": "string",
  "url": "https://api.acme.com/webhooks",
  "description": "Production receiver",
  "event_types": [],
  "enabled": false,
  "status": "enabled",
  "disabled_at": "2026-01-01T00:00:00.000Z",
  "disabled_reason": "manual",
  "consecutive_failures": 0,
  "first_failure_at": "2026-01-01T00:00:00.000Z",
  "signing_secret_hint": "x9Q=",
  "prev_signing_secret_active": false,
  "prev_signing_secret_expires_at": "2026-01-01T00:00:00.000Z",
  "legacy_signature": false,
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z",
  "signing_secret": "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw"
}
post/v1/workspaces/{workspace_id}/webhooks/endpoints/{endpoint_id}/testAuth

Send a test event to this endpoint now and return the result.

Delivers the catalogue example of `event_type`, signed like any delivery, with `"test": true` in the body. Works while the endpoint is switched off or not subscribed to that type, is logged in the event log, and is never retried or counted toward an automatic disable. Waits up to 15 s for your receiver.

Path parameters

workspace_id*string
endpoint_id*string · uuid

Headers

Idempotency-Keystring

Opaque client-generated string (1-256 chars, `[A-Za-z0-9_-:]`) that makes a retry of this request safe for 24 hours. The same key with a different body returns 409 `IDEMPOTENCY_KEY_REUSE`.

Request body

event_type*enum (3)

Which event to send; its catalogue example is the `data`.

Example: "job.succeeded"

Response · 200 The attempt, successful or not.

id*string · uuid
event_id*string

Example: "evt_3f0c3c1e-8a2b-4c55-9d3f-0a1b2c3d4e5f"

endpoint_id*string · uuid
event_type*string

Example: "user.created"

attempt_number*number

1 for the first delivery; retries and replays count up.

trigger*enum (3)

`automatic` (first delivery or a retry), `replay` or `test`.

url*string · uri

Where it was sent.

status_code*number

HTTP status; null when no response came back.

succeeded*boolean

True for a 2xx response.

error_message*string

Example: "request timed out"

response_body*string

The first 1 KiB of the response body.

latency_ms*number
attempted_at*string · date-time

Example

Request

POST /v1/workspaces/{workspace_id}/webhooks/endpoints/{endpoint_id}/test
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "event_type": "job.succeeded"
}

Response

{
  "id": "00000000-0000-0000-0000-000000000000",
  "event_id": "evt_3f0c3c1e-8a2b-4c55-9d3f-0a1b2c3d4e5f",
  "endpoint_id": "00000000-0000-0000-0000-000000000000",
  "event_type": "user.created",
  "attempt_number": 0,
  "trigger": "automatic",
  "url": "string",
  "status_code": 0,
  "succeeded": false,
  "error_message": "request timed out",
  "response_body": "string",
  "latency_ms": 0,
  "attempted_at": "2026-01-01T00:00:00.000Z"
}
get/v1/workspaces/{workspace_id}/webhooks/eventsAuth

List webhook events, newest first (kept for 30 days).

Every event logged while at least one endpoint existed, with the latest state per endpoint and when the next retry is due.

Path parameters

workspace_id*string

Query parameters

typearray

Only these types. Repeat the parameter or comma-separate.

endpoint_idstring · uuid

Only events sent (or queued) to this endpoint.

statusenum (4)
created_afterstring · date-time

Only events created at or after this instant.

created_beforestring · date-time

Only events created before this instant.

limitnumber
cursorstring

The previous page's `pagination.next_cursor`.

Response · 200

data*array
pagination*object
next_cursor*string

Pass as `cursor` to get the next page; null on the last page.

has_more*boolean

Whether another page follows.

Example

Request

GET /v1/workspaces/{workspace_id}/webhooks/events
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "id": "evt_3f0c3c1e-8a2b-4c55-9d3f-0a1b2c3d4e5f",
      "type": "job.succeeded",
      "created_at": "2026-01-01T00:00:00.000Z",
      "test": false,
      "status": "pending",
      "payload": {},
      "deliveries": [
        {
          "endpoint_id": "00000000-0000-0000-0000-000000000000",
          "attempt_number": 0,
          "status_code": 0,
          "succeeded": false,
          "attempted_at": "2026-01-01T00:00:00.000Z",
          "next_attempt_at": "2026-01-01T00:00:00.000Z"
        }
      ]
    }
  ],
  "pagination": {
    "next_cursor": "string",
    "has_more": false
  }
}
get/v1/workspaces/{workspace_id}/webhooks/events/{event_id}Auth

Get one webhook event with every delivery attempt.

Path parameters

workspace_id*string
event_id*string

The event id, `evt_<uuid>`.

Response · 200

id*string

Sent as `webhook-id`; the same on every retry and replay.

Example: "evt_3f0c3c1e-8a2b-4c55-9d3f-0a1b2c3d4e5f"

type*enum (3)

Example: "job.succeeded"

created_at*string · date-time
test*boolean

Made by "Send test".

status*enum (4)

`pending`: a delivery is queued. `failed`: the latest attempt to some endpoint failed and none is queued. `delivered`: every endpoint answered 2xx. `not_sent`: no endpoint subscribed.

payload*object

The JSON body exactly as sent.

deliveries*array

Latest attempt per endpoint (an endpoint whose first attempt is still queued is not listed yet).

attempts*array

Every attempt, newest first.

Example

Request

GET /v1/workspaces/{workspace_id}/webhooks/events/{event_id}
Authorization: Bearer YOUR_TOKEN

Response

{
  "id": "evt_3f0c3c1e-8a2b-4c55-9d3f-0a1b2c3d4e5f",
  "type": "job.succeeded",
  "created_at": "2026-01-01T00:00:00.000Z",
  "test": false,
  "status": "pending",
  "payload": {},
  "deliveries": [
    {
      "endpoint_id": "00000000-0000-0000-0000-000000000000",
      "attempt_number": 0,
      "status_code": 0,
      "succeeded": false,
      "attempted_at": "2026-01-01T00:00:00.000Z",
      "next_attempt_at": "2026-01-01T00:00:00.000Z"
    }
  ],
  "attempts": [
    {
      "id": "00000000-0000-0000-0000-000000000000",
      "event_id": "evt_3f0c3c1e-8a2b-4c55-9d3f-0a1b2c3d4e5f",
      "endpoint_id": "00000000-0000-0000-0000-000000000000",
      "event_type": "user.created",
      "attempt_number": 0,
      "trigger": "automatic",
      "url": "string",
      "status_code": 0,
      "succeeded": false,
      "error_message": "request timed out",
      "response_body": "string",
      "latency_ms": 0,
      "attempted_at": "2026-01-01T00:00:00.000Z"
    }
  ]
}
post/v1/workspaces/{workspace_id}/webhooks/events/{event_id}/replayAuth

Send a logged event again, to its endpoints or to one endpoint.

Same body and `webhook-id`, freshly signed, to every switched-on endpoint that receives this event type now, or only to `endpoint_id`. Waits for the receivers (up to 15 s each) and returns the attempts. A success cancels a retry still queued for that endpoint.

Path parameters

workspace_id*string
event_id*string

The event id, `evt_<uuid>`.

Query parameters

endpoint_idstring · uuid

Send to this endpoint only.

Headers

Idempotency-Keystring

Opaque client-generated string (1-256 chars, `[A-Za-z0-9_-:]`) that makes a retry of this request safe for 24 hours. The same key with a different body returns 409 `IDEMPOTENCY_KEY_REUSE`.

Response · 200

event_id*string
attempts*array

One attempt per endpoint it was sent to.

Example

Request

POST /v1/workspaces/{workspace_id}/webhooks/events/{event_id}/replay
Authorization: Bearer YOUR_TOKEN

Response

{
  "event_id": "string",
  "attempts": [
    {
      "id": "00000000-0000-0000-0000-000000000000",
      "event_id": "evt_3f0c3c1e-8a2b-4c55-9d3f-0a1b2c3d4e5f",
      "endpoint_id": "00000000-0000-0000-0000-000000000000",
      "event_type": "user.created",
      "attempt_number": 0,
      "trigger": "automatic",
      "url": "string",
      "status_code": 0,
      "succeeded": false,
      "error_message": "request timed out",
      "response_body": "string",
      "latency_ms": 0,
      "attempted_at": "2026-01-01T00:00:00.000Z"
    }
  ]
}
get/v1/workspaces/{workspace_id}/webhooksAuth

List webhooks for the workspace (paginated, newest first, secrets masked)

Deprecated: use `/v1/workspaces/{workspace_id}/webhooks/endpoints` (event filter, event descriptions, Send test that reports the result, event log, replay, rotation with a grace period). Responses carry `Deprecation: true` and `Sunset: Fri, 15 Jan 2027 00:00:00 GMT`.

Path parameters

workspace_id*string

Query parameters

cursorstring

Opaque cursor from the previous response's `pagination.next_cursor`.

limitinteger

Page size. Defaults to 20, capped at 100.

Response · 200 Page of webhooks with a pagination cursor.

data*array
pagination*object
next_cursor*string
has_more*boolean

Example

Request

GET /v1/workspaces/{workspace_id}/webhooks
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "id": "00000000-0000-0000-0000-000000000000",
      "workspace_id": "00000000-0000-0000-0000-000000000000",
      "url": "string",
      "active": false,
      "failure_count": 0,
      "last_status_code": 0,
      "last_attempt_at": "2026-01-01T00:00:00.000Z",
      "auto_disabled_at": "2026-01-01T00:00:00.000Z",
      "first_failure_at": "2026-01-01T00:00:00.000Z",
      "created_at": "2026-01-01T00:00:00.000Z",
      "secret_set": false
    }
  ],
  "pagination": {
    "next_cursor": "string",
    "has_more": false
  }
}
post/v1/workspaces/{workspace_id}/webhooksAuth

Create a workspace webhook. The response includes the signing secret in clear EXACTLY ONCE — copy it now.

Deprecated: use `/v1/workspaces/{workspace_id}/webhooks/endpoints` (event filter, event descriptions, Send test that reports the result, event log, replay, rotation with a grace period). Responses carry `Deprecation: true` and `Sunset: Fri, 15 Jan 2027 00:00:00 GMT`.

Path parameters

workspace_id*string

Headers

Idempotency-Keystring

Stripe-style idempotency key. The signing secret is returned exactly once; a retry without this header creates a second webhook + secret. 24h TTL.

Request body

url*string

Destination URL (https required in prod).

activeboolean

Disabled webhooks are never delivered to.

Response · 201 Webhook created. Includes plaintext `signing_secret` exactly once.

id*string · uuid
workspace_id*string · uuid
url*string
active*boolean

Whether it receives events (`enabled` on the new routes).

failure_count*number

Failed deliveries in a row (`consecutive_failures` on the new routes).

last_status_code*number

HTTP status of the latest attempt.

last_attempt_at*string · date-time

When the latest attempt was made.

auto_disabled_at*string · date-time

Set when it was switched off automatically after sustained failures.

first_failure_at*string · date-time
created_at*string · date-time
secret_setboolean

Always true: every webhook has a signing secret.

signing_secret*string

Plaintext signing secret (`whsec_…`), returned EXACTLY ONCE. Persist immediately; it does not appear on later reads.

Example

Request

POST /v1/workspaces/{workspace_id}/webhooks
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "url": "string",
  "active": false
}

Response

{
  "id": "00000000-0000-0000-0000-000000000000",
  "workspace_id": "00000000-0000-0000-0000-000000000000",
  "url": "string",
  "active": false,
  "failure_count": 0,
  "last_status_code": 0,
  "last_attempt_at": "2026-01-01T00:00:00.000Z",
  "auto_disabled_at": "2026-01-01T00:00:00.000Z",
  "first_failure_at": "2026-01-01T00:00:00.000Z",
  "created_at": "2026-01-01T00:00:00.000Z",
  "secret_set": false,
  "signing_secret": "string"
}
get/v1/workspaces/{workspace_id}/webhooks/{id}Auth

Get a webhook (secret masked)

Deprecated: use `/v1/workspaces/{workspace_id}/webhooks/endpoints` (event filter, event descriptions, Send test that reports the result, event log, replay, rotation with a grace period). Responses carry `Deprecation: true` and `Sunset: Fri, 15 Jan 2027 00:00:00 GMT`.

Path parameters

workspace_id*string
id*string

Response · 200 Webhook.

id*string · uuid
workspace_id*string · uuid
url*string
active*boolean

Whether it receives events (`enabled` on the new routes).

failure_count*number

Failed deliveries in a row (`consecutive_failures` on the new routes).

last_status_code*number

HTTP status of the latest attempt.

last_attempt_at*string · date-time

When the latest attempt was made.

auto_disabled_at*string · date-time

Set when it was switched off automatically after sustained failures.

first_failure_at*string · date-time
created_at*string · date-time
secret_setboolean

Always true: every webhook has a signing secret.

Example

Request

GET /v1/workspaces/{workspace_id}/webhooks/{id}
Authorization: Bearer YOUR_TOKEN

Response

{
  "id": "00000000-0000-0000-0000-000000000000",
  "workspace_id": "00000000-0000-0000-0000-000000000000",
  "url": "string",
  "active": false,
  "failure_count": 0,
  "last_status_code": 0,
  "last_attempt_at": "2026-01-01T00:00:00.000Z",
  "auto_disabled_at": "2026-01-01T00:00:00.000Z",
  "first_failure_at": "2026-01-01T00:00:00.000Z",
  "created_at": "2026-01-01T00:00:00.000Z",
  "secret_set": false
}
patch/v1/workspaces/{workspace_id}/webhooks/{id}Auth

Update url / active flag (active=true clears auto-disable)

Deprecated: use `/v1/workspaces/{workspace_id}/webhooks/endpoints` (event filter, event descriptions, Send test that reports the result, event log, replay, rotation with a grace period). Responses carry `Deprecation: true` and `Sunset: Fri, 15 Jan 2027 00:00:00 GMT`.

Path parameters

workspace_id*string
id*string

Request body

urlstring

New destination URL.

activeboolean

Pause/resume the webhook. Setting active=true clears auto-disable.

Response · 200 Updated webhook (secret masked).

id*string · uuid
workspace_id*string · uuid
url*string
active*boolean

Whether it receives events (`enabled` on the new routes).

failure_count*number

Failed deliveries in a row (`consecutive_failures` on the new routes).

last_status_code*number

HTTP status of the latest attempt.

last_attempt_at*string · date-time

When the latest attempt was made.

auto_disabled_at*string · date-time

Set when it was switched off automatically after sustained failures.

first_failure_at*string · date-time
created_at*string · date-time
secret_setboolean

Always true: every webhook has a signing secret.

Example

Request

PATCH /v1/workspaces/{workspace_id}/webhooks/{id}
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "url": "string",
  "active": false
}

Response

{
  "id": "00000000-0000-0000-0000-000000000000",
  "workspace_id": "00000000-0000-0000-0000-000000000000",
  "url": "string",
  "active": false,
  "failure_count": 0,
  "last_status_code": 0,
  "last_attempt_at": "2026-01-01T00:00:00.000Z",
  "auto_disabled_at": "2026-01-01T00:00:00.000Z",
  "first_failure_at": "2026-01-01T00:00:00.000Z",
  "created_at": "2026-01-01T00:00:00.000Z",
  "secret_set": false
}
delete/v1/workspaces/{workspace_id}/webhooks/{id}Auth

Delete a webhook

Deprecated: use `/v1/workspaces/{workspace_id}/webhooks/endpoints` (event filter, event descriptions, Send test that reports the result, event log, replay, rotation with a grace period). Responses carry `Deprecation: true` and `Sunset: Fri, 15 Jan 2027 00:00:00 GMT`.

Path parameters

workspace_id*string
id*string

Response · 204

Deleted.

get/v1/workspaces/{workspace_id}/webhooks/{id}/deliveriesAuth

Delivery attempts for a webhook (paginated, default 50, max 200, newest first)

Deprecated: use `/v1/workspaces/{workspace_id}/webhooks/endpoints` (event filter, event descriptions, Send test that reports the result, event log, replay, rotation with a grace period). Responses carry `Deprecation: true` and `Sunset: Fri, 15 Jan 2027 00:00:00 GMT`.

Path parameters

workspace_id*string
id*string

Query parameters

statusenum (2)

Filter by outcome. `succeeded` = 2xx response; `failed` = any non-2xx, including attempts where the destination never responded.

cursorstring

Opaque cursor from the previous response's `pagination.next_cursor`.

limitinteger

Page size. Defaults to 50, capped at 200.

Response · 200 Page of delivery attempts with a pagination cursor.

data*array
pagination*object
next_cursor*string
has_more*boolean

Example

Request

GET /v1/workspaces/{workspace_id}/webhooks/{id}/deliveries
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "id": "00000000-0000-0000-0000-000000000000",
      "webhook_id": "00000000-0000-0000-0000-000000000000",
      "job_id": "string",
      "event_id": "string",
      "event_type": "job.succeeded",
      "attempt_number": 0,
      "status_code": 0,
      "response_body_truncated": "string",
      "error_message": "string",
      "latency_ms": 0,
      "attempted_at": "2026-01-01T00:00:00.000Z"
    }
  ],
  "pagination": {
    "next_cursor": "string",
    "has_more": false
  }
}
post/v1/workspaces/{workspace_id}/webhooks/{id}/rotate-secretAuth

Rotate the signing secret. Returns the new secret in clear EXACTLY ONCE; the old one stops at once.

Deprecated: use `/v1/workspaces/{workspace_id}/webhooks/endpoints` (event filter, event descriptions, Send test that reports the result, event log, replay, rotation with a grace period). Responses carry `Deprecation: true` and `Sunset: Fri, 15 Jan 2027 00:00:00 GMT`.

Path parameters

workspace_id*string
id*string

Response · 201 Secret rotated. Includes the new plaintext secret exactly once.

id*string · uuid
secret*string

New plaintext signing secret, returned EXACTLY ONCE. Persist immediately; the old secret stops working at once.

Example

Request

POST /v1/workspaces/{workspace_id}/webhooks/{id}/rotate-secret
Authorization: Bearer YOUR_TOKEN

Response

{
  "id": "00000000-0000-0000-0000-000000000000",
  "secret": "string"
}
post/v1/workspaces/{workspace_id}/webhooks/{id}/testAuth

Send the `job.succeeded` example to this webhook; the attempt shows in `/deliveries`

Deprecated: use `/v1/workspaces/{workspace_id}/webhooks/endpoints` (event filter, event descriptions, Send test that reports the result, event log, replay, rotation with a grace period). Responses carry `Deprecation: true` and `Sunset: Fri, 15 Jan 2027 00:00:00 GMT`.

Path parameters

workspace_id*string
id*string

Response · 202 Sent. Read the outcome from `/deliveries`.

accepted*boolean

Example: true

Example

Request

POST /v1/workspaces/{workspace_id}/webhooks/{id}/test
Authorization: Bearer YOUR_TOKEN

Response

{
  "accepted": true
}

Jobs

get/v1/workspaces/{workspace_id}/jobsAuth

List trawl jobs (paginated, newest first)

Path parameters

workspace_id*string

Query parameters

statusenum (5)

Filter by job status.

cursorstring

Opaque cursor for pagination.

limitnumber

Response · 200 Page of jobs with pagination cursor.

data*array
pagination*object
next_cursor*string
has_more*boolean

Example

Request

GET /v1/workspaces/{workspace_id}/jobs
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "id": "string",
      "workspace_id": "string",
      "status": "queued",
      "json_schema": {},
      "description": "string",
      "suggested_urls": [
        "string"
      ],
      "max_steps": 0,
      "timeout_s": 0,
      "model_tier": "string",
      "webhook_id": "string",
      "result": {},
      "error": "string",
      "attempts": 0,
      "enqueued_at": "string",
      "started_at": "string",
      "completed_at": "string",
      "created_at": "string"
    }
  ],
  "pagination": {
    "next_cursor": "string",
    "has_more": false
  }
}
post/v1/workspaces/{workspace_id}/jobsAuth

Create (enqueue) a trawl job

Path parameters

workspace_id*string

Headers

Idempotency-Keystring

Pass a key (1..256 chars, [A-Za-z0-9_\-:]) to make retries safe — replays return the original response for 24h.

Request body

json_schema*object

JSON Schema describing the shape of the extraction result. Must be a non-empty object with `type: "object"`.

Example: {"type":"object","properties":{"title":{"type":"string"}},"required":["title"]}

description*string

Natural-language description of what to extract.

Example: "Extract the product title and price from each page."

suggested_urlsarray

Seed URLs the worker should start from.

webhook_idstring · uuid

A webhook endpoint of this workspace to notify when the job finishes. Endpoints made before 17 Oct 2026 receive only the jobs that name them here; endpoints made under `/webhooks/endpoints` receive every job event they subscribe to, so leave this out for them.

max_stepsnumber

Maximum number of agent steps (one LLM call each) before the job stops. Defaults to 20. Multi-source extractions typically need 40+. The server additionally clamps this to an operator ceiling, so a very large value does not guarantee a very long run — `timeout_s` still applies.

timeout_snumber

Wall-clock timeout for the job, in seconds.

model_tierstring

Model tier the worker should use (e.g. `standard`, `pro`).

Response · 201 Job created and queued.

id*string
workspace_id*string
status*enum (5)
json_schema*object

The JSON Schema the job was created with.

description*string
suggested_urls*array
max_steps*number
timeout_s*number
model_tier*string
webhook_id*string
result*object

The extracted data, shaped by `json_schema`. Null until the job succeeds.

error*string
attempts*number
enqueued_at*string
started_at*string
completed_at*string
created_at*string

Example

Request

POST /v1/workspaces/{workspace_id}/jobs
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "json_schema": {
    "type": "object",
    "properties": {
      "title": {
        "type": "string"
      }
    },
    "required": [
      "title"
    ]
  },
  "description": "Extract the product title and price from each page.",
  "suggested_urls": [
    "string"
  ],
  "webhook_id": "00000000-0000-0000-0000-000000000000",
  "max_steps": 0,
  "timeout_s": 0,
  "model_tier": "string"
}

Response

{
  "id": "string",
  "workspace_id": "string",
  "status": "queued",
  "json_schema": {},
  "description": "string",
  "suggested_urls": [
    "string"
  ],
  "max_steps": 0,
  "timeout_s": 0,
  "model_tier": "string",
  "webhook_id": "string",
  "result": {},
  "error": "string",
  "attempts": 0,
  "enqueued_at": "string",
  "started_at": "string",
  "completed_at": "string",
  "created_at": "string"
}
get/v1/workspaces/{workspace_id}/jobs/{id}Auth

Get a single trawl job

Path parameters

workspace_id*string
id*string

Response · 200 Job row.

id*string
workspace_id*string
status*enum (5)
json_schema*object

The JSON Schema the job was created with.

description*string
suggested_urls*array
max_steps*number
timeout_s*number
model_tier*string
webhook_id*string
result*object

The extracted data, shaped by `json_schema`. Null until the job succeeds.

error*string
attempts*number
enqueued_at*string
started_at*string
completed_at*string
created_at*string

Example

Request

GET /v1/workspaces/{workspace_id}/jobs/{id}
Authorization: Bearer YOUR_TOKEN

Response

{
  "id": "string",
  "workspace_id": "string",
  "status": "queued",
  "json_schema": {},
  "description": "string",
  "suggested_urls": [
    "string"
  ],
  "max_steps": 0,
  "timeout_s": 0,
  "model_tier": "string",
  "webhook_id": "string",
  "result": {},
  "error": "string",
  "attempts": 0,
  "enqueued_at": "string",
  "started_at": "string",
  "completed_at": "string",
  "created_at": "string"
}
post/v1/workspaces/{workspace_id}/jobs/{id}/cancelAuth

Cancel a queued or running trawl job

Path parameters

workspace_id*string
id*string

Response · 200 Job cancelled.

id*string
workspace_id*string
status*enum (5)
json_schema*object

The JSON Schema the job was created with.

description*string
suggested_urls*array
max_steps*number
timeout_s*number
model_tier*string
webhook_id*string
result*object

The extracted data, shaped by `json_schema`. Null until the job succeeds.

error*string
attempts*number
enqueued_at*string
started_at*string
completed_at*string
created_at*string

Example

Request

POST /v1/workspaces/{workspace_id}/jobs/{id}/cancel
Authorization: Bearer YOUR_TOKEN

Response

{
  "id": "string",
  "workspace_id": "string",
  "status": "queued",
  "json_schema": {},
  "description": "string",
  "suggested_urls": [
    "string"
  ],
  "max_steps": 0,
  "timeout_s": 0,
  "model_tier": "string",
  "webhook_id": "string",
  "result": {},
  "error": "string",
  "attempts": 0,
  "enqueued_at": "string",
  "started_at": "string",
  "completed_at": "string",
  "created_at": "string"
}

Credits

get/v1/workspaces/{workspace_id}/creditsAuth

Remaining Trawl steps for this workspace, whether the balance is low, and recent ledger entries

Path parameters

workspace_id*string

Query parameters

limitinteger

How many recent entries to return. Defaults to 50, capped at 200.

Response · 200 Balance, low-balance state and recent entries.

balance_steps*number

Remaining steps. One agent step uses one credit; new jobs are refused (402) at zero. Can dip slightly below zero, because a running job is always allowed to finish.

Example: 8420

low_balance*boolean

True when the balance is under `low_balance_threshold_steps`. The `credits.low` webhook fires once when this turns true, and again only after the next top-up.

Example: false

low_balance_threshold_steps*number

The low-balance line: 10% of the last top-up, and never under 100 steps.

Example: 1000

entries*array

Recent ledger entries, newest first.

Example

Request

GET /v1/workspaces/{workspace_id}/credits
Authorization: Bearer YOUR_TOKEN

Response

{
  "balance_steps": 8420,
  "low_balance": false,
  "low_balance_threshold_steps": 1000,
  "entries": [
    {
      "id": "00000000-0000-0000-0000-000000000000",
      "delta_steps": -12,
      "reason": "job",
      "job_id": "00000000-0000-0000-0000-000000000000",
      "created_at": "2026-01-01T00:00:00.000Z"
    }
  ]
}

Stats

get/v1/workspaces/{workspace_id}/statsAuth

Trawl stats for a workspace

Total jobs, jobs created / succeeded / failed and steps used in the window, the credit balance, when the latest job was created and a daily jobs series. **Active** means a job was created. Days are UTC. Counts only: no target URLs, descriptions or job results. Cached for up to 60 seconds (`generated_at`).

Path parameters

workspace_id*string · uuid

Workspace id.

Query parameters

windowenum (3)

Window for `in_window` and the length of `series`, in UTC days ending today.

Response · 200 Trawl stats.

window*enum (3)

Window the counts cover.

Example: "30d"

generated_at*string · date-time

When these numbers were computed. Responses are cached for up to 60 seconds.

Example: "2026-10-07T09:00:00.000Z"

last_activity_at*string · date-time

When the workspace's most recent job was created. Null when there has never been one.

Example: "2026-10-07T08:41:12.000Z"

totals*object

All-time counts and the credit balance.

in_window*object

Counts for the window.

series*object

Daily jobs.

Example

Request

GET /v1/workspaces/{workspace_id}/stats
Authorization: Bearer YOUR_TOKEN

Response

{
  "window": "30d",
  "generated_at": "2026-10-07T09:00:00.000Z",
  "last_activity_at": "2026-10-07T08:41:12.000Z",
  "totals": {},
  "in_window": {},
  "series": {}
}