Routes API
Routes connect sources to destinations. A route delivers to exactly one destination, so sending one source's events to three destinations means three routes.
Endpoints
| Method | Path | Description |
|---|---|---|
| GET | /api/routes | List routes |
| POST | /api/routes | Create route |
| POST | /api/routes/bulk | Create up to 50 routes in one request |
| GET | /api/routes/{id} | Get route |
| PATCH | /api/routes/{id} | Update route |
| DELETE | /api/routes/{id} | Delete route |
| GET | /api/routes/{id}/circuit-status | Get circuit breaker status |
| POST | /api/routes/{id}/reset-circuit | Reset circuit breaker |
| PATCH | /api/routes/{id}/circuit-config | Update circuit breaker config |
| GET | /api/routes/{id}/failed-event-samples | Sample recent failed events for a route |
| GET | /api/routes/export | Export routes as JSON |
| POST | /api/routes/import | Import routes from JSON |
| PATCH | /api/routes/bulk | Pause/resume multiple routes |
| DELETE | /api/routes/bulk | Delete multiple routes |
Info
Route, source, and destination IDs are UUIDs. The examples on this page use real UUID
shapes for that reason — there is no rte_ prefix to match on.
Route Object
This is what GET /api/routes and GET /api/routes/{id} return. Both return every field below, and
nothing else.
{
"id": "44444444-4444-4444-8444-444444444444",
"organizationId": "9f1c2b7e-3a4d-4f88-91b2-7c0e5d8a6f31",
"name": "GitHub to Slack",
"sourceId": "11111111-1111-4111-8111-111111111111",
"destinationId": "22222222-2222-4222-8222-222222222222",
"filterId": null,
"transformId": null,
"schemaId": null,
"priority": 10,
"isActive": true,
"filterConditions": "[{\"field\":\"action\",\"operator\":\"equals\",\"value\":\"opened\"}]",
"filterLogic": "AND",
"notifyOnFailure": true,
"notifyOnSuccess": false,
"notifyOnRecovery": true,
"notifyEmails": "team@example.com",
"failureThreshold": 3,
"consecutiveFailures": 0,
"failoverDestinationIds": ["33333333-3333-4333-8333-333333333333"],
"failoverAfterAttempts": 3,
"circuitState": "closed",
"circuitOpenedAt": null,
"circuitCooldownSeconds": 300,
"circuitProbeAttempts": 0,
"circuitProbeSuccessThreshold": 2,
"circuitFailureThreshold": 5,
"expectedResponse": { "statusCodes": [200, 201] },
"createdAt": "2026-01-01T00:00:00Z",
"updatedAt": "2026-01-06T00:00:00Z",
"sourceName": "GitHub",
"sourceSlug": "github",
"destinationName": "Slack relay",
"destinationSlug": "slack-relay",
"transformName": null,
"filterName": null,
"schemaName": null
}Notes on the shape
| Field | Note |
|---|---|
isActive | Whether the route is running. There is no enabled field — sending enabled on a write is silently ignored |
filterConditions | A JSON-encoded string, not an array — parse it before reading it. Writes take an array (see below) |
expectedResponse | Parsed to an object, or null |
failoverDestinationIds | Always an array. A route with no failover destinations returns [], not null |
sourceName, destinationName, transformName, filterName, schemaName | Flat joined names, null when the reference is unset. There are no nested source/destination/transform/filter objects |
destName, destSlug | Also present on GET /api/routes/{id} as historical aliases of destinationName/destinationSlug. Prefer the destination* spelling, which every endpoint returns |
Warning
A route object carries no delivery counters. There is no eventsRouted or lastEventAt.
Per-route delivery metrics come from the Analytics API
(GET /api/analytics/routes).
List Routes
GET /api/routesQuery Parameters
| Parameter | Type | Description |
|---|---|---|
| page | number | Page number (default: 1) |
| pageSize | number | Items per page (default: 20, max: 100) |
Info
Those are the only two parameters. There is no server-side filtering by source, destination,
or active status — anything else in the query string is ignored, and the response is the
unfiltered page. Filter client-side on sourceId, destinationId, or isActive.
Example
curl "https://api.hookbase.app/api/routes?page=1&pageSize=50" \
-H "Authorization: Bearer whr_your_api_key"Response
{
"routes": [
{
"id": "44444444-4444-4444-8444-444444444444",
"name": "GitHub to Slack",
"sourceId": "11111111-1111-4111-8111-111111111111",
"destinationId": "22222222-2222-4222-8222-222222222222",
"isActive": true,
"sourceName": "GitHub",
"sourceSlug": "github",
"destinationName": "Slack relay",
"destinationSlug": "slack-relay"
}
],
"pagination": {
"total": 5,
"page": 1,
"pageSize": 20
}
}Each entry is a full Route Object; the fields above are an excerpt.
pagination.total counts every route in the organization, not just this page.
Create Route
POST /api/routesRequest Body
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| name | string | Yes | — | Display name |
| sourceId | string | Yes | — | Source ID to listen to |
| destinationId | string | Yes | — | The destination to deliver to. A route delivers to exactly one destination |
| transformId | string | No | null | Transform to apply |
| filterId | string | No | null | Filter to evaluate |
| schemaId | string | No | null | Schema to validate incoming events against |
| priority | number | No | 0 | Route priority — higher values run first |
| filterConditions | array | No | null | Inline filter conditions (alternative to filterId). See Filters |
| filterLogic | string | No | AND | Logic for inline filters: AND or OR. Only stored when filterConditions is non-empty |
| isActive | boolean | No | true | Whether the route runs |
Info
filterId and filterConditions are mutually exclusive. Sending both is a 400. Pass
filterConditions as an array on writes — it comes back as a JSON-encoded string on reads.
Failover Configuration
| Field | Type | Default | Description |
|---|---|---|---|
| failoverDestinationIds | string[] | [] | Backup destination IDs (max 3). See Failover Guide |
| failoverAfterAttempts | number | 5 | Switch to failover after this many failed attempts (1-5) |
Warning
Failover is a paid-plan feature. Sending a non-empty failoverDestinationIds on a plan
without it returns 403 FEATURE_DISABLED. An empty array is accepted on any plan.
Circuit Breaker Configuration
| Field | Type | Default | Description |
|---|---|---|---|
| circuitCooldownSeconds | number | 300 | Seconds to wait in open state before probing |
| circuitFailureThreshold | number | 5 | Consecutive failures to trip the circuit |
| circuitProbeSuccessThreshold | number | 2 | Successful probes required to close circuit |
Omitting these stores null and the route uses the defaults above. Create does not range-check
them — PATCH /api/routes/{id}/circuit-config does, and is the
only endpoint that can change them afterwards. See the
Circuit Breaker Guide.
Notification Configuration
| Field | Type | Default | Description |
|---|---|---|---|
| notifyOnFailure | boolean | false | Send notification when delivery fails |
| notifyOnSuccess | boolean | false | Send notification when delivery succeeds |
| notifyOnRecovery | boolean | false | Send notification when route recovers from failure |
| notifyEmails | string | null | Comma-separated email addresses for notifications |
| failureThreshold | number | 3 | Consecutive failures before a failure notification is sent |
Response Validation
| Field | Type | Description |
|---|---|---|
| expectedResponse | object | Validate destination response. { "statusCodes": [200, 201], "bodyContains": "ok" } |
Example
curl -X POST https://api.hookbase.app/api/routes \
-H "Authorization: Bearer whr_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"name": "GitHub to Slack",
"sourceId": "11111111-1111-4111-8111-111111111111",
"destinationId": "22222222-2222-4222-8222-222222222222",
"transformId": "88888888-8888-4888-8888-888888888888",
"priority": 10,
"notifyOnFailure": true
}'Response
Create echoes back what it stored, which is not the same shape as a
Route Object: there are no joined names, no createdAt/updatedAt, no circuit
state, and filterConditions is the array you sent rather than a JSON string. Read the route back
with GET /api/routes/{id} if you need the full object.
{
"route": {
"id": "44444444-4444-4444-8444-444444444444",
"name": "GitHub to Slack",
"sourceId": "11111111-1111-4111-8111-111111111111",
"destinationId": "22222222-2222-4222-8222-222222222222",
"filterId": null,
"filterConditions": null,
"filterLogic": null,
"transformId": "88888888-8888-4888-8888-888888888888",
"schemaId": null,
"priority": 10,
"isActive": true,
"notifyOnFailure": true,
"notifyOnSuccess": false,
"notifyOnRecovery": false,
"notifyEmails": "",
"failureThreshold": 3,
"failoverDestinationIds": [],
"failoverAfterAttempts": 5,
"expectedResponse": null
}
}Errors
| Status | Error | Cause |
|---|---|---|
| 400 | name, sourceId, and destinationId are required | A required field is missing or empty |
| 400 | Cannot specify both filterId and filterConditions. Use one or the other. | Both filter forms sent |
| 400 | Not found or does not belong to this organization: <fields> | A referenced source, destination, filter, transform, schema, or failover destination is missing or belongs to another org |
| 400 | Maximum 3 failover destinations allowed | More than 3 failover destinations |
| 400 | Failover destinations cannot include the primary destination | failoverDestinationIds contains destinationId |
| 400 | Failover destinations cannot contain duplicates | Repeated ID in failoverDestinationIds |
| 400 | failoverAfterAttempts must be between 1 and 5 | Out of range |
| 403 | FEATURE_DISABLED | Failover requested on a plan without it |
| 403 | LIMIT_EXCEEDED | The organization is at its plan's route limit. The body also carries current and max |
Create Multiple Routes
Create up to 50 routes in a single request. Each entry takes the same fields as Create Route.
POST /api/routes/bulkRequest Body
| Field | Type | Required | Description |
|---|---|---|---|
| routes | array | Yes | Array of route objects (1-50). Each needs name, sourceId, and destinationId |
Tip
Fan-out means one route per destination. A route delivers to exactly one destination, so sending one source's events to three destinations means three routes. This endpoint creates them in a single request.
Example
One source fanned out to two Slack destinations:
curl -X POST https://api.hookbase.app/api/routes/bulk \
-H "Authorization: Bearer whr_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"routes": [
{
"name": "GitHub to Slack #dev",
"sourceId": "11111111-1111-4111-8111-111111111111",
"destinationId": "22222222-2222-4222-8222-222222222222"
},
{
"name": "GitHub to Slack #alerts",
"sourceId": "11111111-1111-4111-8111-111111111111",
"destinationId": "99999999-9999-4999-8999-999999999999"
}
]
}'Response
Each entry is reported independently, so a partial success is possible — and the response is
always 200, even when every entry failed. Check failed rather than the status code.
{
"results": [
{
"index": 0,
"success": true,
"route": {
"id": "44444444-4444-4444-8444-444444444444",
"name": "GitHub to Slack #dev",
"sourceId": "11111111-1111-4111-8111-111111111111",
"destinationId": "22222222-2222-4222-8222-222222222222",
"priority": 0,
"isActive": true
}
},
{
"index": 1,
"success": false,
"error": "name, sourceId, and destinationId are required"
}
],
"created": 1,
"failed": 1
}Each successful route is the same echo Create Route returns, plus the three
circuit* fields as sent.
Errors
Per-entry failures appear in results[].error and use the same messages as
Create Route. These reject the whole request:
| Status | Error | Cause |
|---|---|---|
| 400 | routes array is required | routes missing, not an array, or empty |
| 400 | Maximum 50 routes can be created at once | More than 50 entries |
| 403 | FEATURE_DISABLED | Any entry requests failover on a plan without it |
| 403 | LIMIT_EXCEEDED | The batch would exceed the plan's route limit. Nothing is created |
Get Route
GET /api/routes/{id}Example
curl https://api.hookbase.app/api/routes/44444444-4444-4444-8444-444444444444 \
-H "Authorization: Bearer whr_your_api_key"Response
{ "route": { ... } } wrapping a full Route Object — the flat joined
sourceName/destinationName/transformName/filterName/schemaName, not nested objects.
| Status | Error |
|---|---|
| 404 | Route not found |
Update Route
PATCH /api/routes/{id}Request Body
All fields are optional. Only fields present in the body are changed.
| Field | Type | Description |
|---|---|---|
| name | string | Display name |
| sourceId | string | Source to listen to |
| destinationId | string | The destination to deliver to |
| transformId | string | Transform ID (null to remove) |
| filterId | string | Filter ID (null to remove). Setting a filter clears any inline filterConditions |
| filterConditions | array | Inline conditions (empty array or null to remove). Setting them clears filterId |
| filterLogic | string | AND or OR, applied when filterConditions is non-empty |
| schemaId | string | Schema ID (null to remove) |
| priority | number | Route priority |
| isActive | boolean | Whether the route runs |
| notifyOnFailure | boolean | Notify on delivery failure |
| notifyOnSuccess | boolean | Notify on delivery success |
| notifyOnRecovery | boolean | Notify on recovery |
| notifyEmails | string | Comma-separated recipients (null or "" to clear) |
| failureThreshold | number | Consecutive failures before a failure notification |
| failoverDestinationIds | string[] | Backup destinations (max 3, empty array to clear) |
| failoverAfterAttempts | number | 1-5 |
| expectedResponse | object | Response validation (null to clear) |
Info
sourceId can be changed. Repointing a route at a different source invalidates the ingest
route cache for both the old and the new source, so the change takes effect immediately.
Warning
The circuit* fields are not accepted here. Use
PATCH /api/routes/{id}/circuit-config — sending them to this
endpoint is silently ignored.
Example
curl -X PATCH https://api.hookbase.app/api/routes/44444444-4444-4444-8444-444444444444 \
-H "Authorization: Bearer whr_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"isActive": false,
"filterId": null
}'Response
{
"success": true
}Warning
Update does not return the route. Follow it with GET /api/routes/{id} if you need the
updated object.
Errors
Same 400s as Create Route for reference validation, filter conflicts, and the failover rules, plus:
| Status | Error |
|---|---|
| 404 | Route not found |
Delete Route
DELETE /api/routes/{id}Info
Deleting a route does not delete the source, destination, transform, or filter.
Example
curl -X DELETE https://api.hookbase.app/api/routes/44444444-4444-4444-8444-444444444444 \
-H "Authorization: Bearer whr_your_api_key"Response
{
"success": true
}Deleting requires an API key with the delete scope.
| Status | Error |
|---|---|
| 404 | Route not found |
Failed Event Samples
Return a sample of recent events that failed to deliver on a route — useful for diagnosing a misconfigured destination or transform.
GET /api/routes/{id}/failed-event-samplesQuery Parameters
| Parameter | Type | Description |
|---|---|---|
| limit | number | Number of samples to return (default: 10, max: 25) |
Response
{
"samples": [
{
"deliveryId": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
"eventId": "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
"sourceId": "11111111-1111-4111-8111-111111111111",
"sourceName": "GitHub",
"sourceSlug": "github",
"eventType": "pull_request",
"receivedAt": "2026-01-06T10:24:00Z",
"deliveryCreatedAt": "2026-01-06T10:24:01Z",
"errorMessage": "HTTP 500: Internal Server Error",
"responseStatus": 500,
"payload": { "action": "opened" }
}
]
}Payloads are fetched from storage and pass through the source's masking rules, so a masked field appears redacted here.
Info
Per-route delivery metrics are available through the Analytics API
(GET /api/analytics/routes), not a per-route /stats endpoint.
Circuit Breaker Status
Get the current circuit breaker state for a route.
GET /api/routes/{id}/circuit-statusResponse
{
"circuitState": "closed",
"circuitOpenedAt": null,
"cooldownSeconds": 300,
"probeAttempts": 0,
"probeSuccessThreshold": 2,
"failureThreshold": 5,
"consecutiveFailures": 2,
"timeUntilProbeSeconds": 0
}timeUntilProbeSeconds counts down only while the circuit is open; it is 0 otherwise.
failureThreshold here is the circuit threshold (circuitFailureThreshold on the route), not
the notification failureThreshold.
Circuit States
| State | Description |
|---|---|
closed | Normal operation — deliveries are flowing |
open | Circuit tripped — deliveries are dead-lettered until the cooldown elapses |
half_open | Probing — sending test deliveries to check recovery |
Warning
The circuit breaker is a paid-plan feature. All three circuit endpoints return 403 on a plan without it. An open circuit dead-letters deliveries rather than activating failover — see the Failover Guide for how the two interact.
Reset Circuit Breaker
Manually reset the circuit breaker to closed state.
POST /api/routes/{id}/reset-circuitExample
curl -X POST \
https://api.hookbase.app/api/routes/44444444-4444-4444-8444-444444444444/reset-circuit \
-H "Authorization: Bearer whr_your_api_key"Response
{
"success": true,
"circuitState": "closed",
"previousState": "open"
}Resetting a circuit that is already closed succeeds, with previousState reporting closed.
Update Circuit Breaker Config
Update the circuit breaker configuration for a route without modifying other route settings.
PATCH /api/routes/{id}/circuit-configRequest Body
All optional; only fields present in the body are changed.
| Field | Type | Range | Description |
|---|---|---|---|
| circuitCooldownSeconds | number | 30-3600 | Seconds to wait before probing |
| circuitFailureThreshold | number | 1-50 | Failures to trip the circuit |
| circuitProbeSuccessThreshold | number | 1-10 | Successful probes to close circuit |
Example
curl -X PATCH \
https://api.hookbase.app/api/routes/44444444-4444-4444-8444-444444444444/circuit-config \
-H "Authorization: Bearer whr_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"circuitFailureThreshold": 10,
"circuitCooldownSeconds": 600
}'Response
{
"success": true
}The stored configuration is not echoed back; read it from
GET /api/routes/{id}/circuit-status.
Errors
| Status | Error |
|---|---|
| 400 | circuitCooldownSeconds must be between 30 and 3600 seconds |
| 400 | circuitFailureThreshold must be between 1 and 50 |
| 400 | circuitProbeSuccessThreshold must be between 1 and 10 |
| 403 | Circuit breaker not enabled for the plan |
| 404 | Route not found |
Tip
Keep circuitFailureThreshold above failoverAfterAttempts if you want failover to get a
chance before the circuit trips. See the Failover Guide.
Export Routes
Export routes as a portable JSON file. Uses slugs instead of IDs for cross-environment
compatibility, and responds with a Content-Disposition: attachment header naming the file
hookbase-routes-<org-slug>-<date>.json.
GET /api/routes/exportQuery Parameters
| Parameter | Type | Description |
|---|---|---|
| ids | string | Comma-separated route IDs to export (optional, exports all if omitted) |
Example
# Export all routes
curl https://api.hookbase.app/api/routes/export \
-H "Authorization: Bearer whr_your_api_key" \
-o routes.json
# Export specific routes
curl "https://api.hookbase.app/api/routes/export?ids=44444444-4444-4444-8444-444444444444" \
-H "Authorization: Bearer whr_your_api_key"Response
{
"version": "1.0",
"exportedAt": "2026-01-30T12:00:00Z",
"organizationSlug": "acme-corp",
"routes": [
{
"name": "Stripe to Slack",
"sourceSlug": "stripe-webhooks",
"destinationSlug": "slack-notifications",
"filterSlug": "payment-events",
"transformSlug": null,
"schemaSlug": null,
"filterConditions": null,
"filterLogic": "AND",
"priority": 10,
"isActive": true,
"notifyOnFailure": true,
"notifyOnSuccess": false,
"notifyOnRecovery": true,
"notifyEmails": "team@example.com",
"failureThreshold": 3,
"failoverDestinationSlugs": ["backup-webhook"],
"failoverAfterAttempts": 3,
"circuitCooldownSeconds": 300,
"circuitFailureThreshold": 5,
"circuitProbeSuccessThreshold": 2
}
]
}Unlike the Route Object, filterConditions here is a parsed array (or null) —
the export is meant to be read and edited by hand. A failover destination that has since been
deleted is omitted from failoverDestinationSlugs rather than exported as an ID no import could
resolve. expectedResponse is not part of the export format and is not carried across.
Tip
Export files use slugs instead of IDs. This makes them portable between environments. Create matching source and destination slugs in the target environment before importing.
Import Routes
Import routes from a JSON export file.
POST /api/routes/importRequest Body
| Field | Type | Required | Description |
|---|---|---|---|
| routes | array | Yes | Array of route objects from an export |
| conflictStrategy | string | Yes | How to handle name conflicts: skip, rename, or overwrite |
| validateOnly | boolean | No | If true, validates without creating routes (dry run) |
Conflict Strategies
| Strategy | Behavior |
|---|---|
skip | Keep existing route, don't import the duplicate |
rename | Import with suffix: "My Route" becomes "My Route (1)" |
overwrite | Replace the existing route's configuration in place, keeping its ID |
Example
# Validate import (dry run)
curl -X POST https://api.hookbase.app/api/routes/import \
-H "Authorization: Bearer whr_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"routes": [...],
"conflictStrategy": "skip",
"validateOnly": true
}'
# Import routes
curl -X POST https://api.hookbase.app/api/routes/import \
-H "Authorization: Bearer whr_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"routes": [...],
"conflictStrategy": "rename"
}'Response
imported and skipped are arrays of route names, and errors pairs a name with its reason.
An entry that fails does not stop the rest, so check summary.errors rather than the status code.
{
"success": true,
"imported": ["Stripe to Slack", "GitHub to Discord"],
"skipped": ["Legacy Webhook"],
"errors": [
{ "name": "Old Route", "error": "Destination 'backup-webhook' not found. Create it first." }
],
"summary": {
"total": 4,
"imported": 2,
"skipped": 1,
"errors": 1
}
}Dry Run Response
With validateOnly: true nothing is created and every entry is reported individually:
{
"success": true,
"validateOnly": true,
"validationResults": [
{
"index": 0,
"name": "Stripe to Slack",
"status": "valid",
"errors": [],
"warnings": []
},
{
"index": 1,
"name": "Old Route",
"status": "error",
"errors": ["Source 'stripe-webhooks' not found. Create it first."],
"warnings": []
},
{
"index": 2,
"name": "Legacy Webhook",
"status": "rename",
"errors": [],
"warnings": ["Route will be renamed to 'Legacy Webhook (1)'"],
"resolvedName": "Legacy Webhook (1)"
}
],
"summary": {
"total": 3,
"valid": 2,
"skipped": 0,
"overwrite": 0,
"errors": 1
}
}status is one of valid, error, skip, rename, or overwrite.
Validation
Every referenced slug is resolved against the target organization before anything is written:
| Message | Kind | Cause |
|---|---|---|
Route name is required | error | Missing or non-string name |
Source slug is required | error | Missing or non-string sourceSlug |
Destination slug is required | error | Missing or non-string destinationSlug |
Source '<slug>' not found. Create it first. | error | No source with that slug in this organization |
Destination '<slug>' not found. Create it first. | error | No destination with that slug |
Filter '<slug>' not found. Create it first. | error | No filter with that slug |
Transform '<slug>' not found. Create it first. | error | No transform with that slug |
Schema '<slug>' not found. Create it first. | error | No schema with that slug |
Failover destination(s) not found: <slugs> | error | A failoverDestinationSlugs entry has no destination |
Failover destinations will be ignored (feature not enabled for this plan) | warning | Failover requested on a plan without it — the route imports without it |
Circuit breaker settings will use defaults (feature not enabled for this plan) | warning | Non-default circuit settings on a plan without the feature |
Errors
| Status | Error | Cause |
|---|---|---|
| 400 | No routes provided for import | routes missing, not an array, or empty |
| 400 | Invalid conflict strategy. Must be skip, rename, or overwrite | Missing or unrecognized conflictStrategy |
| 403 | LIMIT_EXCEEDED | The import would exceed the plan's route limit. Nothing is imported; the body carries validationResults and summary |
Warning
All referenced sources, destinations, filters, transforms, and schemas must exist in the target organization before importing. Import resolves slugs, it does not create the resources behind them.
Bulk Pause / Resume Routes
Pause or resume multiple routes at once.
PATCH /api/routes/bulkRequest Body
| Field | Type | Required | Description |
|---|---|---|---|
| ids | string[] | Yes | Route IDs to update (max 100) |
| isActive | boolean | Yes | Set to false to pause, true to resume |
Example
# Pause multiple routes
curl -X PATCH https://api.hookbase.app/api/routes/bulk \
-H "Authorization: Bearer whr_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"ids": [
"44444444-4444-4444-8444-444444444444",
"55555555-5555-4555-8555-555555555555"
],
"isActive": false
}'Response
{
"success": true,
"updated": 2
}Errors
| Status | Error | Cause |
|---|---|---|
| 400 | ids array is required | ids missing, not an array, or empty |
| 400 | Maximum 100 items can be updated at once | More than 100 IDs |
| 400 | isActive boolean is required | isActive missing or not a boolean |
| 400 | One or more routes not found or do not belong to this organization | Any ID is unknown. Nothing is updated |
Bulk Delete Routes
Delete multiple routes at once.
DELETE /api/routes/bulkRequest Body
| Field | Type | Required | Description |
|---|---|---|---|
| ids | string[] | Yes | Route IDs to delete (max 100) |
Example
curl -X DELETE https://api.hookbase.app/api/routes/bulk \
-H "Authorization: Bearer whr_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"ids": [
"44444444-4444-4444-8444-444444444444",
"55555555-5555-4555-8555-555555555555"
]
}'Response
{
"success": true,
"deleted": 2
}Errors
| Status | Error | Cause |
|---|---|---|
| 400 | ids array is required | ids missing, not an array, or empty |
| 400 | Maximum 100 items can be deleted at once | More than 100 IDs |
| 400 | One or more routes not found or do not belong to this organization | Any ID is unknown. Nothing is deleted |
Danger
Bulk delete is irreversible. Export the routes first if you want a way back. Deleting
requires an API key with the delete scope.
Error Responses
Every error on this API is a JSON object with an error string carrying the message:
{
"error": "Route not found"
}Plan and feature errors add a code, and the limit errors also report the numbers:
{
"error": "Route limit reached for your plan",
"code": "LIMIT_EXCEEDED",
"current": 25,
"max": 25
}| Code | Status | Meaning |
|---|---|---|
LIMIT_EXCEEDED | 403 | At the plan's route limit |
FEATURE_DISABLED | 403 | Failover requested on a plan without it |
Info
There is no message field and no duplicate-route conflict. Nothing on this API returns 409 —
two routes may share a source and destination, which is how you fan out with different filters.
Read the message from error.