Getting started Authentication Send the one-time API key as a Bearer token. The key is automatically limited to the account that created it, so requests never include an account or tenant ID.
Authorization: Bearer hbe_your_key
Content-Type: application/jsonAvailable services only The reference lists the current public catalog. A hidden service route returns not_found to customer keys even when an older key still carries its scope.
Email confirmation required The account must confirm its email before any API or sandbox request can run; otherwise every endpoint returns email_verification_required.
Save keys once Handlebe cannot reveal a plaintext key again. Rotate it before revoking an old credential.
Authorization Scopes A matching scope permits the route family, while the key creator’s current account permission remains authoritative.
links.readRead links, targeting rules, and the safe sandbox response.
links.writeCreate and publish links, reserve and change drafts, add targets, change lifecycle state, and soft-delete links.
analytics.readRead account and link analytics and export aggregate CSV data.
webhooks.readRead link webhook summaries and delivery history without signing material.
webhooks.writeRegister and disable link webhook endpoints and retry failed deliveries.
subdomains.readList branded subdomains and selectable Enterprise root domains. Developer keys cannot change them.
allUse every developer capability above without opening browser-only, account, billing, or platform routes. GET List links /linksPOST Create and publish a link /linksPOST Create a draft /links/draftsGET Get a link /links/{id}PUT Update a link /links/{id}POST Publish a draft /links/{id}/publishPOST Change lifecycle state /links/{id}/{action}DELETE Delete a link /links/{id}GET List targeted destinations /links/{link_id}/targetsPOST Create a targeted destination /links/{link_id}/targets
Request fields statusquery string optional Filter by draft, published, disabled, or archived.
searchquery string optional Search display names, paths, and destinations.
pagequery integer optional Page number; defaults to 1.
per_pagequery integer optional Page size bounded by the server.
Example request LanguageCurl JavaScript PHP Python Java C# Go Rust Success response {
"links": [
{
"id": "00000000-0000-0000-0000-000000000000",
"path": "example",
"visits": 0
}
],
"page": 1,
"per_page": 25,
"total": 1
}Possible errors 401authentication_required 403forbidden Try it against this environment Request fields destination_urlbody absolute URL required The destination checked before publication.
display_namebody string ≤ 255 optional A private label for the workspace.
custom_pathbody string 1–150 optional A plan-gated public path.
expires_atbody RFC 3339 timestamp optional An optional expiration time.
subdomain_idbody UUID optional Enterprise-only active branded prefix owned by the account. Omit to use the account default whenever an active branded prefix exists.
short_domain_idbody UUID optional Enterprise-only active domain allowed for the plan; omit to use weighted allocation.
Example request LanguageCurl JavaScript PHP Python Java C# Go Rust Success response {
"link": {
"destination_url": "https://example.test/product",
"id": "00000000-0000-0000-0000-000000000000",
"path": "generated-path",
"public_host": "handlebe.com",
"short_domain_host": "handlebe.com",
"short_url": "https://handlebe.com/generated-path",
"status": "published"
}
}Possible errors 401authentication_required 403forbidden 422email_verification_required 422payment_method_required 422plan_required 422safety_pending 422unsafe_destination 422billing_blocked 422monthly_credit_exhausted 422spending_limit_exhausted 409conflict 422validation_failed Try it against this environment Request fields destination_urlbody absolute URL required The primary destination checked again at publication.
display_namebody string ≤ 255 optional A private label for the workspace.
custom_pathbody string 1–150 optional A plan-gated public path.
expires_atbody RFC 3339 timestamp optional An optional expiration time.
subdomain_idbody UUID optional Enterprise-only active branded prefix owned by the account.
short_domain_idbody UUID optional Enterprise-only active domain allowed for the plan.
Example request LanguageCurl JavaScript PHP Python Java C# Go Rust Success response {
"link": {
"destination_url": "https://example.test/product",
"id": "00000000-0000-0000-0000-000000000000",
"path": "generated-path",
"short_url": "https://handlebe.com/generated-path",
"status": "draft"
}
}Possible errors 401authentication_required 403forbidden 422plan_required 422billing_blocked 409conflict 422validation_failed Try it against this environment Requests use this page origin under /api and may affect live data available to your key.
JSON body editable {
"destination_url": "https://example.test/product",
"display_name": "Staged campaign"
} Send POST request Request fields idpath UUID required The owned link identifier.
Example request LanguageCurl JavaScript PHP Python Java C# Go Rust Success response {
"destination_url": "https://example.test/product",
"id": "00000000-0000-0000-0000-000000000000",
"path": "generated-path",
"status": "draft"
}Possible errors 401authentication_required 403forbidden 404not_found Try it against this environment Requests use this page origin under /api and may affect live data available to your key.
id path · required Send GET request Request fields idpath UUID required The owned link identifier.
destination_urlbody absolute URL optional Accepted only while the destination remains editable.
display_namebody string ≤ 255 optional A private label.
expires_atbody RFC 3339 timestamp optional A replacement expiration time.
Example request LanguageCurl JavaScript PHP Python Java C# Go Rust Success response {
"link": {
"display_name": "Summer campaign",
"id": "00000000-0000-0000-0000-000000000000"
}
}Possible errors 401authentication_required 403forbidden 404not_found 409conflict 422validation_failed Try it against this environment Requests use this page origin under /api and may affect live data available to your key.
id path · required JSON body editable {
"display_name": "Summer campaign"
} Send PUT request POST /links/{id}/publish
Publish a draft Publish after email, card, budget, plan, and destination-safety gates pass.
Required scope links.write Request fields idpath UUID required The draft identifier.
Example request LanguageCurl JavaScript PHP Python Java C# Go Rust Success response {
"link": {
"id": "00000000-0000-0000-0000-000000000000",
"status": "published"
}
}Possible errors 401authentication_required 422payment_method_required 403forbidden 404not_found 409conflict 422email_verification_required 422safety_pending 422unsafe_destination 429billing_blocked Try it against this environment Requests use this page origin under /api and may affect live data available to your key.
id path · required I understand this request may change live data or consume usage.Send POST request Request fields idpath UUID required The owned link identifier.
actionpath disable | archive | restore required The requested lifecycle transition.
Example request LanguageCurl JavaScript PHP Python Java C# Go Rust Success response {
"link": {
"id": "00000000-0000-0000-0000-000000000000",
"status": "archived"
}
}Possible errors 401authentication_required 403forbidden 404not_found 409conflict Try it against this environment Requests use this page origin under /api and may affect live data available to your key.
id path · required action path · required I understand this request may change live data or consume usage.Send POST request Request fields idpath UUID required The owned link identifier.
Example request LanguageCurl JavaScript PHP Python Java C# Go Rust Success response {
"message": "Link deleted successfully"
}Possible errors 401authentication_required 403forbidden 404not_found Try it against this environment Requests use this page origin under /api and may affect live data available to your key.
id path · required I understand this request may change live data or consume usage.Send DELETE request Request fields link_idpath UUID required The owning link identifier.
Example request LanguageCurl JavaScript PHP Python Java C# Go Rust Possible errors 401authentication_required 403forbidden 404not_found Try it against this environment Requests use this page origin under /api and may affect live data available to your key.
link_id path · required Send GET request POST /links/{link_id}/targets
Create a targeted destination Add a plan-gated routing rule and return its completed destination-safety verdict.
Required scope links.write Request fields link_idpath UUID required The owning link identifier.
prioritybody integer 0–32767 required Evaluation order for this rule.
destination_urlbody absolute URL required The alternate destination.
osbody string 1–32 required Operating-system match or Any.
browserbody string 1–32 required Browser match or Any.
Example request LanguageCurl JavaScript PHP Python Java C# Go Rust Success response {
"target": {
"id": "00000000-0000-0000-0000-000000000000",
"safety_verdict": "safe"
}
}Possible errors 401authentication_required 422plan_required 404not_found 422validation_failed Try it against this environment Requests use this page origin under /api and may affect live data available to your key.
link_id path · required JSON body editable {
"browser": "Any",
"destination_url": "https://example.test/mobile",
"os": "iOS",
"priority": 0
} I understand this request may change live data or consume usage.Send POST request GET Account summary /analytics/summaryGET Link summary /analytics/links/{id}GET Export analytics CSV /analytics/export.csv
GET /analytics/summary
Account summary Return current and previous redirect totals, daily buckets, recent links, and coarse location groups; country code ZZ keeps redirects whose location is unavailable visible.
Required scope analytics.read Request fields daysquery integer 1–366 optional Server-bounded reporting window; defaults to 30.
Example request LanguageCurl JavaScript PHP Python Java C# Go Rust Success response {
"current": {
"active_links": 1,
"unique_visitors": 1,
"visits": 1
},
"delivery_activity": [],
"geographies": [
{
"country_code": "ZZ",
"region_code": null,
"visits": 1
}
],
"previous": {
"active_links": 0,
"unique_visitors": 0,
"visits": 0
},
"recent_links": []
}Possible errors 401authentication_required 403forbidden Try it against this environment Requests use this page origin under /api and may affect live data available to your key.
days query · optional Send GET request Request fields idpath UUID required The owned link identifier.
daysquery integer 1–366 optional Server-bounded reporting window; defaults to 30.
Example request LanguageCurl JavaScript PHP Python Java C# Go Rust Success response {
"current": {
"active_links": 0,
"unique_visitors": 0,
"visits": 0
},
"delivery_activity": [],
"geographies": [],
"previous": {
"active_links": 0,
"unique_visitors": 0,
"visits": 0
},
"recent_links": []
}Possible errors 401authentication_required 403forbidden 404not_found Try it against this environment Requests use this page origin under /api and may affect live data available to your key.
id path · required days query · optional Send GET request Request fields daysquery integer 1–366 optional Server-bounded reporting window; defaults to 30.
Example request LanguageCurl JavaScript PHP Python Java C# Go Rust Success response occurred_at,short_link_id,browser,os,country_code,region_code
2026-07-18T12:00:00Z,00000000-0000-0000-0000-000000000000,Chrome,Linux,US,ILPossible errors 401authentication_required 403forbidden Try it against this environment Requests use this page origin under /api and may affect live data available to your key.
days query · optional Send GET request Outbound request Payload sent to your endpoint After each recorded visit, the worker POSTs this JSON request to the registered endpoint_url. The link_id and webhook_id path values below identify management records; they are not the outbound request body.
Headers sent to your endpoint Content-Typeheader application/json sent The body is JSON encoded.
X-{product-slug}-Eventheader visit.created sent The event name; product-slug is the safe lowercase slug derived from the configured product name.
X-{product-slug}-Event-Idheader same UUID as event_id sent Use this stable identifier to ignore duplicate retry deliveries.
X-{product-slug}-Signatureheader sha256=<lowercase hexadecimal HMAC> sent HMAC-SHA256 of the exact raw request body using the one-time signing secret.
JSON body fields eventbody string sent Always visit.created for the currently supported delivery.
event_idbody UUID sent The idempotency identifier retained across every retry.
short_link_idbody UUID sent The visited link that owns the registered endpoint.
{
"event": "visit.created",
"event_id": "00000000-0000-0000-0000-000000000002",
"short_link_id": "00000000-0000-0000-0000-000000000003"
}GET List link webhooks /links/{link_id}/webhooksPOST Register a webhook /links/{link_id}/webhooksGET List webhook deliveries /links/{link_id}/webhooks/{webhook_id}/deliveriesPOST Retry a webhook delivery /links/{link_id}/webhooks/{webhook_id}/deliveries/{delivery_id}/retryPOST Disable a webhook /links/{link_id}/webhooks/{webhook_id}/disable
GET /links/{link_id}/webhooks
List link webhooks Return safe endpoint summaries without encrypted or plaintext signing material; an unknown or unowned link returns an empty list.
Required scope webhooks.read Request fields link_idpath UUID required The owning link identifier.
Example request LanguageCurl JavaScript PHP Python Java C# Go Rust Success response [
{
"endpoint_url": "https://hooks.example.test/visits",
"id": "00000000-0000-0000-0000-000000000000",
"status": "active"
}
]Possible errors 401authentication_required 403forbidden Try it against this environment Requests use this page origin under /api and may affect live data available to your key.
link_id path · required Send GET request POST /links/{link_id}/webhooks
Register a webhook Register a plan-gated public HTTPS endpoint and reveal its signing secret in this response only.
Required scope webhooks.write Request fields link_idpath UUID required The owned link that emits visit events.
endpoint_urlbody public HTTPS URL required A DNS-resolved endpoint that must not reach a private or internal network.
Example request LanguageCurl JavaScript PHP Python Java C# Go Rust Success response {
"signing_secret": "save-this-one-time-secret",
"webhook": {
"endpoint_url": "https://example.com/handlebe-events",
"id": "00000000-0000-0000-0000-000000000000",
"status": "active"
}
}Possible errors 401authentication_required 403forbidden 404not_found 422plan_required 422unsafe_destination 422validation_failed Try it against this environment Requests use this page origin under /api and may affect live data available to your key.
link_id path · required JSON body editable {
"endpoint_url": "https://example.com/handlebe-events"
} I understand this request may change live data or consume usage.Send POST request GET /links/{link_id}/webhooks/{webhook_id}/deliveries
List webhook deliveries Return up to 100 recent queued, successful, retrying, and dead-letter outcomes; unknown or unowned identifiers return an empty list.
Required scope webhooks.read Request fields link_idpath UUID required The owning link identifier.
webhook_idpath UUID required The owned webhook identifier.
Example request LanguageCurl JavaScript PHP Python Java C# Go Rust Success response [
{
"attempts": 1,
"event_id": "00000000-0000-0000-0000-000000000002",
"id": "00000000-0000-0000-0000-000000000001",
"payload": {
"event": "visit.created",
"event_id": "00000000-0000-0000-0000-000000000002",
"short_link_id": "00000000-0000-0000-0000-000000000003"
},
"response_status": 200,
"status": "delivered"
}
]Possible errors 401authentication_required 403forbidden Try it against this environment Requests use this page origin under /api and may affect live data available to your key.
link_id path · required webhook_id path · required Send GET request POST /links/{link_id}/webhooks/{webhook_id}/deliveries/{delivery_id}/retry
Retry a webhook delivery Queue one owned retrying or dead-letter delivery immediately without changing its event ID.
Required scope webhooks.write Request fields link_idpath UUID required The owning link identifier.
webhook_idpath UUID required The owned webhook identifier.
delivery_idpath UUID required A failed delivery identifier.
Example request LanguageCurl JavaScript PHP Python Java C# Go Rust Success response {
"message": "Webhook delivery queued for retry"
}Possible errors 401authentication_required 403forbidden 404not_found Try it against this environment Requests use this page origin under /api and may affect live data available to your key.
link_id path · required webhook_id path · required delivery_id path · required I understand this request may change live data or consume usage.Send POST request POST /links/{link_id}/webhooks/{webhook_id}/disable
Disable a webhook Stop future sends and move pending deliveries to dead letter while keeping history readable.
Required scope webhooks.write Request fields link_idpath UUID required The owning link identifier.
webhook_idpath UUID required The active webhook identifier.
Example request LanguageCurl JavaScript PHP Python Java C# Go Rust Success response {
"message": "Webhook disabled"
}Possible errors 401authentication_required 403forbidden 404not_found Try it against this environment Requests use this page origin under /api and may affect live data available to your key.
link_id path · required webhook_id path · required I understand this request may change live data or consume usage.Send POST request GET List subdomains /subdomainsGET List selectable short domains /short-domains/selectable
Example request LanguageCurl JavaScript PHP Python Java C# Go Rust Success response [
{
"id": "00000000-0000-0000-0000-000000000000",
"is_default": true,
"label": "acme",
"status": "active"
}
]Possible errors 401authentication_required 403forbidden Try it against this environment Requests use this page origin under /api and may affect live data available to your key.
Send GET request GET /short-domains/selectable
List selectable short domains List active root domains allowed for Enterprise manual selection. A zero-percent domain remains selectable but receives no automatic traffic; inactive and plan-disallowed domains are omitted.
Required scope subdomains.read
Example request LanguageCurl JavaScript PHP Python Java C# Go Rust Success response [
{
"effective_percent": 30,
"hostname": "handlebe.com",
"id": "00000000-0000-0000-0000-000000000000"
}
]Possible errors 401authentication_required 403forbidden Try it against this environment Requests use this page origin under /api and may affect live data available to your key.
Send GET request GET Preview the link shape /sandbox/links
Example request LanguageCurl JavaScript PHP Python Java C# Go Rust Success response {
"link": {
"creation_channel": "api_sandbox",
"status": "draft"
},
"sandbox": true
}Possible errors 401authentication_required 403forbidden Try it against this environment Requests use this page origin under /api and may affect live data available to your key.
Send GET request Error contract Stable errors Every JSON failure uses one envelope. Branch on error.code; the human message may become clearer over time.
{
"success": false,
"error": { "code": "payment_method_required", "message": "Add a verified card before publishing." }
}Available codes include authentication_required, forbidden, validation_failed, not_found, conflict, email_verification_required, payment_method_required, billing_blocked, safety_pending, unsafe_destination, plan_required.