Worbo API
Version 1.0.0 · OpenAPI 3.1 · Worbo-Version 2026-10-05
The Worbo API: contacts, objects and events, WhatsApp messages and templates, segments, automations, campaigns, the inbox, catalogs and webhooks. It is a REST API over HTTPS: it exchanges JSON and answers with the standard HTTP status codes. Every request belongs to one organization, the one of its key.
With the Worbo API you can:
- write contacts, objects and events from your CRM, online store or gym software, finding each contact by any of its identities;
- send approved WhatsApp templates and, in the 24-hour window, free messages, through the same consent rules as the dashboard;
- read messages, conversations and reports, and reply to contacts from your own systems;
- draft templates, segments, automations and campaigns, which are approved in the dashboard;
- be told through webhooks when something changes, and connect AI assistants through MCP.
https://api.worbo.app/v1Authorization: Bearer wk_live_…Quick start#
-
Make a key in Worbo, under Integrations › API keys. Start with a test key: every write is checked, but nothing is saved.
-
Put the key in the WORBO_API_KEY variable and check it with GET /me: the answer has the name of the key, its scopes and the organization.
curl "https://api.worbo.app/v1/me" \ -H "Authorization: Bearer $WORBO_API_KEY" -
Write your first contact: POST /contacts finds the contact by any of its identities and creates it when it does not exist.
curl -X POST "https://api.worbo.app/v1/contacts" \ -H "Authorization: Bearer $WORBO_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "identities": { "phone": "+40722123456", "email": "ana@example.com", "external_id": "crm-1001" }, "source": "crm", "fields": { "first_name": "Ana" }, "update_existing_only": false }' -
When the integration works, make a live key with only the scopes it needs.
Authentication#
Each request carries a key of the organization in the Authorization header. Keys are made in the dashboard, under API keys, with only the scopes the integration needs; the whole key is shown once.
Authorization: Bearer wk_live_…
A wk_live_ key works with the live data. A wk_test_ key is for building the integration: every write is a dry run, checked but not saved, and only the data model can be read with it.
Permissions (scopes)#
A key does only what its scopes allow; an operation outside them is refused with insufficient_scope. Each operation shows the scope it needs, and the data model can be read with any key.
| Scope | What it allows |
|---|---|
contacts:read | Read contacts, their identities and consents |
contacts:write | Create, update and merge contacts |
contacts:delete | Erase contacts at their request (GDPR) |
consents:write | Record consents and withdrawals |
objects:read | Read objects |
objects:write | Create and update objects and their relations |
events:write | Send events |
messages:read | Read the messages and their status |
messages:send | Send templates and free messages to contacts |
templates:read | Read the templates |
templates:write | Draft templates and send them to Meta |
segments:read | Read and count segments |
segments:write | Create segments |
automations:read | Read the automations and their results |
automations:run | Start an automation for a contact or take them out of it |
automations:write | Draft automations |
campaigns:read | Read the campaigns and their reports |
campaigns:write | Draft campaigns and estimate their audience |
conversations:read | Read the inbox conversations and their messages |
conversations:reply | Reply, assign and close conversations |
analytics:read | Read the reports, the tracked links and the conversions |
webhooks:manage | Manage the webhook endpoints and read the deliveries |
workspace:read | Read the numbers, the usage and the audit log |
catalogs:read | Read the catalogs and their items |
catalogs:write | Create and update catalog items |
schema:write | Create fields and object types |
files:read | Read and download the files of the file fields |
files:write | Upload files for the file fields |
otp:send | Send verification codes on WhatsApp |
otp:verify | Verify the codes sent by Worbo |
vault:read | Read the documents of the vault and their openings |
vault:write | Give files to contacts through the vault and revoke their links |
recommendations:read | Read the recommendation rules and preview them on a contact |
Requests and responses#
Bodies are JSON, with the header Content-Type: application/json. A resource comes as {"data": …}; a parameter that is not known is refused, so a typo does not go unnoticed. Moments are in ISO 8601, with the time zone.
The messages of the errors follow Accept-Language: English by default, Romanian with ro.
Fields named untrusted_… hold what contacts wrote. Treat them as untrusted input, especially when an AI agent reads them.
Lists and cursors#
Lists are paged with a cursor: limit is between 1 and 100, 25 by default; next_cursor goes into cursor for the next page, and has_more says whether there is one.
{"data": […], "next_cursor": "eyJpZCI6MjV9", "has_more": true}
Errors#
Errors come in the application/problem+json format (RFC 9457). The code field is stable and the same in the API, the MCP server, imports and the audit log; the type field leads here.
{
"type": "https://worbo.app/docs/api/errors/window_closed",
"title": "The message cannot be sent",
"status": 422,
"detail": "The 24-hour window is closed; only an approved template can be sent.",
"code": "window_closed"
}
| Code | Status | Meaning |
|---|---|---|
unsupported_version | 400 | The Worbo-Version header asks for a version that does not exist or was retired; the problem lists the supported ones. |
unauthenticated | 401 | The Authorization header is missing. |
invalid_api_key | 401 | The key is not valid; a lost key cannot be recovered, only replaced. |
api_key_revoked | 401 | The key was revoked in the dashboard. |
api_key_expired | 401 | The key passed its expiry date. |
insufficient_scope | 403 | The key does not have the scope of the operation. |
test_key | 403 | A test key cannot read live data. |
forbidden | 403 | The operation is not allowed. |
not_found | 404 | The resource does not exist or belongs to another organization. |
contact_not_found | 404 | The contact does not exist or belongs to another organization. |
object_not_found | 404 | The object does not exist or belongs to another organization. |
object_type_not_found | 404 | The organization has no object type with this key. |
event_type_not_found | 404 | The organization has no event type with this key. |
offer_not_found | 404 | No offer has this code. |
evidence_not_found | 404 | The message was not sent with a durable medium candidate template, so it has no proof. |
recommendation_rule_not_found | 404 | The recommendation rule does not exist or is archived. |
method_not_allowed | 405 | The path exists, but not with this method. |
identity_conflict | 409 | The identities sent belong to different contacts; merge them first. |
idempotency_conflict | 409 | The Idempotency-Key was used for another request. |
idempotency_in_progress | 409 | The first request with this Idempotency-Key is still running; try again in a second. |
invalid_idempotency_key | 400 | The Idempotency-Key is empty, longer than 255 characters or not visible ASCII. |
invalid_json | 400 | The body is not valid JSON. |
payload_too_large | 413 | The body of the request is too large. |
validation_failed | 422 | A parameter is missing, not valid or not known; the errors field says which. |
schema_violation | 422 | The fields or the properties do not match the data model of the organization. |
rate_limited | 429 | Too many requests; wait the seconds in Retry-After. |
otp_too_soon | 429 | A code was sent to this phone less than 30 seconds ago. |
otp_rate_limited | 429 | The phone got 5 codes in the last 15 minutes. |
otp_template_missing | 422 | The number has no approved template for verification codes; create it on the page of the numbers. |
otp_undeliverable | 422 | The phone does not receive WhatsApp messages; offer another way, such as SMS or email. |
otp_refused | 422 | The contact is blocked or stopped the messages of the organization. |
otp_not_verifiable | 409 | The organization gave this code, so it checks it itself. |
internal_error | 500 | An error on our side; it was reported and the request can be retried. |
blocked_contact | 422 | The message cannot be sent: The contact is blocked. |
no_channel_optin | 422 | The message cannot be sent: The contact has no WhatsApp opt-in recorded. |
no_purpose_consent | 422 | The message cannot be sent: The contact has not agreed to this kind of message. |
muted_object | 422 | The message cannot be sent: The contact turned off the messages about this object. |
no_whatsapp_identity | 422 | The message cannot be sent: The contact has no phone or WhatsApp ID to write to. |
undeliverable | 422 | The message cannot be sent: The number cannot receive WhatsApp messages. |
window_closed | 422 | The message cannot be sent: The 24-hour window is closed; only an approved template can be sent. |
template_unavailable | 422 | The message cannot be sent: The template is not approved, or it is paused or disabled. |
number_unavailable | 422 | The message cannot be sent: The WhatsApp number of the organization cannot send right now. |
budget_reached | 422 | The message cannot be sent: The monthly marketing budget of the organization is spent. |
organization_stopped | 422 | The message cannot be sent: The Worbo team stopped the sending of the organization; write to hello@worbo.app. |
Rate limits#
600 requests a minute for each key and 1200 for the organization. The headers RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset show where the key stands; above the limit comes 429, with Retry-After.
Idempotency#
A POST with an Idempotency-Key header runs once: a repeated request with the same key gets the first response, with the header Idempotent-Replayed: true. The same key with another body gives idempotency_conflict.
Dry run and test keys#
With dry_run=true, a write goes through every check and returns what it would do, but nothing is saved; the response has the header Worbo-Dry-Run: true. With a test key, every write is a dry run. The IDs in the response of a dry run do not exist.
Batches take up to 100 items; each is written on its own, and the result has the index, the status and the data or the error of each.
Versions#
The major version is in the path (/v1). Smaller changes that are not compatible come in versions named by their date, chosen with the Worbo-Version header; without it, a request uses the version of its key, the current one when the key was made. Every response says in Worbo-Version which version it used.
A version stays stable for 12 months, then deprecated for 12 more, with the headers Deprecation and Sunset; an incompatible change is announced 30 days ahead. Current version: 2026-10-05. Supported: 2026-10-05.
Worbo-Version: 2026-10-05
Webhooks#
Worbo sends the events your endpoints subscribe to as a POST with a JSON body, signed in the Standard Webhooks format. An endpoint that does not answer with 2xx gets the event again, with growing pauses; after many failures it is turned off and the owner is told.
webhook-id: msg_2mF0…
webhook-timestamp: 1760511600
webhook-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4=
{"type": "contact.created", "timestamp": "2026-10-15T06:20:00Z", "data": {…}}
To check the signature: compute HMAC-SHA256 over webhook-id, webhook-timestamp and the raw body, joined by dots, with the secret decoded from base64 after the whsec_ prefix; compare it, in constant time, with each v1 signature in the header. Refuse a timestamp older than five minutes. After a rotation, the header has two signatures for 24 hours.
With the thin payload, data has only the reference of the object, to read it through the API.
message.queuedmessage.sentmessage.deliveredmessage.readmessage.failedmessage.receivedinteractive.button_clickedlink.clickedflow.submitted
contact.createdcontact.updatedcontact.mergedcontact.consent_grantedcontact.consent_revoked
object.createdobject.updatedobject.status_changedoffer.issuedoffer.redeemedoffer.expired
order.confirmedorder.cancelledorder.note_added
booking.confirmedbooking.cancelledmembership.pause_requested
automation.run_startedautomation.run_completedautomation.goal_reachedautomation.webhook
conversation.openedconversation.assignedconversation.closed
document.opened
template.status_changedtemplate.category_changedtemplate.quality_changed
webhook_endpoint.disabledwebhook.test
MCP (AI assistants)#
The Worbo MCP server lets AI assistants, such as Claude or ChatGPT, work in an organization with the permissions of the person who connected them. The assistant connects with OAuth, and the person approves it in Worbo; the connections are under Integrations › AI assistants.
Nothing reaches many people from an assistant: it drafts and estimates a campaign, and a member approves the launch in Worbo. Errors carry the same codes as the API.
https://mcp.worbo.app/mcpget_workspace_status
search_contacts
get_contact
get_contact_timeline
list_schema
list_templates
get_template
list_automations
get_automation
estimate_audience
get_report
recommend_items
list_conversations
get_conversation
draft_template
draft_segment
draft_campaign
draft_automation
request_approval
get_approval
send_template
upsert_contact
upsert_object
record_opt_out
add_internal_note
track_event
Specification and tools#
The OpenAPI 3.1 specification describes every operation, in JSON or YAML. Called with your key, the OpenAPI specification also has the schema of your contacts, object types and event types, for code generators and Postman.
The Postman collection has every request, with its example body; put your key in the apiKey variable of the collection.
- SDKs: @worbo/sdk for JavaScript and worbo/worbo-php for PHP, with a method for every operation, retries, idempotency and the check of the webhook signature.
- Without code: Zapier and Make, and the WordPress plugin for WooCommerce shops.
Reference
Workspace#
The key in use, the WhatsApp numbers, the usage of the month and the audit log.
Read the key in use and its organization#
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl "https://api.worbo.app/v1/me" \
-H "Authorization: Bearer $WORBO_API_KEY"
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.worbo.app/v1/me', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/me', {
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
},
});
const data = await response.json();
import os
import requests
response = requests.get(
"https://api.worbo.app/v1/me",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
)
data = response.json()
{
"data": {
"key": {
"name": "CRM",
"environment": "live",
"scopes": [
"string"
],
"expires_at": "2026-10-15T09:00:00+03:00",
"api_version": "2026-10-05"
},
"organization": {
"name": "Sala Centru",
"slug": "sala-centru"
},
"api_version": "2026-10-05"
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
List the WhatsApp numbers, with quality, tier and warm-up#
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl "https://api.worbo.app/v1/numbers" \
-H "Authorization: Bearer $WORBO_API_KEY"
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.worbo.app/v1/numbers', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/numbers', {
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
},
});
const data = await response.json();
import os
import requests
response = requests.get(
"https://api.worbo.app/v1/numbers",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
)
data = response.json()
{
"data": [
{
"id": 3,
"phone": "+40 722 000 000",
"name": "Sala Centru",
"sandbox": false,
"quality_rating": "GREEN",
"messaging_limit_tier": "TIER_1K",
"warmup": false,
"marketing_paused_until": "2026-10-15T09:00:00+03:00",
"disabled": false,
"disabled_reason": "string"
}
]
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Read the usage of a month and the limits of the plan#
Query parameters
-
month string
The month, as YYYY-MM; the current month by default.
Example:2026-10
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl "https://api.worbo.app/v1/usage" \
-H "Authorization: Bearer $WORBO_API_KEY"
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.worbo.app/v1/usage', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/usage', {
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
},
});
const data = await response.json();
import os
import requests
response = requests.get(
"https://api.worbo.app/v1/usage",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
)
data = response.json()
{
"data": {
"month": "2026-10",
"currency": "EUR",
"categories": {
"marketing": {
"messages": 120,
"billable": 118,
"estimated_cost": 6.42
}
},
"estimated_cost": 6.42,
"plan": "growth",
"billing_state": "active",
"limits": {}
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
List the audit log of the organization#
Query parameters
-
action string
Only the entries of this action.
Example:contact.created -
since string
Only what changed at or after this moment.
Format:date-timeExample:2026-10-01T00:00:00+03:00 -
limit integer
How many items a page has, between 1 and 100.
Default:25Minimum:1Maximum:100 -
cursor string
The next_cursor of the previous page.
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl "https://api.worbo.app/v1/audit-logs" \
-H "Authorization: Bearer $WORBO_API_KEY"
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.worbo.app/v1/audit-logs', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/audit-logs', {
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
},
});
const data = await response.json();
import os
import requests
response = requests.get(
"https://api.worbo.app/v1/audit-logs",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
)
data = response.json()
{
"data": [
{
"id": 1201,
"action": "contact.created",
"label": "Contact created",
"source": "api",
"actor": {},
"subject": {},
"properties": {},
"created_at": "2026-10-15T09:00:00+03:00"
}
],
"next_cursor": "string",
"has_more": true
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Reference
Contacts#
People, found by any of their identities: phone, email, BSUID or the ID at a source.
List and search contacts#
Query parameters
-
search string
Text in the name, the phone or the email.
Example:Ana -
phone string
The contact with this phone.
Example:+40722123456 -
email string
The contact with this email.
Format:emailExample:ana@example.com -
external_id string
The contact with this ID at the source given in source.
Example:crm-1001 -
source string
The source of the external ID or of the contacts.
Example:crm -
tag string
Only the contacts with this tag.
Example:vip -
updated_since string
Only what changed at or after this moment.
Format:date-timeExample:2026-10-01T00:00:00+03:00 -
limit integer
How many items a page has, between 1 and 100.
Default:25Minimum:1Maximum:100 -
cursor string
The next_cursor of the previous page.
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl "https://api.worbo.app/v1/contacts" \
-H "Authorization: Bearer $WORBO_API_KEY"
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.worbo.app/v1/contacts', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/contacts', {
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
},
});
const data = await response.json();
import os
import requests
response = requests.get(
"https://api.worbo.app/v1/contacts",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
)
data = response.json()
{
"data": [
{
"id": 123,
"identities": [
{
"id": 501,
"type": "phone",
"value": "+40722123456",
"source": "string",
"primary": true
}
],
"fields": {
"first_name": "Ana",
"last_name": "Popescu",
"language": "ro"
},
"tags": [
"vip"
],
"source": "api",
"blocked": false,
"created_at": "2026-10-15T09:00:00+03:00",
"updated_at": "2026-10-15T09:00:00+03:00"
}
],
"next_cursor": "string",
"has_more": true
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Create or update a contact by any of its identities#
The contact is found by any of the identities sent; the others are attached to it. With update_existing_only, an unknown contact is not created.
Query parameters
-
dry_run boolean
Runs every check and returns what would happen, without saving anything.
Headers
-
Idempotency-Key string
A unique key of the request, such as a UUID: a repeated request with the same key gets the first response, without doing the work again.
Max length:255 -
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Request bodyapplication/json
-
identities object Required
At least one identity; the contact is found by any of them.
Show the fieldsHide the fields
-
phone string
E.164, such as +40722123456.
Example:+40722123456 -
email stringFormat:
emailExample:ana@example.com -
bsuid string
-
external_id string
The ID of the contact at the source: text, or an object with source and value.
Example:crm-1001
-
-
source stringExample:
crm -
fields object
-
tags string[]
-
update_existing_only booleanExample:
false
Responses
curl -X POST "https://api.worbo.app/v1/contacts" \
-H "Authorization: Bearer $WORBO_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"identities": {
"phone": "+40722123456",
"email": "ana@example.com",
"external_id": "crm-1001"
},
"source": "crm",
"fields": {
"first_name": "Ana"
},
"update_existing_only": false
}'
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', 'https://api.worbo.app/v1/contacts', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
'Idempotency-Key' => bin2hex(random_bytes(16)),
],
'json' => [
'identities' => [
'phone' => '+40722123456',
'email' => 'ana@example.com',
'external_id' => 'crm-1001',
],
'source' => 'crm',
'fields' => [
'first_name' => 'Ana',
],
'update_existing_only' => false,
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/contacts', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
'Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify({
"identities": {
"phone": "+40722123456",
"email": "ana@example.com",
"external_id": "crm-1001",
},
"source": "crm",
"fields": {
"first_name": "Ana",
},
"update_existing_only": false,
}),
});
const data = await response.json();
import os
import uuid
import requests
response = requests.post(
"https://api.worbo.app/v1/contacts",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"identities": {
"phone": "+40722123456",
"email": "ana@example.com",
"external_id": "crm-1001",
},
"source": "crm",
"fields": {
"first_name": "Ana",
},
"update_existing_only": False,
},
)
data = response.json()
{
"data": {
"id": 123,
"identities": [
{
"id": 501,
"type": "phone",
"value": "+40722123456",
"source": "string",
"primary": true
}
],
"fields": {
"first_name": "Ana",
"last_name": "Popescu",
"language": "ro"
},
"tags": [
"vip"
],
"source": "api",
"blocked": false,
"created_at": "2026-10-15T09:00:00+03:00",
"updated_at": "2026-10-15T09:00:00+03:00"
}
}
{
"data": {
"id": 123,
"identities": [
{
"id": 501,
"type": "phone",
"value": "+40722123456",
"source": "string",
"primary": true
}
],
"fields": {
"first_name": "Ana",
"last_name": "Popescu",
"language": "ro"
},
"tags": [
"vip"
],
"source": "api",
"blocked": false,
"created_at": "2026-10-15T09:00:00+03:00",
"updated_at": "2026-10-15T09:00:00+03:00"
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/idempotency_conflict",
"title": "The Idempotency-Key was used for another request",
"status": 409,
"detail": "The Idempotency-Key was used for another request.",
"code": "idempotency_conflict"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Create or update up to 100 contacts#
Query parameters
-
dry_run boolean
Runs every check and returns what would happen, without saving anything.
Headers
-
Idempotency-Key string
A unique key of the request, such as a UUID: a repeated request with the same key gets the first response, without doing the work again.
Max length:255 -
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Request bodyapplication/json
-
items object[] Required
Show the fieldsHide the fields
-
identities object Required
At least one identity; the contact is found by any of them.
Show the fieldsHide the fields
-
phone string
E.164, such as +40722123456.
Example:+40722123456 -
email stringFormat:
emailExample:ana@example.com -
bsuid string
-
external_id string
The ID of the contact at the source: text, or an object with source and value.
Example:crm-1001
-
-
source stringExample:
crm -
fields object
-
tags string[]
-
update_existing_only booleanExample:
false
-
Responses
curl -X POST "https://api.worbo.app/v1/contacts/batch" \
-H "Authorization: Bearer $WORBO_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"identities": {
"phone": "+40722123456",
"email": "ana@example.com",
"external_id": "crm-1001"
},
"source": "crm",
"fields": {
"first_name": "Ana"
},
"update_existing_only": false
}
]
}'
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', 'https://api.worbo.app/v1/contacts/batch', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
'Idempotency-Key' => bin2hex(random_bytes(16)),
],
'json' => [
'items' => [
[
'identities' => [
'phone' => '+40722123456',
'email' => 'ana@example.com',
'external_id' => 'crm-1001',
],
'source' => 'crm',
'fields' => [
'first_name' => 'Ana',
],
'update_existing_only' => false,
],
],
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/contacts/batch', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
'Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify({
"items": [
{
"identities": {
"phone": "+40722123456",
"email": "ana@example.com",
"external_id": "crm-1001",
},
"source": "crm",
"fields": {
"first_name": "Ana",
},
"update_existing_only": false,
},
],
}),
});
const data = await response.json();
import os
import uuid
import requests
response = requests.post(
"https://api.worbo.app/v1/contacts/batch",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"items": [
{
"identities": {
"phone": "+40722123456",
"email": "ana@example.com",
"external_id": "crm-1001",
},
"source": "crm",
"fields": {
"first_name": "Ana",
},
"update_existing_only": False,
},
],
},
)
data = response.json()
{
"data": [
{
"index": 0,
"status": "created",
"data": {
"id": 123,
"identities": [
{
"id": 501,
"type": "phone",
"value": "+40722123456",
"source": "string",
"primary": true
}
],
"fields": {
"first_name": "Ana",
"last_name": "Popescu",
"language": "ro"
},
"tags": [
"vip"
],
"source": "api",
"blocked": false,
"created_at": "2026-10-15T09:00:00+03:00",
"updated_at": "2026-10-15T09:00:00+03:00"
},
"error": {
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "The name field is required.",
"code": "validation_failed",
"errors": {
"name": [
"The name field is required."
]
}
}
}
]
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/idempotency_conflict",
"title": "The Idempotency-Key was used for another request",
"status": 409,
"detail": "The Idempotency-Key was used for another request.",
"code": "idempotency_conflict"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Read a contact#
Path parameters
-
contact integer Required
The ID of the contact.
Example:123
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl "https://api.worbo.app/v1/contacts/123" \
-H "Authorization: Bearer $WORBO_API_KEY"
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.worbo.app/v1/contacts/123', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/contacts/123', {
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
},
});
const data = await response.json();
import os
import requests
response = requests.get(
"https://api.worbo.app/v1/contacts/123",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
)
data = response.json()
{
"data": {
"id": 123,
"identities": [
{
"id": 501,
"type": "phone",
"value": "+40722123456",
"source": "string",
"primary": true
}
],
"fields": {
"first_name": "Ana",
"last_name": "Popescu",
"language": "ro"
},
"tags": [
"vip"
],
"source": "api",
"blocked": false,
"created_at": "2026-10-15T09:00:00+03:00",
"updated_at": "2026-10-15T09:00:00+03:00"
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/not_found",
"title": "Not found",
"status": 404,
"detail": "The resource does not exist or belongs to another organization.",
"code": "not_found"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Change the fields and the tags of a contact#
Path parameters
-
contact integer Required
The ID of the contact.
Example:123
Query parameters
-
dry_run boolean
Runs every check and returns what would happen, without saving anything.
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Request bodyapplication/json
-
fields object
-
tags string[]
Responses
curl -X PATCH "https://api.worbo.app/v1/contacts/123" \
-H "Authorization: Bearer $WORBO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"fields": {
"last_name": "Ionescu"
}
}'
$client = new \GuzzleHttp\Client();
$response = $client->request('PATCH', 'https://api.worbo.app/v1/contacts/123', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
'json' => [
'fields' => [
'last_name' => 'Ionescu',
],
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/contacts/123', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"fields": {
"last_name": "Ionescu",
},
}),
});
const data = await response.json();
import os
import requests
response = requests.patch(
"https://api.worbo.app/v1/contacts/123",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
json={
"fields": {
"last_name": "Ionescu",
},
},
)
data = response.json()
{
"data": {
"id": 123,
"identities": [
{
"id": 501,
"type": "phone",
"value": "+40722123456",
"source": "string",
"primary": true
}
],
"fields": {
"first_name": "Ana",
"last_name": "Popescu",
"language": "ro"
},
"tags": [
"vip"
],
"source": "api",
"blocked": false,
"created_at": "2026-10-15T09:00:00+03:00",
"updated_at": "2026-10-15T09:00:00+03:00"
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/not_found",
"title": "Not found",
"status": 404,
"detail": "The resource does not exist or belongs to another organization.",
"code": "not_found"
}
{
"type": "https://worbo.app/docs/api/errors/idempotency_conflict",
"title": "The Idempotency-Key was used for another request",
"status": 409,
"detail": "The Idempotency-Key was used for another request.",
"code": "idempotency_conflict"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Erase a contact, at the request of the person (GDPR)#
The data of the contact is erased for good; the audit log keeps only that the erasure happened.
Path parameters
-
contact integer Required
The ID of the contact.
Example:123
Query parameters
-
dry_run boolean
Runs every check and returns what would happen, without saving anything.
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl -X DELETE "https://api.worbo.app/v1/contacts/123" \
-H "Authorization: Bearer $WORBO_API_KEY"
$client = new \GuzzleHttp\Client();
$response = $client->request('DELETE', 'https://api.worbo.app/v1/contacts/123', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
]);
const response = await fetch('https://api.worbo.app/v1/contacts/123', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
},
});
import os
import requests
response = requests.delete(
"https://api.worbo.app/v1/contacts/123",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
)
No content
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/not_found",
"title": "Not found",
"status": 404,
"detail": "The resource does not exist or belongs to another organization.",
"code": "not_found"
}
{
"type": "https://worbo.app/docs/api/errors/idempotency_conflict",
"title": "The Idempotency-Key was used for another request",
"status": 409,
"detail": "The Idempotency-Key was used for another request.",
"code": "idempotency_conflict"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Turn the keyword bot off or on for a contact#
For example when a CRM takes over the conversation.
Path parameters
-
contact integer Required
The ID of the contact.
Example:123
Query parameters
-
dry_run boolean
Runs every check and returns what would happen, without saving anything.
Headers
-
Idempotency-Key string
A unique key of the request, such as a UUID: a repeated request with the same key gets the first response, without doing the work again.
Max length:255 -
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Request bodyapplication/json
-
enabled boolean RequiredExample:
false
Responses
curl -X POST "https://api.worbo.app/v1/contacts/123/bot" \
-H "Authorization: Bearer $WORBO_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"enabled": false
}'
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', 'https://api.worbo.app/v1/contacts/123/bot', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
'Idempotency-Key' => bin2hex(random_bytes(16)),
],
'json' => [
'enabled' => false,
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/contacts/123/bot', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
'Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify({
"enabled": false,
}),
});
const data = await response.json();
import os
import uuid
import requests
response = requests.post(
"https://api.worbo.app/v1/contacts/123/bot",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"enabled": False,
},
)
data = response.json()
{
"data": {
"contact_id": 123,
"bot_enabled": false
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/not_found",
"title": "Not found",
"status": 404,
"detail": "The resource does not exist or belongs to another organization.",
"code": "not_found"
}
{
"type": "https://worbo.app/docs/api/errors/idempotency_conflict",
"title": "The Idempotency-Key was used for another request",
"status": 409,
"detail": "The Idempotency-Key was used for another request.",
"code": "idempotency_conflict"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
List the identities of a contact#
Path parameters
-
contact integer Required
The ID of the contact.
Example:123
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl "https://api.worbo.app/v1/contacts/123/identities" \
-H "Authorization: Bearer $WORBO_API_KEY"
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.worbo.app/v1/contacts/123/identities', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/contacts/123/identities', {
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
},
});
const data = await response.json();
import os
import requests
response = requests.get(
"https://api.worbo.app/v1/contacts/123/identities",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
)
data = response.json()
{
"data": [
{
"id": 501,
"type": "phone",
"value": "+40722123456",
"source": "string",
"primary": true
}
]
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/not_found",
"title": "Not found",
"status": 404,
"detail": "The resource does not exist or belongs to another organization.",
"code": "not_found"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Attach an identity to a contact#
Path parameters
-
contact integer Required
The ID of the contact.
Example:123
Query parameters
-
dry_run boolean
Runs every check and returns what would happen, without saving anything.
Headers
-
Idempotency-Key string
A unique key of the request, such as a UUID: a repeated request with the same key gets the first response, without doing the work again.
Max length:255 -
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Request bodyapplication/json
-
type string RequiredPossible values:
phoneemailbsuidusernameexternal_id -
value string RequiredExample:
ana@example.com -
source string
-
primary booleanExample:
false
Responses
curl -X POST "https://api.worbo.app/v1/contacts/123/identities" \
-H "Authorization: Bearer $WORBO_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"type": "email",
"value": "ana@example.com",
"primary": false
}'
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', 'https://api.worbo.app/v1/contacts/123/identities', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
'Idempotency-Key' => bin2hex(random_bytes(16)),
],
'json' => [
'type' => 'email',
'value' => 'ana@example.com',
'primary' => false,
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/contacts/123/identities', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
'Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify({
"type": "email",
"value": "ana@example.com",
"primary": false,
}),
});
const data = await response.json();
import os
import uuid
import requests
response = requests.post(
"https://api.worbo.app/v1/contacts/123/identities",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"type": "email",
"value": "ana@example.com",
"primary": False,
},
)
data = response.json()
{
"data": {
"id": 501,
"type": "phone",
"value": "+40722123456",
"source": "string",
"primary": true
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/not_found",
"title": "Not found",
"status": 404,
"detail": "The resource does not exist or belongs to another organization.",
"code": "not_found"
}
{
"type": "https://worbo.app/docs/api/errors/idempotency_conflict",
"title": "The Idempotency-Key was used for another request",
"status": 409,
"detail": "The Idempotency-Key was used for another request.",
"code": "idempotency_conflict"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Detach an identity from a contact#
Path parameters
-
contact integer Required
The ID of the contact.
Example:123 -
identity integer Required
The ID of the identity.
Example:501
Query parameters
-
dry_run boolean
Runs every check and returns what would happen, without saving anything.
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl -X DELETE "https://api.worbo.app/v1/contacts/123/identities/501" \
-H "Authorization: Bearer $WORBO_API_KEY"
$client = new \GuzzleHttp\Client();
$response = $client->request('DELETE', 'https://api.worbo.app/v1/contacts/123/identities/501', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
]);
const response = await fetch('https://api.worbo.app/v1/contacts/123/identities/501', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
},
});
import os
import requests
response = requests.delete(
"https://api.worbo.app/v1/contacts/123/identities/501",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
)
No content
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/not_found",
"title": "Not found",
"status": 404,
"detail": "The resource does not exist or belongs to another organization.",
"code": "not_found"
}
{
"type": "https://worbo.app/docs/api/errors/idempotency_conflict",
"title": "The Idempotency-Key was used for another request",
"status": 409,
"detail": "The Idempotency-Key was used for another request.",
"code": "idempotency_conflict"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Merge another contact into this one#
The identities, the history and the objects of the other contact move here; the event contact.merged follows.
Path parameters
-
contact integer Required
The ID of the contact.
Example:123
Query parameters
-
dry_run boolean
Runs every check and returns what would happen, without saving anything.
Headers
-
Idempotency-Key string
A unique key of the request, such as a UUID: a repeated request with the same key gets the first response, without doing the work again.
Max length:255 -
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Request bodyapplication/json
-
contact_id integer Required
The contact merged into this one; its identities and history move here.
Example:124
Responses
curl -X POST "https://api.worbo.app/v1/contacts/123/merge" \
-H "Authorization: Bearer $WORBO_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"contact_id": 124
}'
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', 'https://api.worbo.app/v1/contacts/123/merge', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
'Idempotency-Key' => bin2hex(random_bytes(16)),
],
'json' => [
'contact_id' => 124,
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/contacts/123/merge', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
'Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify({
"contact_id": 124,
}),
});
const data = await response.json();
import os
import uuid
import requests
response = requests.post(
"https://api.worbo.app/v1/contacts/123/merge",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"contact_id": 124,
},
)
data = response.json()
{
"data": {
"id": 123,
"identities": [
{
"id": 501,
"type": "phone",
"value": "+40722123456",
"source": "string",
"primary": true
}
],
"fields": {
"first_name": "Ana",
"last_name": "Popescu",
"language": "ro"
},
"tags": [
"vip"
],
"source": "api",
"blocked": false,
"created_at": "2026-10-15T09:00:00+03:00",
"updated_at": "2026-10-15T09:00:00+03:00"
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/not_found",
"title": "Not found",
"status": 404,
"detail": "The resource does not exist or belongs to another organization.",
"code": "not_found"
}
{
"type": "https://worbo.app/docs/api/errors/idempotency_conflict",
"title": "The Idempotency-Key was used for another request",
"status": 409,
"detail": "The Idempotency-Key was used for another request.",
"code": "idempotency_conflict"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Reference
Consent#
The append-only consent history, by purpose and topic, with the proof of each consent.
Read the consent of a contact, with its history#
Path parameters
-
contact integer Required
The ID of the contact.
Example:123
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl "https://api.worbo.app/v1/contacts/123/consents" \
-H "Authorization: Bearer $WORBO_API_KEY"
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.worbo.app/v1/contacts/123/consents', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/contacts/123/consents', {
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
},
});
const data = await response.json();
import os
import requests
response = requests.get(
"https://api.worbo.app/v1/contacts/123/consents",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
)
data = response.json()
{
"data": {
"status": {
"purposes": {
"channel": "granted",
"transactional": null,
"marketing": "granted",
"recommendations": null
},
"topics": {},
"meta_blocked": false,
"blocked": false,
"allowed": {
"transactional": true,
"marketing": true,
"recommendations": false
}
},
"history": [
{
"id": 900,
"purpose": "marketing",
"topic": "string",
"state": "granted",
"basis": "consent",
"source": "form",
"proof": {},
"occurred_at": "2026-10-15T09:00:00+03:00"
}
]
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/not_found",
"title": "Not found",
"status": 404,
"detail": "The resource does not exist or belongs to another organization.",
"code": "not_found"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Record a consent, with its proof, or a withdrawal#
A consent withdrawn by the person can be given back only by the person: through a form, at checkout or through a link.
Path parameters
-
contact integer Required
The ID of the contact.
Example:123
Query parameters
-
dry_run boolean
Runs every check and returns what would happen, without saving anything.
Headers
-
Idempotency-Key string
A unique key of the request, such as a UUID: a repeated request with the same key gets the first response, without doing the work again.
Max length:255 -
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Request bodyapplication/json
-
purposes string[] RequiredMinimum:
1 -
state string RequiredPossible values:
grantedrevoked -
source stringPossible values:
formcheckoutclick_to_chatapi -
topic string null
-
basis stringPossible values:
consentsoft_opt_in -
proof object
What shows the consent: the text of the form, the page, the IP.
-
occurred_at string nullFormat:
date-time
Responses
curl -X POST "https://api.worbo.app/v1/contacts/123/consents" \
-H "Authorization: Bearer $WORBO_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"purposes": [
"channel",
"marketing"
],
"state": "granted",
"source": "form",
"proof": {
"form": "Newsletter, footer",
"text": "I agree to receive offers on WhatsApp"
}
}'
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', 'https://api.worbo.app/v1/contacts/123/consents', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
'Idempotency-Key' => bin2hex(random_bytes(16)),
],
'json' => [
'purposes' => [
'channel',
'marketing',
],
'state' => 'granted',
'source' => 'form',
'proof' => [
'form' => 'Newsletter, footer',
'text' => 'I agree to receive offers on WhatsApp',
],
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/contacts/123/consents', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
'Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify({
"purposes": [
"channel",
"marketing",
],
"state": "granted",
"source": "form",
"proof": {
"form": "Newsletter, footer",
"text": "I agree to receive offers on WhatsApp",
},
}),
});
const data = await response.json();
import os
import uuid
import requests
response = requests.post(
"https://api.worbo.app/v1/contacts/123/consents",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"purposes": [
"channel",
"marketing",
],
"state": "granted",
"source": "form",
"proof": {
"form": "Newsletter, footer",
"text": "I agree to receive offers on WhatsApp",
},
},
)
data = response.json()
{
"data": {
"recorded": [
{
"id": 900,
"purpose": "marketing",
"topic": "string",
"state": "granted",
"basis": "consent",
"source": "form",
"proof": {},
"occurred_at": "2026-10-15T09:00:00+03:00"
}
],
"status": {
"purposes": {
"channel": "granted",
"transactional": null,
"marketing": "granted",
"recommendations": null
},
"topics": {},
"meta_blocked": false,
"blocked": false,
"allowed": {
"transactional": true,
"marketing": true,
"recommendations": false
}
},
"history": [
{
"id": 900,
"purpose": "marketing",
"topic": "string",
"state": "granted",
"basis": "consent",
"source": "form",
"proof": {},
"occurred_at": "2026-10-15T09:00:00+03:00"
}
]
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/not_found",
"title": "Not found",
"status": 404,
"detail": "The resource does not exist or belongs to another organization.",
"code": "not_found"
}
{
"type": "https://worbo.app/docs/api/errors/idempotency_conflict",
"title": "The Idempotency-Key was used for another request",
"status": 409,
"detail": "The Idempotency-Key was used for another request.",
"code": "idempotency_conflict"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Create a signed link to the preference center of a contact#
The link is valid for 30 days.
Query parameters
-
dry_run boolean
Runs every check and returns what would happen, without saving anything.
Headers
-
Idempotency-Key string
A unique key of the request, such as a UUID: a repeated request with the same key gets the first response, without doing the work again.
Max length:255 -
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Request bodyapplication/json
-
contact_id integer RequiredExample:
123
Responses
curl -X POST "https://api.worbo.app/v1/preference-links" \
-H "Authorization: Bearer $WORBO_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"contact_id": 123
}'
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', 'https://api.worbo.app/v1/preference-links', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
'Idempotency-Key' => bin2hex(random_bytes(16)),
],
'json' => [
'contact_id' => 123,
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/preference-links', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
'Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify({
"contact_id": 123,
}),
});
const data = await response.json();
import os
import uuid
import requests
response = requests.post(
"https://api.worbo.app/v1/preference-links",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"contact_id": 123,
},
)
data = response.json()
{
"data": {
"contact_id": 123,
"url": "https://my.worbo.app/preferences/abc",
"expires_at": "2026-10-15T09:00:00+03:00"
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/idempotency_conflict",
"title": "The Idempotency-Key was used for another request",
"status": 409,
"detail": "The Idempotency-Key was used for another request.",
"code": "idempotency_conflict"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Reference
Data model#
The fields of the contacts, the object types and the event types of the organization, with their JSON Schema.
Read the JSON Schema of the body that writes a contact#
With the custom fields of the organization; any key reads it.
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl "https://api.worbo.app/v1/contacts/schema" \
-H "Authorization: Bearer $WORBO_API_KEY"
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.worbo.app/v1/contacts/schema', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/contacts/schema', {
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
},
});
const data = await response.json();
import os
import requests
response = requests.get(
"https://api.worbo.app/v1/contacts/schema",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
)
data = response.json()
{
"data": {}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
List the fields of the contacts#
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl "https://api.worbo.app/v1/field-definitions" \
-H "Authorization: Bearer $WORBO_API_KEY"
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.worbo.app/v1/field-definitions', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/field-definitions', {
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
},
});
const data = await response.json();
import os
import requests
response = requests.get(
"https://api.worbo.app/v1/field-definitions",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
)
data = response.json()
{
"data": [
{
"key": "shoe_size",
"label": "Shoe size",
"labels": {},
"type": "number",
"required": false,
"unique": false,
"sensitivity": "normal",
"key_date": false,
"calculated": false,
"multiple": false,
"choices": [
{
"key": "string",
"labels": {}
}
],
"default": {},
"archived": false
}
]
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Create a field of the contacts or of an object type#
Through the same checks and limits as in the dashboard; the key does not change afterwards.
Query parameters
-
dry_run boolean
Runs every check and returns what would happen, without saving anything.
Headers
-
Idempotency-Key string
A unique key of the request, such as a UUID: a repeated request with the same key gets the first response, without doing the work again.
Max length:255 -
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Request bodyapplication/json
-
object_type string
The key of the object type; without it, the field belongs to the contacts.
Example:vehicle -
labels object Required
-
key stringExample:
plate -
type string RequiredPossible values:
textnumbermoneydatedatetimebooleansingle_selectmulti_selectphoneemailurlreferencefilelocationinterval -
sensitivity stringPossible values:
normalpersonalspecial -
is_required boolean
-
is_unique boolean
-
segmentable boolean
-
templatable boolean
-
key_date boolean
-
audited boolean
-
settings object
-
default_value string
The default value, of the type of the field.
Responses
curl -X POST "https://api.worbo.app/v1/field-definitions" \
-H "Authorization: Bearer $WORBO_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"object_type": "vehicle",
"labels": {
"en": "Plate",
"ro": "Număr de înmatriculare"
},
"key": "plate",
"type": "text"
}'
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', 'https://api.worbo.app/v1/field-definitions', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
'Idempotency-Key' => bin2hex(random_bytes(16)),
],
'json' => [
'object_type' => 'vehicle',
'labels' => [
'en' => 'Plate',
'ro' => 'Număr de înmatriculare',
],
'key' => 'plate',
'type' => 'text',
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/field-definitions', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
'Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify({
"object_type": "vehicle",
"labels": {
"en": "Plate",
"ro": "Număr de înmatriculare",
},
"key": "plate",
"type": "text",
}),
});
const data = await response.json();
import os
import uuid
import requests
response = requests.post(
"https://api.worbo.app/v1/field-definitions",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"object_type": "vehicle",
"labels": {
"en": "Plate",
"ro": "Număr de înmatriculare",
},
"key": "plate",
"type": "text",
},
)
data = response.json()
{
"data": {
"key": "shoe_size",
"label": "Shoe size",
"labels": {},
"type": "number",
"required": false,
"unique": false,
"sensitivity": "normal",
"key_date": false,
"calculated": false,
"multiple": false,
"choices": [
{
"key": "string",
"labels": {}
}
],
"default": {},
"archived": false
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/idempotency_conflict",
"title": "The Idempotency-Key was used for another request",
"status": 409,
"detail": "The Idempotency-Key was used for another request.",
"code": "idempotency_conflict"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
List the object types#
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl "https://api.worbo.app/v1/object-types" \
-H "Authorization: Bearer $WORBO_API_KEY"
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.worbo.app/v1/object-types', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/object-types', {
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
},
});
const data = await response.json();
import os
import requests
response = requests.get(
"https://api.worbo.app/v1/object-types",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
)
data = response.json()
{
"data": [
{
"key": "fit.membership",
"label": "Membership",
"labels": {},
"plural_labels": {},
"source": "fitness",
"status_axes": [
{
"key": "lifecycle",
"labels": {},
"statuses": [
{
"key": "active",
"labels": {},
"category": "active"
}
]
}
],
"roles": [
{
"key": "holder",
"labels": {},
"multiple": false,
"primary": true
}
],
"fields": [
{
"key": "shoe_size",
"label": "Shoe size",
"labels": {},
"type": "number",
"required": false,
"unique": false,
"sensitivity": "normal",
"key_date": false,
"calculated": false,
"multiple": false,
"choices": [
{
"key": "string",
"labels": {}
}
],
"default": {},
"archived": false
}
]
}
]
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Create an object type#
The type belongs to the organization and counts against its limit of types.
Query parameters
-
dry_run boolean
Runs every check and returns what would happen, without saving anything.
Headers
-
Idempotency-Key string
A unique key of the request, such as a UUID: a repeated request with the same key gets the first response, without doing the work again.
Max length:255 -
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Request bodyapplication/json
-
labels object Required
-
plural_labels object
-
key stringExample:
vehicle -
status_axes object[]
-
roles object[]
-
title_field string
-
retention_days integer
Responses
curl -X POST "https://api.worbo.app/v1/object-types" \
-H "Authorization: Bearer $WORBO_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"labels": {
"en": "Vehicle",
"ro": "Vehicul"
},
"key": "vehicle"
}'
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', 'https://api.worbo.app/v1/object-types', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
'Idempotency-Key' => bin2hex(random_bytes(16)),
],
'json' => [
'labels' => [
'en' => 'Vehicle',
'ro' => 'Vehicul',
],
'key' => 'vehicle',
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/object-types', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
'Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify({
"labels": {
"en": "Vehicle",
"ro": "Vehicul",
},
"key": "vehicle",
}),
});
const data = await response.json();
import os
import uuid
import requests
response = requests.post(
"https://api.worbo.app/v1/object-types",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"labels": {
"en": "Vehicle",
"ro": "Vehicul",
},
"key": "vehicle",
},
)
data = response.json()
{
"data": {
"key": "fit.membership",
"label": "Membership",
"labels": {},
"plural_labels": {},
"source": "fitness",
"status_axes": [
{
"key": "lifecycle",
"labels": {},
"statuses": [
{
"key": "active",
"labels": {},
"category": "active"
}
]
}
],
"roles": [
{
"key": "holder",
"labels": {},
"multiple": false,
"primary": true
}
],
"fields": [
{
"key": "shoe_size",
"label": "Shoe size",
"labels": {},
"type": "number",
"required": false,
"unique": false,
"sensitivity": "normal",
"key_date": false,
"calculated": false,
"multiple": false,
"choices": [
{
"key": "string",
"labels": {}
}
],
"default": {},
"archived": false
}
]
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/idempotency_conflict",
"title": "The Idempotency-Key was used for another request",
"status": 409,
"detail": "The Idempotency-Key was used for another request.",
"code": "idempotency_conflict"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Read an object type, with its statuses, roles and fields#
Path parameters
-
type string Required
The key of the type, such as fit.membership.
Example:fit.membership
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl "https://api.worbo.app/v1/object-types/fit.membership" \
-H "Authorization: Bearer $WORBO_API_KEY"
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.worbo.app/v1/object-types/fit.membership', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/object-types/fit.membership', {
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
},
});
const data = await response.json();
import os
import requests
response = requests.get(
"https://api.worbo.app/v1/object-types/fit.membership",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
)
data = response.json()
{
"data": {
"key": "fit.membership",
"label": "Membership",
"labels": {},
"plural_labels": {},
"source": "fitness",
"status_axes": [
{
"key": "lifecycle",
"labels": {},
"statuses": [
{
"key": "active",
"labels": {},
"category": "active"
}
]
}
],
"roles": [
{
"key": "holder",
"labels": {},
"multiple": false,
"primary": true
}
],
"fields": [
{
"key": "shoe_size",
"label": "Shoe size",
"labels": {},
"type": "number",
"required": false,
"unique": false,
"sensitivity": "normal",
"key_date": false,
"calculated": false,
"multiple": false,
"choices": [
{
"key": "string",
"labels": {}
}
],
"default": {},
"archived": false
}
]
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/not_found",
"title": "Not found",
"status": 404,
"detail": "The resource does not exist or belongs to another organization.",
"code": "not_found"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Read the JSON Schema of the body that writes an object of the type#
Path parameters
-
type string Required
The key of the type, such as fit.membership.
Example:fit.membership
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl "https://api.worbo.app/v1/object-types/fit.membership/schema" \
-H "Authorization: Bearer $WORBO_API_KEY"
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.worbo.app/v1/object-types/fit.membership/schema', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/object-types/fit.membership/schema', {
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
},
});
const data = await response.json();
import os
import requests
response = requests.get(
"https://api.worbo.app/v1/object-types/fit.membership/schema",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
)
data = response.json()
{
"data": {}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/not_found",
"title": "Not found",
"status": 404,
"detail": "The resource does not exist or belongs to another organization.",
"code": "not_found"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
List the event types#
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl "https://api.worbo.app/v1/event-types" \
-H "Authorization: Bearer $WORBO_API_KEY"
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.worbo.app/v1/event-types', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/event-types', {
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
},
});
const data = await response.json();
import os
import requests
response = requests.get(
"https://api.worbo.app/v1/event-types",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
)
data = response.json()
{
"data": [
{
"key": "visit.recorded",
"label": "Visit recorded",
"labels": {},
"source": "fitness",
"violation_policy": "reject",
"properties": [
{
"key": "shoe_size",
"label": "Shoe size",
"labels": {},
"type": "number",
"required": false,
"unique": false,
"sensitivity": "normal",
"key_date": false,
"calculated": false,
"multiple": false,
"choices": [
{
"key": "string",
"labels": {}
}
],
"default": {},
"archived": false
}
]
}
]
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Read an event type, with its properties#
Path parameters
-
type string Required
The key of the type, such as fit.membership.
Example:fit.membership
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl "https://api.worbo.app/v1/event-types/fit.membership" \
-H "Authorization: Bearer $WORBO_API_KEY"
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.worbo.app/v1/event-types/fit.membership', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/event-types/fit.membership', {
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
},
});
const data = await response.json();
import os
import requests
response = requests.get(
"https://api.worbo.app/v1/event-types/fit.membership",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
)
data = response.json()
{
"data": {
"key": "visit.recorded",
"label": "Visit recorded",
"labels": {},
"source": "fitness",
"violation_policy": "reject",
"properties": [
{
"key": "shoe_size",
"label": "Shoe size",
"labels": {},
"type": "number",
"required": false,
"unique": false,
"sensitivity": "normal",
"key_date": false,
"calculated": false,
"multiple": false,
"choices": [
{
"key": "string",
"labels": {}
}
],
"default": {},
"archived": false
}
]
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/not_found",
"title": "Not found",
"status": 404,
"detail": "The resource does not exist or belongs to another organization.",
"code": "not_found"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Read the JSON Schema of the properties of an event type#
Path parameters
-
type string Required
The key of the type, such as fit.membership.
Example:fit.membership
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl "https://api.worbo.app/v1/event-types/fit.membership/schema" \
-H "Authorization: Bearer $WORBO_API_KEY"
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.worbo.app/v1/event-types/fit.membership/schema', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/event-types/fit.membership/schema', {
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
},
});
const data = await response.json();
import os
import requests
response = requests.get(
"https://api.worbo.app/v1/event-types/fit.membership/schema",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
)
data = response.json()
{
"data": {}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/not_found",
"title": "Not found",
"status": 404,
"detail": "The resource does not exist or belongs to another organization.",
"code": "not_found"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Reference
Objects#
Memberships, bookings, orders: the records of each object type, linked to contacts in roles.
List the objects of a type#
Path parameters
-
type string Required
The key of the type, such as fit.membership.
Example:fit.membership
Query parameters
-
external_id string
The object with this ID at the source.
Example:abn-7781 -
source string
Only the objects of this source.
Example:upfit -
contact_id integer
Only the objects linked to this contact.
Example:123 -
status object
The status on each axis, such as status[lifecycle]=active.
-
updated_since string
Only what changed at or after this moment.
Format:date-timeExample:2026-10-01T00:00:00+03:00 -
limit integer
How many items a page has, between 1 and 100.
Default:25Minimum:1Maximum:100 -
cursor string
The next_cursor of the previous page.
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl "https://api.worbo.app/v1/objects/fit.membership" \
-H "Authorization: Bearer $WORBO_API_KEY"
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.worbo.app/v1/objects/fit.membership', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/objects/fit.membership', {
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
},
});
const data = await response.json();
import os
import requests
response = requests.get(
"https://api.worbo.app/v1/objects/fit.membership",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
)
data = response.json()
{
"data": [
{
"id": 456,
"type": "fit.membership",
"source": "string",
"external_id": "abn-7781",
"title": "Full access 12 luni",
"fields": {
"plan": 12,
"starts_on": "2026-09-01",
"ends_on": "2027-08-31"
},
"statuses": {
"lifecycle": "active",
"payment": "ok"
},
"raw_status": "string",
"relations": [
{
"id": 77,
"contact_id": 123,
"role": "holder",
"primary": true,
"muted": false,
"valid_from": "2026-10-15",
"valid_until": "2026-10-15"
}
],
"created_at": "2026-10-15T09:00:00+03:00",
"updated_at": "2026-10-15T09:00:00+03:00"
}
],
"next_cursor": "string",
"has_more": true
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/not_found",
"title": "Not found",
"status": 404,
"detail": "The resource does not exist or belongs to another organization.",
"code": "not_found"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Create or update an object by its ID at the source#
Path parameters
-
type string Required
The key of the type, such as fit.membership.
Example:fit.membership
Query parameters
-
dry_run boolean
Runs every check and returns what would happen, without saving anything.
Headers
-
Idempotency-Key string
A unique key of the request, such as a UUID: a repeated request with the same key gets the first response, without doing the work again.
Max length:255 -
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Request bodyapplication/json
-
external_id string RequiredExample:
abn-7781 -
source stringExample:
upfit -
fields object
-
statuses object
-
raw_status string
-
raw_status_label string
-
contact object
The contact linked in role: {"id": 123} or {"identities": {...}, "fields": {...}}.
-
role stringExample:
holder -
update_existing_only booleanExample:
false
Responses
curl -X POST "https://api.worbo.app/v1/objects/fit.membership" \
-H "Authorization: Bearer $WORBO_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"external_id": "abn-7781",
"source": "upfit",
"fields": {
"starts_on": "2026-09-01",
"ends_on": "2027-08-31"
},
"statuses": {
"lifecycle": "active"
},
"contact": {
"identities": {
"phone": "+40722123456"
}
},
"role": "holder",
"update_existing_only": false
}'
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', 'https://api.worbo.app/v1/objects/fit.membership', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
'Idempotency-Key' => bin2hex(random_bytes(16)),
],
'json' => [
'external_id' => 'abn-7781',
'source' => 'upfit',
'fields' => [
'starts_on' => '2026-09-01',
'ends_on' => '2027-08-31',
],
'statuses' => [
'lifecycle' => 'active',
],
'contact' => [
'identities' => [
'phone' => '+40722123456',
],
],
'role' => 'holder',
'update_existing_only' => false,
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/objects/fit.membership', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
'Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify({
"external_id": "abn-7781",
"source": "upfit",
"fields": {
"starts_on": "2026-09-01",
"ends_on": "2027-08-31",
},
"statuses": {
"lifecycle": "active",
},
"contact": {
"identities": {
"phone": "+40722123456",
},
},
"role": "holder",
"update_existing_only": false,
}),
});
const data = await response.json();
import os
import uuid
import requests
response = requests.post(
"https://api.worbo.app/v1/objects/fit.membership",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"external_id": "abn-7781",
"source": "upfit",
"fields": {
"starts_on": "2026-09-01",
"ends_on": "2027-08-31",
},
"statuses": {
"lifecycle": "active",
},
"contact": {
"identities": {
"phone": "+40722123456",
},
},
"role": "holder",
"update_existing_only": False,
},
)
data = response.json()
{
"data": {
"id": 456,
"type": "fit.membership",
"source": "string",
"external_id": "abn-7781",
"title": "Full access 12 luni",
"fields": {
"plan": 12,
"starts_on": "2026-09-01",
"ends_on": "2027-08-31"
},
"statuses": {
"lifecycle": "active",
"payment": "ok"
},
"raw_status": "string",
"relations": [
{
"id": 77,
"contact_id": 123,
"role": "holder",
"primary": true,
"muted": false,
"valid_from": "2026-10-15",
"valid_until": "2026-10-15"
}
],
"created_at": "2026-10-15T09:00:00+03:00",
"updated_at": "2026-10-15T09:00:00+03:00"
}
}
{
"data": {
"id": 456,
"type": "fit.membership",
"source": "string",
"external_id": "abn-7781",
"title": "Full access 12 luni",
"fields": {
"plan": 12,
"starts_on": "2026-09-01",
"ends_on": "2027-08-31"
},
"statuses": {
"lifecycle": "active",
"payment": "ok"
},
"raw_status": "string",
"relations": [
{
"id": 77,
"contact_id": 123,
"role": "holder",
"primary": true,
"muted": false,
"valid_from": "2026-10-15",
"valid_until": "2026-10-15"
}
],
"created_at": "2026-10-15T09:00:00+03:00",
"updated_at": "2026-10-15T09:00:00+03:00"
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/not_found",
"title": "Not found",
"status": 404,
"detail": "The resource does not exist or belongs to another organization.",
"code": "not_found"
}
{
"type": "https://worbo.app/docs/api/errors/idempotency_conflict",
"title": "The Idempotency-Key was used for another request",
"status": 409,
"detail": "The Idempotency-Key was used for another request.",
"code": "idempotency_conflict"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Create or update up to 100 objects#
Path parameters
-
type string Required
The key of the type, such as fit.membership.
Example:fit.membership
Query parameters
-
dry_run boolean
Runs every check and returns what would happen, without saving anything.
Headers
-
Idempotency-Key string
A unique key of the request, such as a UUID: a repeated request with the same key gets the first response, without doing the work again.
Max length:255 -
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Request bodyapplication/json
-
items object[] Required
Show the fieldsHide the fields
-
external_id string RequiredExample:
abn-7781 -
source stringExample:
upfit -
fields object
-
statuses object
-
raw_status string
-
raw_status_label string
-
contact object
The contact linked in role: {"id": 123} or {"identities": {...}, "fields": {...}}.
-
role stringExample:
holder -
update_existing_only booleanExample:
false
-
Responses
curl -X POST "https://api.worbo.app/v1/objects/fit.membership/batch" \
-H "Authorization: Bearer $WORBO_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"external_id": "abn-7781",
"source": "upfit",
"fields": {
"starts_on": "2026-09-01",
"ends_on": "2027-08-31"
},
"statuses": {
"lifecycle": "active"
},
"contact": {
"identities": {
"phone": "+40722123456"
}
},
"role": "holder",
"update_existing_only": false
}
]
}'
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', 'https://api.worbo.app/v1/objects/fit.membership/batch', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
'Idempotency-Key' => bin2hex(random_bytes(16)),
],
'json' => [
'items' => [
[
'external_id' => 'abn-7781',
'source' => 'upfit',
'fields' => [
'starts_on' => '2026-09-01',
'ends_on' => '2027-08-31',
],
'statuses' => [
'lifecycle' => 'active',
],
'contact' => [
'identities' => [
'phone' => '+40722123456',
],
],
'role' => 'holder',
'update_existing_only' => false,
],
],
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/objects/fit.membership/batch', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
'Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify({
"items": [
{
"external_id": "abn-7781",
"source": "upfit",
"fields": {
"starts_on": "2026-09-01",
"ends_on": "2027-08-31",
},
"statuses": {
"lifecycle": "active",
},
"contact": {
"identities": {
"phone": "+40722123456",
},
},
"role": "holder",
"update_existing_only": false,
},
],
}),
});
const data = await response.json();
import os
import uuid
import requests
response = requests.post(
"https://api.worbo.app/v1/objects/fit.membership/batch",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"items": [
{
"external_id": "abn-7781",
"source": "upfit",
"fields": {
"starts_on": "2026-09-01",
"ends_on": "2027-08-31",
},
"statuses": {
"lifecycle": "active",
},
"contact": {
"identities": {
"phone": "+40722123456",
},
},
"role": "holder",
"update_existing_only": False,
},
],
},
)
data = response.json()
{
"data": [
{
"index": 0,
"status": "created",
"data": {
"id": 456,
"type": "fit.membership",
"source": "string",
"external_id": "abn-7781",
"title": "Full access 12 luni",
"fields": {
"plan": 12,
"starts_on": "2026-09-01",
"ends_on": "2027-08-31"
},
"statuses": {
"lifecycle": "active",
"payment": "ok"
},
"raw_status": "string",
"relations": [
{
"id": 77,
"contact_id": 123,
"role": "holder",
"primary": true,
"muted": false,
"valid_from": "2026-10-15",
"valid_until": "2026-10-15"
}
],
"created_at": "2026-10-15T09:00:00+03:00",
"updated_at": "2026-10-15T09:00:00+03:00"
},
"error": {
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "The name field is required.",
"code": "validation_failed",
"errors": {
"name": [
"The name field is required."
]
}
}
}
]
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/not_found",
"title": "Not found",
"status": 404,
"detail": "The resource does not exist or belongs to another organization.",
"code": "not_found"
}
{
"type": "https://worbo.app/docs/api/errors/idempotency_conflict",
"title": "The Idempotency-Key was used for another request",
"status": 409,
"detail": "The Idempotency-Key was used for another request.",
"code": "idempotency_conflict"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Read an object#
Path parameters
-
type string Required
The key of the type, such as fit.membership.
Example:fit.membership -
object integer Required
The ID of the object.
Example:456
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl "https://api.worbo.app/v1/objects/fit.membership/456" \
-H "Authorization: Bearer $WORBO_API_KEY"
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.worbo.app/v1/objects/fit.membership/456', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/objects/fit.membership/456', {
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
},
});
const data = await response.json();
import os
import requests
response = requests.get(
"https://api.worbo.app/v1/objects/fit.membership/456",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
)
data = response.json()
{
"data": {
"id": 456,
"type": "fit.membership",
"source": "string",
"external_id": "abn-7781",
"title": "Full access 12 luni",
"fields": {
"plan": 12,
"starts_on": "2026-09-01",
"ends_on": "2027-08-31"
},
"statuses": {
"lifecycle": "active",
"payment": "ok"
},
"raw_status": "string",
"relations": [
{
"id": 77,
"contact_id": 123,
"role": "holder",
"primary": true,
"muted": false,
"valid_from": "2026-10-15",
"valid_until": "2026-10-15"
}
],
"created_at": "2026-10-15T09:00:00+03:00",
"updated_at": "2026-10-15T09:00:00+03:00"
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/not_found",
"title": "Not found",
"status": 404,
"detail": "The resource does not exist or belongs to another organization.",
"code": "not_found"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Change the fields or the statuses of an object#
Path parameters
-
type string Required
The key of the type, such as fit.membership.
Example:fit.membership -
object integer Required
The ID of the object.
Example:456
Query parameters
-
dry_run boolean
Runs every check and returns what would happen, without saving anything.
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Request bodyapplication/json
-
source string
-
fields object
-
statuses object
-
raw_status string
-
raw_status_label string
-
contact object
-
role string
Responses
curl -X PATCH "https://api.worbo.app/v1/objects/fit.membership/456" \
-H "Authorization: Bearer $WORBO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"statuses": {
"lifecycle": "frozen"
}
}'
$client = new \GuzzleHttp\Client();
$response = $client->request('PATCH', 'https://api.worbo.app/v1/objects/fit.membership/456', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
'json' => [
'statuses' => [
'lifecycle' => 'frozen',
],
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/objects/fit.membership/456', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"statuses": {
"lifecycle": "frozen",
},
}),
});
const data = await response.json();
import os
import requests
response = requests.patch(
"https://api.worbo.app/v1/objects/fit.membership/456",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
json={
"statuses": {
"lifecycle": "frozen",
},
},
)
data = response.json()
{
"data": {
"id": 456,
"type": "fit.membership",
"source": "string",
"external_id": "abn-7781",
"title": "Full access 12 luni",
"fields": {
"plan": 12,
"starts_on": "2026-09-01",
"ends_on": "2027-08-31"
},
"statuses": {
"lifecycle": "active",
"payment": "ok"
},
"raw_status": "string",
"relations": [
{
"id": 77,
"contact_id": 123,
"role": "holder",
"primary": true,
"muted": false,
"valid_from": "2026-10-15",
"valid_until": "2026-10-15"
}
],
"created_at": "2026-10-15T09:00:00+03:00",
"updated_at": "2026-10-15T09:00:00+03:00"
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/not_found",
"title": "Not found",
"status": 404,
"detail": "The resource does not exist or belongs to another organization.",
"code": "not_found"
}
{
"type": "https://worbo.app/docs/api/errors/idempotency_conflict",
"title": "The Idempotency-Key was used for another request",
"status": 409,
"detail": "The Idempotency-Key was used for another request.",
"code": "idempotency_conflict"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Link a contact to an object, in a role#
Path parameters
-
type string Required
The key of the type, such as fit.membership.
Example:fit.membership -
object integer Required
The ID of the object.
Example:456
Query parameters
-
dry_run boolean
Runs every check and returns what would happen, without saving anything.
Headers
-
Idempotency-Key string
A unique key of the request, such as a UUID: a repeated request with the same key gets the first response, without doing the work again.
Max length:255 -
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Request bodyapplication/json
-
contact_id integerExample:
123 -
contact object
A contact found or created by its identities, instead of contact_id.
-
role string RequiredExample:
beneficiary -
primary booleanExample:
false -
muted booleanExample:
false -
valid_from string nullFormat:
date -
valid_until string nullFormat:
date
Responses
curl -X POST "https://api.worbo.app/v1/objects/fit.membership/456/relations" \
-H "Authorization: Bearer $WORBO_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"contact_id": 123,
"role": "beneficiary",
"primary": false,
"muted": false
}'
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', 'https://api.worbo.app/v1/objects/fit.membership/456/relations', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
'Idempotency-Key' => bin2hex(random_bytes(16)),
],
'json' => [
'contact_id' => 123,
'role' => 'beneficiary',
'primary' => false,
'muted' => false,
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/objects/fit.membership/456/relations', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
'Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify({
"contact_id": 123,
"role": "beneficiary",
"primary": false,
"muted": false,
}),
});
const data = await response.json();
import os
import uuid
import requests
response = requests.post(
"https://api.worbo.app/v1/objects/fit.membership/456/relations",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"contact_id": 123,
"role": "beneficiary",
"primary": False,
"muted": False,
},
)
data = response.json()
{
"data": {
"id": 77,
"contact_id": 123,
"role": "holder",
"primary": true,
"muted": false,
"valid_from": "2026-10-15",
"valid_until": "2026-10-15"
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/not_found",
"title": "Not found",
"status": 404,
"detail": "The resource does not exist or belongs to another organization.",
"code": "not_found"
}
{
"type": "https://worbo.app/docs/api/errors/idempotency_conflict",
"title": "The Idempotency-Key was used for another request",
"status": 409,
"detail": "The Idempotency-Key was used for another request.",
"code": "idempotency_conflict"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Remove the link of a contact to an object#
Path parameters
-
type string Required
The key of the type, such as fit.membership.
Example:fit.membership -
object integer Required
The ID of the object.
Example:456 -
relation integer Required
The ID of the relation.
Example:77
Query parameters
-
dry_run boolean
Runs every check and returns what would happen, without saving anything.
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl -X DELETE "https://api.worbo.app/v1/objects/fit.membership/456/relations/77" \
-H "Authorization: Bearer $WORBO_API_KEY"
$client = new \GuzzleHttp\Client();
$response = $client->request('DELETE', 'https://api.worbo.app/v1/objects/fit.membership/456/relations/77', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
]);
const response = await fetch('https://api.worbo.app/v1/objects/fit.membership/456/relations/77', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
},
});
import os
import requests
response = requests.delete(
"https://api.worbo.app/v1/objects/fit.membership/456/relations/77",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
)
No content
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/not_found",
"title": "Not found",
"status": 404,
"detail": "The resource does not exist or belongs to another organization.",
"code": "not_found"
}
{
"type": "https://worbo.app/docs/api/errors/idempotency_conflict",
"title": "The Idempotency-Key was used for another request",
"status": 409,
"detail": "The Idempotency-Key was used for another request.",
"code": "idempotency_conflict"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Reference
Files#
The files of the file fields: uploaded first, then given by ID as the value of the field.
Upload a file for a file field#
PDF, images, Office, text or CSV documents, up to 16 MB. A file not used by any field within a day is deleted.
Query parameters
-
dry_run boolean
Runs every check and returns what would happen, without saving anything.
Headers
-
Idempotency-Key string
A unique key of the request, such as a UUID: a repeated request with the same key gets the first response, without doing the work again.
Max length:255 -
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Request bodymultipart/form-data
-
file string Required
The file: PDF, image, Office, text or CSV, up to 16 MB.
Responses
curl -X POST "https://api.worbo.app/v1/files" \
-H "Authorization: Bearer $WORBO_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-F "file=@document.pdf"
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', 'https://api.worbo.app/v1/files', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
'Idempotency-Key' => bin2hex(random_bytes(16)),
],
'multipart' => [
['name' => 'file', 'contents' => fopen('document.pdf', 'r')],
],
]);
$data = json_decode((string) $response->getBody(), true);
import { openAsBlob } from 'node:fs';
const form = new FormData();
form.append('file', await openAsBlob('document.pdf'), 'document.pdf');
const response = await fetch('https://api.worbo.app/v1/files', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
'Idempotency-Key': crypto.randomUUID(),
},
body: form,
});
const data = await response.json();
import os
import uuid
import requests
response = requests.post(
"https://api.worbo.app/v1/files",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
files={"file": open("document.pdf", "rb")},
)
data = response.json()
{
"data": {
"id": 77,
"name": "contract.pdf",
"mime": "application/pdf",
"size": 48213,
"sha256": "string",
"contact_id": 123,
"object_id": 456,
"created_at": "2026-10-15T09:00:00+03:00"
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/idempotency_conflict",
"title": "The Idempotency-Key was used for another request",
"status": 409,
"detail": "The Idempotency-Key was used for another request.",
"code": "idempotency_conflict"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Read a file, with the contact or the object that uses it#
Path parameters
-
file integer Required
The ID of the file.
Example:77
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl "https://api.worbo.app/v1/files/77" \
-H "Authorization: Bearer $WORBO_API_KEY"
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.worbo.app/v1/files/77', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/files/77', {
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
},
});
const data = await response.json();
import os
import requests
response = requests.get(
"https://api.worbo.app/v1/files/77",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
)
data = response.json()
{
"data": {
"id": 77,
"name": "contract.pdf",
"mime": "application/pdf",
"size": 48213,
"sha256": "string",
"contact_id": 123,
"object_id": 456,
"created_at": "2026-10-15T09:00:00+03:00"
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/not_found",
"title": "Not found",
"status": 404,
"detail": "The resource does not exist or belongs to another organization.",
"code": "not_found"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Download the content of a file#
Path parameters
-
file integer Required
The ID of the file.
Example:77
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl "https://api.worbo.app/v1/files/77/content" \
-H "Authorization: Bearer $WORBO_API_KEY"
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.worbo.app/v1/files/77/content', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
]);
file_put_contents('download', (string) $response->getBody());
const response = await fetch('https://api.worbo.app/v1/files/77/content', {
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
},
});
const file = await response.blob();
import os
import requests
response = requests.get(
"https://api.worbo.app/v1/files/77/content",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
)
content = response.content
A file, as it was uploaded.
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/not_found",
"title": "Not found",
"status": 404,
"detail": "The resource does not exist or belongs to another organization.",
"code": "not_found"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Reference
Events#
What happened at the source; events start automations and update objects.
Record an event#
The contact and the object of the event are found or created; an event with the same ID from the same source is recorded only once.
Query parameters
-
dry_run boolean
Runs every check and returns what would happen, without saving anything.
Headers
-
Idempotency-Key string
A unique key of the request, such as a UUID: a repeated request with the same key gets the first response, without doing the work again.
Max length:255 -
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Request bodyapplication/json
-
id string
The ID of the event at the source; the same ID is recorded only once.
Example:m-1 -
type string RequiredExample:
membership.created -
occurred_at stringFormat:
date-timeExample:2026-10-15T09:00:00+03:00 -
source stringExample:
upfit -
contact object
-
object object
Show the fieldsHide the fields
-
type string
-
external_id string
-
role string
-
upsert object
-
-
properties object
-
historical booleanExample:
false -
update_existing_only booleanExample:
false
Responses
curl -X POST "https://api.worbo.app/v1/events" \
-H "Authorization: Bearer $WORBO_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"id": "m-1",
"type": "membership.created",
"occurred_at": "2026-10-15T09:00:00+03:00",
"source": "upfit",
"contact": {
"phone": "+40722123456"
},
"object": {
"type": "fit.membership",
"external_id": "abn-7781",
"upsert": {
"statuses": {
"lifecycle": "active"
}
}
},
"properties": {
"plan": "plan-12"
},
"historical": false,
"update_existing_only": false
}'
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', 'https://api.worbo.app/v1/events', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
'Idempotency-Key' => bin2hex(random_bytes(16)),
],
'json' => [
'id' => 'm-1',
'type' => 'membership.created',
'occurred_at' => '2026-10-15T09:00:00+03:00',
'source' => 'upfit',
'contact' => [
'phone' => '+40722123456',
],
'object' => [
'type' => 'fit.membership',
'external_id' => 'abn-7781',
'upsert' => [
'statuses' => [
'lifecycle' => 'active',
],
],
],
'properties' => [
'plan' => 'plan-12',
],
'historical' => false,
'update_existing_only' => false,
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/events', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
'Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify({
"id": "m-1",
"type": "membership.created",
"occurred_at": "2026-10-15T09:00:00+03:00",
"source": "upfit",
"contact": {
"phone": "+40722123456",
},
"object": {
"type": "fit.membership",
"external_id": "abn-7781",
"upsert": {
"statuses": {
"lifecycle": "active",
},
},
},
"properties": {
"plan": "plan-12",
},
"historical": false,
"update_existing_only": false,
}),
});
const data = await response.json();
import os
import uuid
import requests
response = requests.post(
"https://api.worbo.app/v1/events",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"id": "m-1",
"type": "membership.created",
"occurred_at": "2026-10-15T09:00:00+03:00",
"source": "upfit",
"contact": {
"phone": "+40722123456",
},
"object": {
"type": "fit.membership",
"external_id": "abn-7781",
"upsert": {
"statuses": {
"lifecycle": "active",
},
},
},
"properties": {
"plan": "plan-12",
},
"historical": False,
"update_existing_only": False,
},
)
data = response.json()
{
"data": {
"event_id": 8812,
"type": "membership.created",
"source": "upfit",
"source_id": "m-1",
"occurred_at": "2026-10-15T09:00:00+03:00",
"duplicate": false,
"historical": false,
"contact_id": 123,
"object_id": 456,
"warnings": {},
"identity_conflict": {}
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/idempotency_conflict",
"title": "The Idempotency-Key was used for another request",
"status": 409,
"detail": "The Idempotency-Key was used for another request.",
"code": "idempotency_conflict"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Record up to 100 events#
Query parameters
-
dry_run boolean
Runs every check and returns what would happen, without saving anything.
Headers
-
Idempotency-Key string
A unique key of the request, such as a UUID: a repeated request with the same key gets the first response, without doing the work again.
Max length:255 -
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Request bodyapplication/json
-
items object[] Required
Show the fieldsHide the fields
-
id string
The ID of the event at the source; the same ID is recorded only once.
Example:m-1 -
type string RequiredExample:
membership.created -
occurred_at stringFormat:
date-timeExample:2026-10-15T09:00:00+03:00 -
source stringExample:
upfit -
contact object
-
object object
Show the fieldsHide the fields
-
type string
-
external_id string
-
role string
-
upsert object
-
-
properties object
-
historical booleanExample:
false -
update_existing_only booleanExample:
false
-
Responses
curl -X POST "https://api.worbo.app/v1/events/batch" \
-H "Authorization: Bearer $WORBO_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"id": "m-1",
"type": "membership.created",
"occurred_at": "2026-10-15T09:00:00+03:00",
"source": "upfit",
"contact": {
"phone": "+40722123456"
},
"object": {
"type": "fit.membership",
"external_id": "abn-7781",
"upsert": {
"statuses": {
"lifecycle": "active"
}
}
},
"properties": {
"plan": "plan-12"
},
"historical": false,
"update_existing_only": false
}
]
}'
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', 'https://api.worbo.app/v1/events/batch', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
'Idempotency-Key' => bin2hex(random_bytes(16)),
],
'json' => [
'items' => [
[
'id' => 'm-1',
'type' => 'membership.created',
'occurred_at' => '2026-10-15T09:00:00+03:00',
'source' => 'upfit',
'contact' => [
'phone' => '+40722123456',
],
'object' => [
'type' => 'fit.membership',
'external_id' => 'abn-7781',
'upsert' => [
'statuses' => [
'lifecycle' => 'active',
],
],
],
'properties' => [
'plan' => 'plan-12',
],
'historical' => false,
'update_existing_only' => false,
],
],
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/events/batch', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
'Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify({
"items": [
{
"id": "m-1",
"type": "membership.created",
"occurred_at": "2026-10-15T09:00:00+03:00",
"source": "upfit",
"contact": {
"phone": "+40722123456",
},
"object": {
"type": "fit.membership",
"external_id": "abn-7781",
"upsert": {
"statuses": {
"lifecycle": "active",
},
},
},
"properties": {
"plan": "plan-12",
},
"historical": false,
"update_existing_only": false,
},
],
}),
});
const data = await response.json();
import os
import uuid
import requests
response = requests.post(
"https://api.worbo.app/v1/events/batch",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"items": [
{
"id": "m-1",
"type": "membership.created",
"occurred_at": "2026-10-15T09:00:00+03:00",
"source": "upfit",
"contact": {
"phone": "+40722123456",
},
"object": {
"type": "fit.membership",
"external_id": "abn-7781",
"upsert": {
"statuses": {
"lifecycle": "active",
},
},
},
"properties": {
"plan": "plan-12",
},
"historical": False,
"update_existing_only": False,
},
],
},
)
data = response.json()
{
"data": [
{
"index": 0,
"status": "created",
"data": {
"event_id": 8812,
"type": "membership.created",
"source": "upfit",
"source_id": "m-1",
"occurred_at": "2026-10-15T09:00:00+03:00",
"duplicate": false,
"historical": false,
"contact_id": 123,
"object_id": 456,
"warnings": {},
"identity_conflict": {}
},
"error": {
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "The name field is required.",
"code": "validation_failed",
"errors": {
"name": [
"The name field is required."
]
}
}
}
]
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/idempotency_conflict",
"title": "The Idempotency-Key was used for another request",
"status": 409,
"detail": "The Idempotency-Key was used for another request.",
"code": "idempotency_conflict"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Reference
Messages#
WhatsApp messages sent and received, through the same rules as in the dashboard.
List the messages#
Query parameters
-
contact_id integer
Only the messages of this contact.
Example:123 -
direction string
Received or sent.
Possible values:inboundoutbound -
status string
Only the messages with this status.
Example:delivered -
since string
Only what changed at or after this moment.
Format:date-timeExample:2026-10-01T00:00:00+03:00 -
limit integer
How many items a page has, between 1 and 100.
Default:25Minimum:1Maximum:100 -
cursor string
The next_cursor of the previous page.
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl "https://api.worbo.app/v1/messages" \
-H "Authorization: Bearer $WORBO_API_KEY"
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.worbo.app/v1/messages', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/messages', {
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
},
});
const data = await response.json();
import os
import requests
response = requests.get(
"https://api.worbo.app/v1/messages",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
)
data = response.json()
{
"data": [
{
"id": 7001,
"contact_id": 123,
"number_id": 3,
"direction": "outbound",
"type": "template",
"text": "string",
"untrusted_text": "string",
"template": {
"name": "booking_confirmed",
"language": "ro"
},
"category": "utility",
"status": "pending",
"reason": "string",
"error_code": "string",
"wamid": "string",
"created_at": "2026-10-15T09:00:00+03:00",
"sent_at": "2026-10-15T09:00:00+03:00",
"delivered_at": "2026-10-15T09:00:00+03:00",
"read_at": "2026-10-15T09:00:00+03:00",
"failed_at": "2026-10-15T09:00:00+03:00"
}
],
"next_cursor": "string",
"has_more": true
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Send a template, a free text or a form to a contact#
The message goes through the same rules as in the dashboard: consent, quiet hours, frequency and limits. A free text or a form needs the 24-hour window open; a refusal comes with the code of the reason, such as window_closed. The answers to a form come with the webhook flow.submitted.
Query parameters
-
dry_run boolean
Runs every check and returns what would happen, without saving anything.
Headers
-
Idempotency-Key string
A unique key of the request, such as a UUID: a repeated request with the same key gets the first response, without doing the work again.
Max length:255 -
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Request bodyapplication/json
-
contact_id integer RequiredExample:
123 -
number_id integer null
-
template object
Show the fieldsHide the fields
-
name string RequiredExample:
booking_confirmed -
language stringExample:
ro -
variables string[]
The values of the variables; a missing value is filled from the field of the variable. In a library template left unchanged, a missing first name takes the fallback text of the library, for example “Hello from the gym team.”.
-
-
text string
A free text, only while the 24-hour window is open; instead of template.
-
flow object
Show the fieldsHide the fields
-
key string Required
The key of a published WhatsApp form of the account of the number.
Example:nps -
body string Required
The text of the message, above the button that opens the form.
Example:Ne spui în 10 secunde cum a fost?
-
Responses
curl -X POST "https://api.worbo.app/v1/messages" \
-H "Authorization: Bearer $WORBO_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"contact_id": 123,
"template": {
"name": "booking_confirmed",
"language": "ro",
"variables": [
"Ana",
"3 octombrie, 18:00"
]
},
"flow": {
"key": "nps",
"body": "Ne spui în 10 secunde cum a fost?"
}
}'
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', 'https://api.worbo.app/v1/messages', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
'Idempotency-Key' => bin2hex(random_bytes(16)),
],
'json' => [
'contact_id' => 123,
'template' => [
'name' => 'booking_confirmed',
'language' => 'ro',
'variables' => [
'Ana',
'3 octombrie, 18:00',
],
],
'flow' => [
'key' => 'nps',
'body' => 'Ne spui în 10 secunde cum a fost?',
],
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/messages', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
'Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify({
"contact_id": 123,
"template": {
"name": "booking_confirmed",
"language": "ro",
"variables": [
"Ana",
"3 octombrie, 18:00",
],
},
"flow": {
"key": "nps",
"body": "Ne spui în 10 secunde cum a fost?",
},
}),
});
const data = await response.json();
import os
import uuid
import requests
response = requests.post(
"https://api.worbo.app/v1/messages",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"contact_id": 123,
"template": {
"name": "booking_confirmed",
"language": "ro",
"variables": [
"Ana",
"3 octombrie, 18:00",
],
},
"flow": {
"key": "nps",
"body": "Ne spui în 10 secunde cum a fost?",
},
},
)
data = response.json()
{
"data": {
"id": 7001,
"contact_id": 123,
"number_id": 3,
"direction": "outbound",
"type": "template",
"text": "string",
"untrusted_text": "string",
"template": {
"name": "booking_confirmed",
"language": "ro"
},
"category": "utility",
"status": "pending",
"reason": "string",
"error_code": "string",
"wamid": "string",
"created_at": "2026-10-15T09:00:00+03:00",
"sent_at": "2026-10-15T09:00:00+03:00",
"delivered_at": "2026-10-15T09:00:00+03:00",
"read_at": "2026-10-15T09:00:00+03:00",
"failed_at": "2026-10-15T09:00:00+03:00"
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/idempotency_conflict",
"title": "The Idempotency-Key was used for another request",
"status": 409,
"detail": "The Idempotency-Key was used for another request.",
"code": "idempotency_conflict"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Read a message and its status#
Path parameters
-
message integer Required
The ID of the message.
Example:7001
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl "https://api.worbo.app/v1/messages/7001" \
-H "Authorization: Bearer $WORBO_API_KEY"
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.worbo.app/v1/messages/7001', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/messages/7001', {
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
},
});
const data = await response.json();
import os
import requests
response = requests.get(
"https://api.worbo.app/v1/messages/7001",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
)
data = response.json()
{
"data": {
"id": 7001,
"contact_id": 123,
"number_id": 3,
"direction": "outbound",
"type": "template",
"text": "string",
"untrusted_text": "string",
"template": {
"name": "booking_confirmed",
"language": "ro"
},
"category": "utility",
"status": "pending",
"reason": "string",
"error_code": "string",
"wamid": "string",
"created_at": "2026-10-15T09:00:00+03:00",
"sent_at": "2026-10-15T09:00:00+03:00",
"delivered_at": "2026-10-15T09:00:00+03:00",
"read_at": "2026-10-15T09:00:00+03:00",
"failed_at": "2026-10-15T09:00:00+03:00"
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/not_found",
"title": "Not found",
"status": 404,
"detail": "The resource does not exist or belongs to another organization.",
"code": "not_found"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Read the proof of a message sent with a durable medium candidate template#
The exact text the contact got and the document of the header kept by Worbo, with their SHA-256 fingerprints, and the moments Meta reported. It does not replace the email required by law.
Path parameters
-
message integer Required
The ID of the message.
Example:7001
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl "https://api.worbo.app/v1/messages/7001/evidence" \
-H "Authorization: Bearer $WORBO_API_KEY"
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.worbo.app/v1/messages/7001/evidence', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/messages/7001/evidence', {
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
},
});
const data = await response.json();
import os
import requests
response = requests.get(
"https://api.worbo.app/v1/messages/7001/evidence",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
)
data = response.json()
{
"data": {
"id": 52,
"message_id": 7001,
"contact_id": 123,
"number_id": 3,
"template": {
"name": "withdrawal_received",
"language": "ro"
},
"recipient": "+40722123456",
"wamid": "string",
"text": "string",
"text_sha256": "string",
"document": {
"name": "confirmare.pdf",
"mime": "application/pdf",
"size": 48213,
"sha256": "string",
"stored": true,
"error": "string",
"url": "string"
},
"sent_at": "2026-10-15T09:00:00+03:00",
"delivered_at": "2026-10-15T09:00:00+03:00",
"read_at": "2026-10-15T09:00:00+03:00",
"failed_at": "2026-10-15T09:00:00+03:00",
"created_at": "2026-10-15T09:00:00+03:00"
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/not_found",
"title": "Not found",
"status": 404,
"detail": "The resource does not exist or belongs to another organization.",
"code": "not_found"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Download the document kept as the proof of a message#
Path parameters
-
message integer Required
The ID of the message.
Example:7001
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl "https://api.worbo.app/v1/messages/7001/evidence/document" \
-H "Authorization: Bearer $WORBO_API_KEY"
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.worbo.app/v1/messages/7001/evidence/document', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
]);
file_put_contents('download', (string) $response->getBody());
const response = await fetch('https://api.worbo.app/v1/messages/7001/evidence/document', {
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
},
});
const file = await response.blob();
import os
import requests
response = requests.get(
"https://api.worbo.app/v1/messages/7001/evidence/document",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
)
content = response.content
A file, as it was uploaded.
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/not_found",
"title": "Not found",
"status": 404,
"detail": "The resource does not exist or belongs to another organization.",
"code": "not_found"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Reference
Document vault#
Files given to a contact through a link that expires, announced on WhatsApp and opened only with a code sent to the phone of the contact.
Read a document of the vault, with the state of its link and the openings#
Path parameters
-
vault integer Required
The ID of the document in the vault.
Example:15
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl "https://api.worbo.app/v1/vault/15" \
-H "Authorization: Bearer $WORBO_API_KEY"
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.worbo.app/v1/vault/15', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/vault/15', {
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
},
});
const data = await response.json();
import os
import requests
response = requests.get(
"https://api.worbo.app/v1/vault/15",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
)
data = response.json()
{
"data": {
"id": 15,
"contact_id": 123,
"file_id": 77,
"number_id": 3,
"title": "Rezultatul analizelor",
"state": "active",
"expires_at": "2026-10-15T09:00:00+03:00",
"opened_at": "2026-10-15T09:00:00+03:00",
"opens": 1,
"message_id": 7001,
"created_at": "2026-10-15T09:00:00+03:00",
"url": "https://example.com"
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/not_found",
"title": "Not found",
"status": 404,
"detail": "The resource does not exist or belongs to another organization.",
"code": "not_found"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Revoke the link of a document of the vault#
Path parameters
-
vault integer Required
The ID of the document in the vault.
Example:15
Query parameters
-
dry_run boolean
Runs every check and returns what would happen, without saving anything.
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl -X DELETE "https://api.worbo.app/v1/vault/15" \
-H "Authorization: Bearer $WORBO_API_KEY"
$client = new \GuzzleHttp\Client();
$response = $client->request('DELETE', 'https://api.worbo.app/v1/vault/15', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
]);
const response = await fetch('https://api.worbo.app/v1/vault/15', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
},
});
import os
import requests
response = requests.delete(
"https://api.worbo.app/v1/vault/15",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
)
No content
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/not_found",
"title": "Not found",
"status": 404,
"detail": "The resource does not exist or belongs to another organization.",
"code": "not_found"
}
{
"type": "https://worbo.app/docs/api/errors/idempotency_conflict",
"title": "The Idempotency-Key was used for another request",
"status": 409,
"detail": "The Idempotency-Key was used for another request.",
"code": "idempotency_conflict"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Reference
Verification codes#
One-time codes sent on WhatsApp with the authentication template of the number, checked by Worbo or by the organization.
Send a verification code on WhatsApp#
Without code, Worbo makes a 6-digit code, valid for 10 minutes, and checks it with verifyOtp; with code, the organization checks it itself. The message leaves at once, with the authentication template of the number, approved on its page. A phone known to have no WhatsApp gets otp_undeliverable at once, so the site can offer SMS or email; otherwise a failure comes as the status of the message and the webhook message.failed. A phone gets at most 5 codes in 15 minutes and one every 30 seconds (otp_rate_limited, otp_too_soon). The code never appears in answers, webhooks or the inbox.
Query parameters
-
dry_run boolean
Runs every check and returns what would happen, without saving anything.
Headers
-
Idempotency-Key string
A unique key of the request, such as a UUID: a repeated request with the same key gets the first response, without doing the work again.
Max length:255 -
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Request bodyapplication/json
-
phone string Required
The phone that gets the code, in international format.
Example:+40722123456 -
code string
The code made by the organization, which checks it itself; without it, Worbo makes the code and checks it.
Example:482913 -
number_id integer null
-
language string
The language of the message; by default, the language of the contact.
Example:ro -
purpose string
What the code is for, kept with it, such as login or document.
Max length:64Example:login
Responses
curl -X POST "https://api.worbo.app/v1/otp" \
-H "Authorization: Bearer $WORBO_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"phone": "+40722123456",
"code": "482913",
"language": "ro",
"purpose": "login"
}'
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', 'https://api.worbo.app/v1/otp', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
'Idempotency-Key' => bin2hex(random_bytes(16)),
],
'json' => [
'phone' => '+40722123456',
'code' => '482913',
'language' => 'ro',
'purpose' => 'login',
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/otp', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
'Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify({
"phone": "+40722123456",
"code": "482913",
"language": "ro",
"purpose": "login",
}),
});
const data = await response.json();
import os
import uuid
import requests
response = requests.post(
"https://api.worbo.app/v1/otp",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"phone": "+40722123456",
"code": "482913",
"language": "ro",
"purpose": "login",
},
)
data = response.json()
{
"data": {
"id": 880,
"phone": "+40722123456",
"purpose": "login",
"verifiable": true,
"expires_at": "2026-10-15T09:00:00+03:00",
"message_id": 7001,
"status": "pending"
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/idempotency_conflict",
"title": "The Idempotency-Key was used for another request",
"status": 409,
"detail": "The Idempotency-Key was used for another request.",
"code": "idempotency_conflict"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Check a verification code made by Worbo#
A code is used once, within its term, with at most 5 attempts; the answer says why a check failed. A code given by the organization gets 409 otp_not_verifiable.
Path parameters
-
otp integer Required
The ID of the verification code, from the answer that sent it.
Example:880
Query parameters
-
dry_run boolean
Runs every check and returns what would happen, without saving anything.
Headers
-
Idempotency-Key string
A unique key of the request, such as a UUID: a repeated request with the same key gets the first response, without doing the work again.
Max length:255 -
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Request bodyapplication/json
-
code string RequiredExample:
482913
Responses
curl -X POST "https://api.worbo.app/v1/otp/880/verify" \
-H "Authorization: Bearer $WORBO_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"code": "482913"
}'
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', 'https://api.worbo.app/v1/otp/880/verify', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
'Idempotency-Key' => bin2hex(random_bytes(16)),
],
'json' => [
'code' => '482913',
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/otp/880/verify', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
'Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify({
"code": "482913",
}),
});
const data = await response.json();
import os
import uuid
import requests
response = requests.post(
"https://api.worbo.app/v1/otp/880/verify",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"code": "482913",
},
)
data = response.json()
{
"data": {
"id": 880,
"verified": true,
"reason": "wrong_code",
"attempts_left": 4
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/not_found",
"title": "Not found",
"status": 404,
"detail": "The resource does not exist or belongs to another organization.",
"code": "not_found"
}
{
"type": "https://worbo.app/docs/api/errors/idempotency_conflict",
"title": "The Idempotency-Key was used for another request",
"status": 409,
"detail": "The Idempotency-Key was used for another request.",
"code": "idempotency_conflict"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Reference
Recommendations#
The rules of the recommendation engine and what one of them would recommend now to a contact, with the reason of each item.
List the recommendation rules#
The active rules, made in the dashboard at Recommendations: the catalog, the types in their order, the number of items and the cooldowns. The automations use them by key.
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl "https://api.worbo.app/v1/recommendation-rules" \
-H "Authorization: Bearer $WORBO_API_KEY"
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.worbo.app/v1/recommendation-rules', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/recommendation-rules', {
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
},
});
const data = await response.json();
import os
import requests
response = requests.get(
"https://api.worbo.app/v1/recommendation-rules",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
)
data = response.json()
{
"data": [
{
"key": "ecom.cross_sell",
"name": "Bought together",
"catalog": "ecom.products",
"types": [
{
"type": "co_purchase",
"params": {}
}
],
"max_items": 3,
"contact_cooldown_days": 14,
"item_cooldown_days": 30,
"fill_bestsellers": true
}
]
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Read the results of a recommendation rule#
On a period, by default the last 30 days: the recommendations sent and their recipients, the steps skipped by reason, the clicks on the items, the conversions within 7 days of the message (an order for the products, a booking for the classes, a membership for the plans), the revenue per recipient, the control group of the automations, the opt-outs after the message and the cost at Meta per conversion.
Path parameters
-
rule string Required
The key of the recommendation rule.
Example:ecom.cross_sell
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl "https://api.worbo.app/v1/recommendation-rules/ecom.cross_sell/results" \
-H "Authorization: Bearer $WORBO_API_KEY"
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.worbo.app/v1/recommendation-rules/ecom.cross_sell/results', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/recommendation-rules/ecom.cross_sell/results', {
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
},
});
const data = await response.json();
import os
import requests
response = requests.get(
"https://api.worbo.app/v1/recommendation-rules/ecom.cross_sell/results",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
)
data = response.json()
{
"data": {
"rule": "ecom.cross_sell",
"from": "2026-10-15T09:00:00+03:00",
"to": "2026-10-15T09:00:00+03:00",
"sent": 120,
"recipients": 118,
"skipped": {},
"clicks": 34,
"clickers": 29,
"items": [
{
"id": 310,
"name": "Filtre de cafea",
"clicks": 12
}
],
"goals": [
"order.created"
],
"conversions": 9,
"conversion_rate": 0.075,
"revenue": 1840.5,
"revenue_per_recipient": 15.6,
"currency": "RON",
"control": {
"contacts": 13,
"conversions": 0,
"rate": 0
},
"lift": 0.075,
"opt_outs": 1,
"cost": 18.4,
"cost_per_conversion": 2.04
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/not_found",
"title": "Not found",
"status": 404,
"detail": "The resource does not exist or belongs to another organization.",
"code": "not_found"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Preview a recommendation rule on a contact#
What the rule would recommend now to the contact, with the reason of each item, the items left out and why, the cooldown of the rule and the consent needed. Nothing is sent and the cooldowns stay as they are. The recommendations from the history of the contact need the purpose recommendations besides marketing.
Query parameters
-
dry_run boolean
Runs every check and returns what would happen, without saving anything.
Headers
-
Idempotency-Key string
A unique key of the request, such as a UUID: a repeated request with the same key gets the first response, without doing the work again.
Max length:255 -
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Request bodyapplication/json
-
rule string RequiredExample:
ecom.cross_sell -
contact_id integer RequiredExample:
123
Responses
curl -X POST "https://api.worbo.app/v1/recommendations/preview" \
-H "Authorization: Bearer $WORBO_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"rule": "ecom.cross_sell",
"contact_id": 123
}'
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', 'https://api.worbo.app/v1/recommendations/preview', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
'Idempotency-Key' => bin2hex(random_bytes(16)),
],
'json' => [
'rule' => 'ecom.cross_sell',
'contact_id' => 123,
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/recommendations/preview', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
'Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify({
"rule": "ecom.cross_sell",
"contact_id": 123,
}),
});
const data = await response.json();
import os
import uuid
import requests
response = requests.post(
"https://api.worbo.app/v1/recommendations/preview",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"rule": "ecom.cross_sell",
"contact_id": 123,
},
)
data = response.json()
{
"data": {
"rule": "ecom.cross_sell",
"contact_id": 123,
"items": [
{
"id": 310,
"name": "Filtre de cafea",
"price": 19.9,
"currency": "RON",
"url": "string",
"image_url": "string",
"type": "co_purchase",
"score": 5,
"reason": "Often bought together with Coffee beans 1kg (5 orders).",
"details": {}
}
],
"excluded": {},
"empty": "cooldown",
"cooldown_until": "2026-10-15T09:00:00+03:00",
"purpose": "marketing",
"sendable": true,
"consent_reason": "string"
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/idempotency_conflict",
"title": "The Idempotency-Key was used for another request",
"status": 409,
"detail": "The Idempotency-Key was used for another request.",
"code": "idempotency_conflict"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Reference
Templates#
WhatsApp templates: drafts checked by the linter, submission to Meta and status.
List the templates#
Query parameters
-
number_id integer
Only the templates of this number.
Example:3 -
status string
Only the templates with this status.
Example:approved -
name string
Only the templates with this name.
Example:booking_confirmed -
limit integer
How many items a page has, between 1 and 100.
Default:25Minimum:1Maximum:100 -
cursor string
The next_cursor of the previous page.
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl "https://api.worbo.app/v1/templates" \
-H "Authorization: Bearer $WORBO_API_KEY"
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.worbo.app/v1/templates', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/templates', {
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
},
});
const data = await response.json();
import os
import requests
response = requests.get(
"https://api.worbo.app/v1/templates",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
)
data = response.json()
{
"data": [
{
"id": 31,
"number_id": 3,
"name": "booking_confirmed",
"language": "ro",
"category": "utility",
"status": "approved",
"rejected_reason": "string",
"quality": "string",
"components": [
{}
],
"variables": [
{
"example": "Ana",
"field": "contact.first_name"
}
],
"source": "string",
"submitted_at": "2026-10-15T09:00:00+03:00",
"created_at": "2026-10-15T09:00:00+03:00",
"updated_at": "2026-10-15T09:00:00+03:00"
}
],
"next_cursor": "string",
"has_more": true
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Draft a template, checked by the linter#
Query parameters
-
dry_run boolean
Runs every check and returns what would happen, without saving anything.
Headers
-
Idempotency-Key string
A unique key of the request, such as a UUID: a repeated request with the same key gets the first response, without doing the work again.
Max length:255 -
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Request bodyapplication/json
-
number_id integer null
-
name string RequiredExample:
booking_confirmed -
language string RequiredExample:
ro -
category string RequiredPossible values:
utilitymarketing -
body string RequiredExample:
Bună, {{1}}. Rezervarea ta pentru {{2}} este confirmată. -
examples string[]
-
fields string[]
For each variable, the field that fills it, such as contact.first_name, or null.
-
header_format string
-
header_text string
-
header_example string
-
footer string
-
buttons object[]
Responses
curl -X POST "https://api.worbo.app/v1/templates" \
-H "Authorization: Bearer $WORBO_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"name": "booking_confirmed",
"language": "ro",
"category": "utility",
"body": "Bună, {{1}}. Rezervarea ta pentru {{2}} este confirmată.",
"examples": [
"Ana",
"3 octombrie, 18:00"
],
"fields": [
"contact.first_name",
null
]
}'
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', 'https://api.worbo.app/v1/templates', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
'Idempotency-Key' => bin2hex(random_bytes(16)),
],
'json' => [
'name' => 'booking_confirmed',
'language' => 'ro',
'category' => 'utility',
'body' => 'Bună, {{1}}. Rezervarea ta pentru {{2}} este confirmată.',
'examples' => [
'Ana',
'3 octombrie, 18:00',
],
'fields' => [
'contact.first_name',
null,
],
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/templates', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
'Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "booking_confirmed",
"language": "ro",
"category": "utility",
"body": "Bună, {{1}}. Rezervarea ta pentru {{2}} este confirmată.",
"examples": [
"Ana",
"3 octombrie, 18:00",
],
"fields": [
"contact.first_name",
null,
],
}),
});
const data = await response.json();
import os
import uuid
import requests
response = requests.post(
"https://api.worbo.app/v1/templates",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"name": "booking_confirmed",
"language": "ro",
"category": "utility",
"body": "Bună, {{1}}. Rezervarea ta pentru {{2}} este confirmată.",
"examples": [
"Ana",
"3 octombrie, 18:00",
],
"fields": [
"contact.first_name",
None,
],
},
)
data = response.json()
{
"data": {
"id": 31,
"number_id": 3,
"name": "booking_confirmed",
"language": "ro",
"category": "utility",
"status": "approved",
"rejected_reason": "string",
"quality": "string",
"components": [
{}
],
"variables": [
{
"example": "Ana",
"field": "contact.first_name"
}
],
"source": "string",
"submitted_at": "2026-10-15T09:00:00+03:00",
"created_at": "2026-10-15T09:00:00+03:00",
"updated_at": "2026-10-15T09:00:00+03:00",
"lint": {}
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/idempotency_conflict",
"title": "The Idempotency-Key was used for another request",
"status": 409,
"detail": "The Idempotency-Key was used for another request.",
"code": "idempotency_conflict"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Read a template#
Path parameters
-
template integer Required
The ID of the template.
Example:31
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl "https://api.worbo.app/v1/templates/31" \
-H "Authorization: Bearer $WORBO_API_KEY"
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.worbo.app/v1/templates/31', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/templates/31', {
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
},
});
const data = await response.json();
import os
import requests
response = requests.get(
"https://api.worbo.app/v1/templates/31",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
)
data = response.json()
{
"data": {
"id": 31,
"number_id": 3,
"name": "booking_confirmed",
"language": "ro",
"category": "utility",
"status": "approved",
"rejected_reason": "string",
"quality": "string",
"components": [
{}
],
"variables": [
{
"example": "Ana",
"field": "contact.first_name"
}
],
"source": "string",
"submitted_at": "2026-10-15T09:00:00+03:00",
"created_at": "2026-10-15T09:00:00+03:00",
"updated_at": "2026-10-15T09:00:00+03:00"
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/not_found",
"title": "Not found",
"status": 404,
"detail": "The resource does not exist or belongs to another organization.",
"code": "not_found"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Submit a template to Meta#
Stopped by the linter, with the reason, or accepted, with the status in review.
Path parameters
-
template integer Required
The ID of the template.
Example:31
Query parameters
-
dry_run boolean
Runs every check and returns what would happen, without saving anything.
Headers
-
Idempotency-Key string
A unique key of the request, such as a UUID: a repeated request with the same key gets the first response, without doing the work again.
Max length:255 -
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl -X POST "https://api.worbo.app/v1/templates/31/submit" \
-H "Authorization: Bearer $WORBO_API_KEY" \
-H "Idempotency-Key: $(uuidgen)"
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', 'https://api.worbo.app/v1/templates/31/submit', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
'Idempotency-Key' => bin2hex(random_bytes(16)),
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/templates/31/submit', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
'Idempotency-Key': crypto.randomUUID(),
},
});
const data = await response.json();
import os
import uuid
import requests
response = requests.post(
"https://api.worbo.app/v1/templates/31/submit",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
)
data = response.json()
{
"data": {
"id": 31,
"number_id": 3,
"name": "booking_confirmed",
"language": "ro",
"category": "utility",
"status": "approved",
"rejected_reason": "string",
"quality": "string",
"components": [
{}
],
"variables": [
{
"example": "Ana",
"field": "contact.first_name"
}
],
"source": "string",
"submitted_at": "2026-10-15T09:00:00+03:00",
"created_at": "2026-10-15T09:00:00+03:00",
"updated_at": "2026-10-15T09:00:00+03:00"
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/not_found",
"title": "Not found",
"status": 404,
"detail": "The resource does not exist or belongs to another organization.",
"code": "not_found"
}
{
"type": "https://worbo.app/docs/api/errors/idempotency_conflict",
"title": "The Idempotency-Key was used for another request",
"status": 409,
"detail": "The Idempotency-Key was used for another request.",
"code": "idempotency_conflict"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Reference
Offers#
Offers found by their code and redeemed, at the front desk or in the software of the source.
Find an offer by its code#
Path parameters
-
code string Required
The code of the offer.
Example:AUTUMN-7KQ2
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl "https://api.worbo.app/v1/offers/AUTUMN-7KQ2" \
-H "Authorization: Bearer $WORBO_API_KEY"
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.worbo.app/v1/offers/AUTUMN-7KQ2', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/offers/AUTUMN-7KQ2', {
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
},
});
const data = await response.json();
import os
import requests
response = requests.get(
"https://api.worbo.app/v1/offers/AUTUMN-7KQ2",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
)
data = response.json()
{
"data": {
"id": 456,
"type": "fit.membership",
"source": "string",
"external_id": "abn-7781",
"title": "Full access 12 luni",
"fields": {
"plan": 12,
"starts_on": "2026-09-01",
"ends_on": "2027-08-31"
},
"statuses": {
"lifecycle": "active",
"payment": "ok"
},
"raw_status": "string",
"relations": [
{
"id": 77,
"contact_id": 123,
"role": "holder",
"primary": true,
"muted": false,
"valid_from": "2026-10-15",
"valid_until": "2026-10-15"
}
],
"created_at": "2026-10-15T09:00:00+03:00",
"updated_at": "2026-10-15T09:00:00+03:00"
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/not_found",
"title": "Not found",
"status": 404,
"detail": "The resource does not exist or belongs to another organization.",
"code": "not_found"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Mark an offer as used#
Path parameters
-
code string Required
The code of the offer.
Example:AUTUMN-7KQ2
Query parameters
-
dry_run boolean
Runs every check and returns what would happen, without saving anything.
Headers
-
Idempotency-Key string
A unique key of the request, such as a UUID: a repeated request with the same key gets the first response, without doing the work again.
Max length:255 -
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Request bodyapplication/json
-
location_id integer null
-
beneficiary_contact_id integer null
-
occurred_at string nullFormat:
date-time
Responses
curl -X POST "https://api.worbo.app/v1/offers/AUTUMN-7KQ2/redeem" \
-H "Authorization: Bearer $WORBO_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{}'
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', 'https://api.worbo.app/v1/offers/AUTUMN-7KQ2/redeem', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
'Idempotency-Key' => bin2hex(random_bytes(16)),
],
'json' => new \stdClass(),
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/offers/AUTUMN-7KQ2/redeem', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
'Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify({}),
});
const data = await response.json();
import os
import uuid
import requests
response = requests.post(
"https://api.worbo.app/v1/offers/AUTUMN-7KQ2/redeem",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={},
)
data = response.json()
{
"data": {
"id": 456,
"type": "fit.membership",
"source": "string",
"external_id": "abn-7781",
"title": "Full access 12 luni",
"fields": {
"plan": 12,
"starts_on": "2026-09-01",
"ends_on": "2027-08-31"
},
"statuses": {
"lifecycle": "active",
"payment": "ok"
},
"raw_status": "string",
"relations": [
{
"id": 77,
"contact_id": 123,
"role": "holder",
"primary": true,
"muted": false,
"valid_from": "2026-10-15",
"valid_until": "2026-10-15"
}
],
"created_at": "2026-10-15T09:00:00+03:00",
"updated_at": "2026-10-15T09:00:00+03:00"
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/not_found",
"title": "Not found",
"status": 404,
"detail": "The resource does not exist or belongs to another organization.",
"code": "not_found"
}
{
"type": "https://worbo.app/docs/api/errors/idempotency_conflict",
"title": "The Idempotency-Key was used for another request",
"status": 409,
"detail": "The Idempotency-Key was used for another request.",
"code": "idempotency_conflict"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Reference
Segments#
Dynamic segments of contacts, counted live.
List the segments#
Query parameters
-
limit integer
How many items a page has, between 1 and 100.
Default:25Minimum:1Maximum:100 -
cursor string
The next_cursor of the previous page.
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl "https://api.worbo.app/v1/segments" \
-H "Authorization: Bearer $WORBO_API_KEY"
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.worbo.app/v1/segments', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/segments', {
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
},
});
const data = await response.json();
import os
import requests
response = requests.get(
"https://api.worbo.app/v1/segments",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
)
data = response.json()
{
"data": [
{
"id": 12,
"name": "Toți membrii",
"description": "string",
"definition": {
"match": "all",
"conditions": [
{
"type": "field",
"field": "first_name",
"operator": "not_empty"
}
]
},
"last_count": 240,
"counted_at": "2026-10-15T09:00:00+03:00",
"created_at": "2026-10-15T09:00:00+03:00"
}
],
"next_cursor": "string",
"has_more": true
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Create a segment#
Query parameters
-
dry_run boolean
Runs every check and returns what would happen, without saving anything.
Headers
-
Idempotency-Key string
A unique key of the request, such as a UUID: a repeated request with the same key gets the first response, without doing the work again.
Max length:255 -
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Request bodyapplication/json
-
name string RequiredExample:
Toți membrii -
description string
-
definition object Required
Show the fieldsHide the fields
-
match string RequiredPossible values:
allany -
conditions object[] Required
-
Responses
curl -X POST "https://api.worbo.app/v1/segments" \
-H "Authorization: Bearer $WORBO_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"name": "Toți membrii",
"definition": {
"match": "all",
"conditions": [
{
"type": "field",
"field": "first_name",
"operator": "not_empty"
}
]
}
}'
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', 'https://api.worbo.app/v1/segments', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
'Idempotency-Key' => bin2hex(random_bytes(16)),
],
'json' => [
'name' => 'Toți membrii',
'definition' => [
'match' => 'all',
'conditions' => [
[
'type' => 'field',
'field' => 'first_name',
'operator' => 'not_empty',
],
],
],
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/segments', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
'Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "Toți membrii",
"definition": {
"match": "all",
"conditions": [
{
"type": "field",
"field": "first_name",
"operator": "not_empty",
},
],
},
}),
});
const data = await response.json();
import os
import uuid
import requests
response = requests.post(
"https://api.worbo.app/v1/segments",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"name": "Toți membrii",
"definition": {
"match": "all",
"conditions": [
{
"type": "field",
"field": "first_name",
"operator": "not_empty",
},
],
},
},
)
data = response.json()
{
"data": {
"id": 12,
"name": "Toți membrii",
"description": "string",
"definition": {
"match": "all",
"conditions": [
{
"type": "field",
"field": "first_name",
"operator": "not_empty"
}
]
},
"last_count": 240,
"counted_at": "2026-10-15T09:00:00+03:00",
"created_at": "2026-10-15T09:00:00+03:00"
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/idempotency_conflict",
"title": "The Idempotency-Key was used for another request",
"status": 409,
"detail": "The Idempotency-Key was used for another request.",
"code": "idempotency_conflict"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Count the contacts of a definition, without saving it#
Query parameters
-
dry_run boolean
Runs every check and returns what would happen, without saving anything.
Headers
-
Idempotency-Key string
A unique key of the request, such as a UUID: a repeated request with the same key gets the first response, without doing the work again.
Max length:255 -
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Request bodyapplication/json
-
definition object Required
Show the fieldsHide the fields
-
match string RequiredPossible values:
allany -
conditions object[] Required
-
Responses
curl -X POST "https://api.worbo.app/v1/segments/count" \
-H "Authorization: Bearer $WORBO_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"definition": {
"match": "all",
"conditions": [
{
"type": "field",
"field": "first_name",
"operator": "not_empty"
}
]
}
}'
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', 'https://api.worbo.app/v1/segments/count', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
'Idempotency-Key' => bin2hex(random_bytes(16)),
],
'json' => [
'definition' => [
'match' => 'all',
'conditions' => [
[
'type' => 'field',
'field' => 'first_name',
'operator' => 'not_empty',
],
],
],
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/segments/count', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
'Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify({
"definition": {
"match": "all",
"conditions": [
{
"type": "field",
"field": "first_name",
"operator": "not_empty",
},
],
},
}),
});
const data = await response.json();
import os
import uuid
import requests
response = requests.post(
"https://api.worbo.app/v1/segments/count",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"definition": {
"match": "all",
"conditions": [
{
"type": "field",
"field": "first_name",
"operator": "not_empty",
},
],
},
},
)
data = response.json()
{
"data": {
"count": 240
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/idempotency_conflict",
"title": "The Idempotency-Key was used for another request",
"status": 409,
"detail": "The Idempotency-Key was used for another request.",
"code": "idempotency_conflict"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Read a segment, with its live count#
Path parameters
-
segment integer Required
The ID of the segment.
Example:12
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl "https://api.worbo.app/v1/segments/12" \
-H "Authorization: Bearer $WORBO_API_KEY"
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.worbo.app/v1/segments/12', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/segments/12', {
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
},
});
const data = await response.json();
import os
import requests
response = requests.get(
"https://api.worbo.app/v1/segments/12",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
)
data = response.json()
{
"data": {
"id": 12,
"name": "Toți membrii",
"description": "string",
"definition": {
"match": "all",
"conditions": [
{
"type": "field",
"field": "first_name",
"operator": "not_empty"
}
]
},
"last_count": 240,
"counted_at": "2026-10-15T09:00:00+03:00",
"created_at": "2026-10-15T09:00:00+03:00",
"count": 240
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/not_found",
"title": "Not found",
"status": 404,
"detail": "The resource does not exist or belongs to another organization.",
"code": "not_found"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Reference
Automations#
Reading automations, starting one for a contact and taking a contact out of a run.
List the automations#
Query parameters
-
limit integer
How many items a page has, between 1 and 100.
Default:25Minimum:1Maximum:100 -
cursor string
The next_cursor of the previous page.
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl "https://api.worbo.app/v1/automations" \
-H "Authorization: Bearer $WORBO_API_KEY"
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.worbo.app/v1/automations', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/automations', {
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
},
});
const data = await response.json();
import os
import requests
response = requests.get(
"https://api.worbo.app/v1/automations",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
)
data = response.json()
{
"data": [
{
"id": 8,
"name": "Recâștigare",
"status": "active",
"trigger": "manual",
"number_id": 3,
"recipe": "string",
"version_id": 14,
"holdout_percent": 0,
"created_at": "2026-10-15T09:00:00+03:00",
"updated_at": "2026-10-15T09:00:00+03:00"
}
],
"next_cursor": "string",
"has_more": true
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Read an automation, with its definition and its figures#
Path parameters
-
automation integer Required
The ID of the automation.
Example:8
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl "https://api.worbo.app/v1/automations/8" \
-H "Authorization: Bearer $WORBO_API_KEY"
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.worbo.app/v1/automations/8', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/automations/8', {
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
},
});
const data = await response.json();
import os
import requests
response = requests.get(
"https://api.worbo.app/v1/automations/8",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
)
data = response.json()
{
"data": {
"id": 8,
"name": "Recâștigare",
"status": "active",
"trigger": "manual",
"number_id": 3,
"recipe": "string",
"version_id": 14,
"holdout_percent": 0,
"created_at": "2026-10-15T09:00:00+03:00",
"updated_at": "2026-10-15T09:00:00+03:00",
"definition": {},
"report": {}
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/not_found",
"title": "Not found",
"status": 404,
"detail": "The resource does not exist or belongs to another organization.",
"code": "not_found"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Start an automation for a contact#
Only an active automation started by hand. The run takes the newest object of the contact of the type chosen in the trigger; the same Idempotency-Key starts it only once.
Path parameters
-
automation integer Required
The ID of the automation.
Example:8
Query parameters
-
dry_run boolean
Runs every check and returns what would happen, without saving anything.
Headers
-
Idempotency-Key string
A unique key of the request, such as a UUID: a repeated request with the same key gets the first response, without doing the work again.
Max length:255 -
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Request bodyapplication/json
-
contact_id integer RequiredExample:
123
Responses
curl -X POST "https://api.worbo.app/v1/automations/8/runs" \
-H "Authorization: Bearer $WORBO_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"contact_id": 123
}'
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', 'https://api.worbo.app/v1/automations/8/runs', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
'Idempotency-Key' => bin2hex(random_bytes(16)),
],
'json' => [
'contact_id' => 123,
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/automations/8/runs', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
'Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify({
"contact_id": 123,
}),
});
const data = await response.json();
import os
import uuid
import requests
response = requests.post(
"https://api.worbo.app/v1/automations/8/runs",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"contact_id": 123,
},
)
data = response.json()
{
"data": {
"id": 3301,
"automation_id": 8,
"contact_id": 123,
"object_id": 1,
"status": "active",
"exit_reason": "string",
"held_out": false,
"started_at": "2026-10-15T09:00:00+03:00",
"finished_at": "2026-10-15T09:00:00+03:00",
"converted_at": "2026-10-15T09:00:00+03:00"
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/not_found",
"title": "Not found",
"status": 404,
"detail": "The resource does not exist or belongs to another organization.",
"code": "not_found"
}
{
"type": "https://worbo.app/docs/api/errors/idempotency_conflict",
"title": "The Idempotency-Key was used for another request",
"status": 409,
"detail": "The Idempotency-Key was used for another request.",
"code": "idempotency_conflict"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Take a contact out of a run#
Path parameters
-
automation integer Required
The ID of the automation.
Example:8 -
run integer Required
The ID of the run.
Example:3301
Query parameters
-
dry_run boolean
Runs every check and returns what would happen, without saving anything.
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl -X DELETE "https://api.worbo.app/v1/automations/8/runs/3301" \
-H "Authorization: Bearer $WORBO_API_KEY"
$client = new \GuzzleHttp\Client();
$response = $client->request('DELETE', 'https://api.worbo.app/v1/automations/8/runs/3301', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/automations/8/runs/3301', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
},
});
const data = await response.json();
import os
import requests
response = requests.delete(
"https://api.worbo.app/v1/automations/8/runs/3301",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
)
data = response.json()
{
"data": {
"id": 3301,
"automation_id": 8,
"contact_id": 123,
"object_id": 1,
"status": "active",
"exit_reason": "string",
"held_out": false,
"started_at": "2026-10-15T09:00:00+03:00",
"finished_at": "2026-10-15T09:00:00+03:00",
"converted_at": "2026-10-15T09:00:00+03:00"
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/not_found",
"title": "Not found",
"status": 404,
"detail": "The resource does not exist or belongs to another organization.",
"code": "not_found"
}
{
"type": "https://worbo.app/docs/api/errors/idempotency_conflict",
"title": "The Idempotency-Key was used for another request",
"status": 409,
"detail": "The Idempotency-Key was used for another request.",
"code": "idempotency_conflict"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Reference
Campaigns#
Campaign drafts and their estimate; the launch is approved in the dashboard.
List the campaigns#
Query parameters
-
limit integer
How many items a page has, between 1 and 100.
Default:25Minimum:1Maximum:100 -
cursor string
The next_cursor of the previous page.
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl "https://api.worbo.app/v1/campaigns" \
-H "Authorization: Bearer $WORBO_API_KEY"
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.worbo.app/v1/campaigns', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/campaigns', {
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
},
});
const data = await response.json();
import os
import requests
response = requests.get(
"https://api.worbo.app/v1/campaigns",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
)
data = response.json()
{
"data": [
{
"id": 21,
"name": "Oferta de toamnă",
"status": "draft",
"kind": "template",
"text": "string",
"object_id": 1,
"object_roles": [
"string"
],
"purpose": "marketing",
"number_id": 3,
"template": "autumn_offer",
"variables": [
"string"
],
"header_text": "string",
"topic": "string",
"segment_id": 12,
"location_id": 1,
"scheduled_at": "2026-10-15T09:00:00+03:00",
"expires_at": "2026-10-15T09:00:00+03:00",
"wave_by": "size",
"wave_size": 1,
"wave_hours": 24,
"wave_segments": [
1
],
"budget": "250.00",
"total": 1,
"processed": 1,
"created_at": "2026-10-15T09:00:00+03:00"
}
],
"next_cursor": "string",
"has_more": true
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Create a campaign draft#
The moments come in ISO 8601, with their time zone. The launch is approved in the dashboard.
Query parameters
-
dry_run boolean
Runs every check and returns what would happen, without saving anything.
Headers
-
Idempotency-Key string
A unique key of the request, such as a UUID: a repeated request with the same key gets the first response, without doing the work again.
Max length:255 -
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Request bodyapplication/json
-
name string RequiredExample:
Oferta de toamnă -
kind string
free: a free message without a template, only to the contacts with the 24-hour window open on the number. object: a utility template to the people of one object, instead of a segment.
Default:templatePossible values:templatefreeobject -
object_id integer null
With kind object: the object whose people get the notice.
Example:456 -
object_roles string[] null
With kind object: the roles of the people who get it; none means all.
-
text string null
The free message, the same for everyone; only with kind free.
Max length:4096 -
purpose string null
What the free message is: marketing, with its consent and limit, or information.
Possible values:marketingtransactional -
number_id integer nullExample:
3 -
template string nullExample:
autumn_offer -
variables string[]
-
header_text string null
-
topic string null
-
segment_id integer nullExample:
12 -
location_id integer null
-
scheduled_at string nullFormat:
date-timeExample:2026-10-20T10:00:00+03:00 -
expires_at string nullFormat:
date-time -
wave_by stringPossible values:
sizelocationsegment -
wave_size integer null
-
wave_hours integerExample:
24 -
wave_segments integer[] null
-
budget number null
Responses
curl -X POST "https://api.worbo.app/v1/campaigns" \
-H "Authorization: Bearer $WORBO_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"name": "Oferta de toamnă",
"object_id": 456,
"object_roles": [
"holder"
],
"number_id": 3,
"template": "autumn_offer",
"segment_id": 12,
"scheduled_at": "2026-10-20T10:00:00+03:00",
"wave_by": "size",
"wave_hours": 24
}'
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', 'https://api.worbo.app/v1/campaigns', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
'Idempotency-Key' => bin2hex(random_bytes(16)),
],
'json' => [
'name' => 'Oferta de toamnă',
'object_id' => 456,
'object_roles' => [
'holder',
],
'number_id' => 3,
'template' => 'autumn_offer',
'segment_id' => 12,
'scheduled_at' => '2026-10-20T10:00:00+03:00',
'wave_by' => 'size',
'wave_hours' => 24,
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/campaigns', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
'Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "Oferta de toamnă",
"object_id": 456,
"object_roles": [
"holder",
],
"number_id": 3,
"template": "autumn_offer",
"segment_id": 12,
"scheduled_at": "2026-10-20T10:00:00+03:00",
"wave_by": "size",
"wave_hours": 24,
}),
});
const data = await response.json();
import os
import uuid
import requests
response = requests.post(
"https://api.worbo.app/v1/campaigns",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"name": "Oferta de toamnă",
"object_id": 456,
"object_roles": [
"holder",
],
"number_id": 3,
"template": "autumn_offer",
"segment_id": 12,
"scheduled_at": "2026-10-20T10:00:00+03:00",
"wave_by": "size",
"wave_hours": 24,
},
)
data = response.json()
{
"data": {
"id": 21,
"name": "Oferta de toamnă",
"status": "draft",
"kind": "template",
"text": "string",
"object_id": 1,
"object_roles": [
"string"
],
"purpose": "marketing",
"number_id": 3,
"template": "autumn_offer",
"variables": [
"string"
],
"header_text": "string",
"topic": "string",
"segment_id": 12,
"location_id": 1,
"scheduled_at": "2026-10-15T09:00:00+03:00",
"expires_at": "2026-10-15T09:00:00+03:00",
"wave_by": "size",
"wave_size": 1,
"wave_hours": 24,
"wave_segments": [
1
],
"budget": "250.00",
"total": 1,
"processed": 1,
"created_at": "2026-10-15T09:00:00+03:00"
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/idempotency_conflict",
"title": "The Idempotency-Key was used for another request",
"status": 409,
"detail": "The Idempotency-Key was used for another request.",
"code": "idempotency_conflict"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Read a campaign, with the report of its messages and the cost#
Path parameters
-
campaign integer Required
The ID of the campaign.
Example:21
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl "https://api.worbo.app/v1/campaigns/21" \
-H "Authorization: Bearer $WORBO_API_KEY"
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.worbo.app/v1/campaigns/21', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/campaigns/21', {
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
},
});
const data = await response.json();
import os
import requests
response = requests.get(
"https://api.worbo.app/v1/campaigns/21",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
)
data = response.json()
{
"data": {
"id": 21,
"name": "Oferta de toamnă",
"status": "draft",
"kind": "template",
"text": "string",
"object_id": 1,
"object_roles": [
"string"
],
"purpose": "marketing",
"number_id": 3,
"template": "autumn_offer",
"variables": [
"string"
],
"header_text": "string",
"topic": "string",
"segment_id": 12,
"location_id": 1,
"scheduled_at": "2026-10-15T09:00:00+03:00",
"expires_at": "2026-10-15T09:00:00+03:00",
"wave_by": "size",
"wave_size": 1,
"wave_hours": 24,
"wave_segments": [
1
],
"budget": "250.00",
"total": 1,
"processed": 1,
"created_at": "2026-10-15T09:00:00+03:00",
"report": {}
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/not_found",
"title": "Not found",
"status": 404,
"detail": "The resource does not exist or belongs to another organization.",
"code": "not_found"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Estimate the audience and the cost of a campaign#
With the same exclusions as at launch: no WhatsApp, no consent, undeliverable, the frequency cap.
Path parameters
-
campaign integer Required
The ID of the campaign.
Example:21
Query parameters
-
dry_run boolean
Runs every check and returns what would happen, without saving anything.
Headers
-
Idempotency-Key string
A unique key of the request, such as a UUID: a repeated request with the same key gets the first response, without doing the work again.
Max length:255 -
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl -X POST "https://api.worbo.app/v1/campaigns/21/estimate" \
-H "Authorization: Bearer $WORBO_API_KEY" \
-H "Idempotency-Key: $(uuidgen)"
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', 'https://api.worbo.app/v1/campaigns/21/estimate', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
'Idempotency-Key' => bin2hex(random_bytes(16)),
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/campaigns/21/estimate', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
'Idempotency-Key': crypto.randomUUID(),
},
});
const data = await response.json();
import os
import uuid
import requests
response = requests.post(
"https://api.worbo.app/v1/campaigns/21/estimate",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
)
data = response.json()
{
"data": {
"audience": 240,
"contactable": 198,
"excluded": {
"whatsapp": 12,
"consent": 28,
"optin": 10,
"marketing": 18,
"window": 0,
"undeliverable": 2
},
"capped": 0,
"other_countries": 0,
"number": {
"quality": "GREEN",
"marketing_paused_until": "2026-10-15T09:00:00+03:00"
},
"united_states": 0,
"template": {},
"cost": {
"amount": 10.79,
"free": 0,
"rate": 0.0545,
"currency": "EUR",
"country": "RO",
"budget_messages": 1
},
"waves": {
"count": 1,
"per_day": 198,
"last_at": "2026-10-15T09:00:00+03:00",
"sizes": [
{}
]
},
"limit": {
"daily": 1000,
"days": 1,
"over": false
},
"start": "2026-10-15T09:00:00+03:00",
"quiet_until": "2026-10-15T09:00:00+03:00",
"ends_before_last_wave": false
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/not_found",
"title": "Not found",
"status": 404,
"detail": "The resource does not exist or belongs to another organization.",
"code": "not_found"
}
{
"type": "https://worbo.app/docs/api/errors/idempotency_conflict",
"title": "The Idempotency-Key was used for another request",
"status": 409,
"detail": "The Idempotency-Key was used for another request.",
"code": "idempotency_conflict"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Reference
Conversations#
The inbox: conversations, their messages, replies, assignment and closing.
List the conversations#
Query parameters
-
status string
Only the conversations with this status.
Possible values:openpendingresolved -
contact_id integer
Only the conversations of this contact.
Example:123 -
assigned_user_id integer
Only the conversations assigned to this member.
Example:5 -
limit integer
How many items a page has, between 1 and 100.
Default:25Minimum:1Maximum:100 -
cursor string
The next_cursor of the previous page.
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl "https://api.worbo.app/v1/conversations" \
-H "Authorization: Bearer $WORBO_API_KEY"
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.worbo.app/v1/conversations', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/conversations', {
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
},
});
const data = await response.json();
import os
import requests
response = requests.get(
"https://api.worbo.app/v1/conversations",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
)
data = response.json()
{
"data": [
{
"id": 640,
"contact_id": 123,
"number_id": 3,
"status": "open",
"assigned_user_id": 1,
"assigned_team_id": 1,
"unread": 1,
"last_message_at": "2026-10-15T09:00:00+03:00",
"last_message_direction": "inbound",
"last_message_preview": "string",
"untrusted_last_message_preview": "string",
"window_closes_at": "2026-10-15T09:00:00+03:00"
}
],
"next_cursor": "string",
"has_more": true
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
List the messages of a conversation#
Path parameters
-
conversation integer Required
The ID of the conversation.
Example:640
Query parameters
-
limit integer
How many items a page has, between 1 and 100.
Default:25Minimum:1Maximum:100 -
cursor string
The next_cursor of the previous page.
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl "https://api.worbo.app/v1/conversations/640/messages" \
-H "Authorization: Bearer $WORBO_API_KEY"
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.worbo.app/v1/conversations/640/messages', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/conversations/640/messages', {
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
},
});
const data = await response.json();
import os
import requests
response = requests.get(
"https://api.worbo.app/v1/conversations/640/messages",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
)
data = response.json()
{
"data": [
{
"id": 7001,
"contact_id": 123,
"number_id": 3,
"direction": "outbound",
"type": "template",
"text": "string",
"untrusted_text": "string",
"template": {
"name": "booking_confirmed",
"language": "ro"
},
"category": "utility",
"status": "pending",
"reason": "string",
"error_code": "string",
"wamid": "string",
"created_at": "2026-10-15T09:00:00+03:00",
"sent_at": "2026-10-15T09:00:00+03:00",
"delivered_at": "2026-10-15T09:00:00+03:00",
"read_at": "2026-10-15T09:00:00+03:00",
"failed_at": "2026-10-15T09:00:00+03:00"
}
],
"next_cursor": "string",
"has_more": true
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/not_found",
"title": "Not found",
"status": 404,
"detail": "The resource does not exist or belongs to another organization.",
"code": "not_found"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Reply with a free text, in the 24-hour window#
Path parameters
-
conversation integer Required
The ID of the conversation.
Example:640
Query parameters
-
dry_run boolean
Runs every check and returns what would happen, without saving anything.
Headers
-
Idempotency-Key string
A unique key of the request, such as a UUID: a repeated request with the same key gets the first response, without doing the work again.
Max length:255 -
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Request bodyapplication/json
-
text string RequiredExample:
Bună! Cu ce vă ajutăm?
Responses
curl -X POST "https://api.worbo.app/v1/conversations/640/messages" \
-H "Authorization: Bearer $WORBO_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"text": "Bună! Cu ce vă ajutăm?"
}'
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', 'https://api.worbo.app/v1/conversations/640/messages', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
'Idempotency-Key' => bin2hex(random_bytes(16)),
],
'json' => [
'text' => 'Bună! Cu ce vă ajutăm?',
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/conversations/640/messages', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
'Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify({
"text": "Bună! Cu ce vă ajutăm?",
}),
});
const data = await response.json();
import os
import uuid
import requests
response = requests.post(
"https://api.worbo.app/v1/conversations/640/messages",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"text": "Bună! Cu ce vă ajutăm?",
},
)
data = response.json()
{
"data": {
"id": 7001,
"contact_id": 123,
"number_id": 3,
"direction": "outbound",
"type": "template",
"text": "string",
"untrusted_text": "string",
"template": {
"name": "booking_confirmed",
"language": "ro"
},
"category": "utility",
"status": "pending",
"reason": "string",
"error_code": "string",
"wamid": "string",
"created_at": "2026-10-15T09:00:00+03:00",
"sent_at": "2026-10-15T09:00:00+03:00",
"delivered_at": "2026-10-15T09:00:00+03:00",
"read_at": "2026-10-15T09:00:00+03:00",
"failed_at": "2026-10-15T09:00:00+03:00"
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/not_found",
"title": "Not found",
"status": 404,
"detail": "The resource does not exist or belongs to another organization.",
"code": "not_found"
}
{
"type": "https://worbo.app/docs/api/errors/idempotency_conflict",
"title": "The Idempotency-Key was used for another request",
"status": 409,
"detail": "The Idempotency-Key was used for another request.",
"code": "idempotency_conflict"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Assign a conversation to a member#
Path parameters
-
conversation integer Required
The ID of the conversation.
Example:640
Query parameters
-
dry_run boolean
Runs every check and returns what would happen, without saving anything.
Headers
-
Idempotency-Key string
A unique key of the request, such as a UUID: a repeated request with the same key gets the first response, without doing the work again.
Max length:255 -
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Request bodyapplication/json
-
user_id integer Required null
The member who takes the conversation; null leaves it unassigned.
Example:5
Responses
curl -X POST "https://api.worbo.app/v1/conversations/640/assign" \
-H "Authorization: Bearer $WORBO_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"user_id": 5
}'
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', 'https://api.worbo.app/v1/conversations/640/assign', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
'Idempotency-Key' => bin2hex(random_bytes(16)),
],
'json' => [
'user_id' => 5,
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/conversations/640/assign', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
'Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify({
"user_id": 5,
}),
});
const data = await response.json();
import os
import uuid
import requests
response = requests.post(
"https://api.worbo.app/v1/conversations/640/assign",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"user_id": 5,
},
)
data = response.json()
{
"data": {
"id": 640,
"contact_id": 123,
"number_id": 3,
"status": "open",
"assigned_user_id": 1,
"assigned_team_id": 1,
"unread": 1,
"last_message_at": "2026-10-15T09:00:00+03:00",
"last_message_direction": "inbound",
"last_message_preview": "string",
"untrusted_last_message_preview": "string",
"window_closes_at": "2026-10-15T09:00:00+03:00"
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/not_found",
"title": "Not found",
"status": 404,
"detail": "The resource does not exist or belongs to another organization.",
"code": "not_found"
}
{
"type": "https://worbo.app/docs/api/errors/idempotency_conflict",
"title": "The Idempotency-Key was used for another request",
"status": 409,
"detail": "The Idempotency-Key was used for another request.",
"code": "idempotency_conflict"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Close a conversation#
Path parameters
-
conversation integer Required
The ID of the conversation.
Example:640
Query parameters
-
dry_run boolean
Runs every check and returns what would happen, without saving anything.
Headers
-
Idempotency-Key string
A unique key of the request, such as a UUID: a repeated request with the same key gets the first response, without doing the work again.
Max length:255 -
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl -X POST "https://api.worbo.app/v1/conversations/640/close" \
-H "Authorization: Bearer $WORBO_API_KEY" \
-H "Idempotency-Key: $(uuidgen)"
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', 'https://api.worbo.app/v1/conversations/640/close', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
'Idempotency-Key' => bin2hex(random_bytes(16)),
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/conversations/640/close', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
'Idempotency-Key': crypto.randomUUID(),
},
});
const data = await response.json();
import os
import uuid
import requests
response = requests.post(
"https://api.worbo.app/v1/conversations/640/close",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
)
data = response.json()
{
"data": {
"id": 640,
"contact_id": 123,
"number_id": 3,
"status": "open",
"assigned_user_id": 1,
"assigned_team_id": 1,
"unread": 1,
"last_message_at": "2026-10-15T09:00:00+03:00",
"last_message_direction": "inbound",
"last_message_preview": "string",
"untrusted_last_message_preview": "string",
"window_closes_at": "2026-10-15T09:00:00+03:00"
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/not_found",
"title": "Not found",
"status": 404,
"detail": "The resource does not exist or belongs to another organization.",
"code": "not_found"
}
{
"type": "https://worbo.app/docs/api/errors/idempotency_conflict",
"title": "The Idempotency-Key was used for another request",
"status": 409,
"detail": "The Idempotency-Key was used for another request.",
"code": "idempotency_conflict"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Reference
Catalogs#
Products, services, classes, plans and locations, written one by one or in batches.
List the catalogs#
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl "https://api.worbo.app/v1/catalogs" \
-H "Authorization: Bearer $WORBO_API_KEY"
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.worbo.app/v1/catalogs', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/catalogs', {
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
},
});
const data = await response.json();
import os
import requests
response = requests.get(
"https://api.worbo.app/v1/catalogs",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
)
data = response.json()
{
"data": [
{
"key": "fit.classes",
"name": "Classes",
"kind": "class",
"currency": "RON",
"source": "fitness"
}
]
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
List the items of a catalog#
Path parameters
-
catalog string Required
The key of the catalog, such as fit.classes.
Example:fit.classes
Query parameters
-
limit integer
How many items a page has, between 1 and 100.
Default:25Minimum:1Maximum:100 -
cursor string
The next_cursor of the previous page.
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl "https://api.worbo.app/v1/catalogs/fit.classes/items" \
-H "Authorization: Bearer $WORBO_API_KEY"
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.worbo.app/v1/catalogs/fit.classes/items', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/catalogs/fit.classes/items', {
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
},
});
const data = await response.json();
import os
import requests
response = requests.get(
"https://api.worbo.app/v1/catalogs/fit.classes/items",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
)
data = response.json()
{
"data": [
{
"id": 310,
"external_id": "yoga",
"sku": "string",
"name": "Yoga",
"price": "50.00",
"currency": "RON",
"stock": 1,
"available": true,
"recommendable": true,
"url": "string",
"image_url": "string",
"fields": {},
"updated_at": "2026-10-15T09:00:00+03:00"
}
],
"next_cursor": "string",
"has_more": true
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/not_found",
"title": "Not found",
"status": 404,
"detail": "The resource does not exist or belongs to another organization.",
"code": "not_found"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Create or update an item by its ID at the source#
Path parameters
-
catalog string Required
The key of the catalog, such as fit.classes.
Example:fit.classes
Query parameters
-
dry_run boolean
Runs every check and returns what would happen, without saving anything.
Headers
-
Idempotency-Key string
A unique key of the request, such as a UUID: a repeated request with the same key gets the first response, without doing the work again.
Max length:255 -
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Request bodyapplication/json
-
id integer
-
external_id stringExample:
yoga -
name stringExample:
Yoga -
sku string
-
price numberExample:
50 -
currency stringExample:
RON -
stock integer
-
available booleanExample:
true -
recommendable booleanExample:
true -
url string
-
image_url string
-
custom object
Responses
curl -X POST "https://api.worbo.app/v1/catalogs/fit.classes/items" \
-H "Authorization: Bearer $WORBO_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"external_id": "yoga",
"name": "Yoga",
"price": 50,
"currency": "RON",
"available": true,
"recommendable": true
}'
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', 'https://api.worbo.app/v1/catalogs/fit.classes/items', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
'Idempotency-Key' => bin2hex(random_bytes(16)),
],
'json' => [
'external_id' => 'yoga',
'name' => 'Yoga',
'price' => 50,
'currency' => 'RON',
'available' => true,
'recommendable' => true,
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/catalogs/fit.classes/items', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
'Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify({
"external_id": "yoga",
"name": "Yoga",
"price": 50,
"currency": "RON",
"available": true,
"recommendable": true,
}),
});
const data = await response.json();
import os
import uuid
import requests
response = requests.post(
"https://api.worbo.app/v1/catalogs/fit.classes/items",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"external_id": "yoga",
"name": "Yoga",
"price": 50,
"currency": "RON",
"available": True,
"recommendable": True,
},
)
data = response.json()
{
"data": {
"id": 310,
"external_id": "yoga",
"sku": "string",
"name": "Yoga",
"price": "50.00",
"currency": "RON",
"stock": 1,
"available": true,
"recommendable": true,
"url": "string",
"image_url": "string",
"fields": {},
"updated_at": "2026-10-15T09:00:00+03:00"
}
}
{
"data": {
"id": 310,
"external_id": "yoga",
"sku": "string",
"name": "Yoga",
"price": "50.00",
"currency": "RON",
"stock": 1,
"available": true,
"recommendable": true,
"url": "string",
"image_url": "string",
"fields": {},
"updated_at": "2026-10-15T09:00:00+03:00"
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/not_found",
"title": "Not found",
"status": 404,
"detail": "The resource does not exist or belongs to another organization.",
"code": "not_found"
}
{
"type": "https://worbo.app/docs/api/errors/idempotency_conflict",
"title": "The Idempotency-Key was used for another request",
"status": 409,
"detail": "The Idempotency-Key was used for another request.",
"code": "idempotency_conflict"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Create or update up to 100 items#
Path parameters
-
catalog string Required
The key of the catalog, such as fit.classes.
Example:fit.classes
Query parameters
-
dry_run boolean
Runs every check and returns what would happen, without saving anything.
Headers
-
Idempotency-Key string
A unique key of the request, such as a UUID: a repeated request with the same key gets the first response, without doing the work again.
Max length:255 -
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Request bodyapplication/json
-
items object[] Required
Show the fieldsHide the fields
-
id integer
-
external_id stringExample:
yoga -
name stringExample:
Yoga -
sku string
-
price numberExample:
50 -
currency stringExample:
RON -
stock integer
-
available booleanExample:
true -
recommendable booleanExample:
true -
url string
-
image_url string
-
custom object
-
Responses
curl -X POST "https://api.worbo.app/v1/catalogs/fit.classes/items/batch" \
-H "Authorization: Bearer $WORBO_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"external_id": "yoga",
"name": "Yoga",
"price": 50,
"currency": "RON",
"available": true,
"recommendable": true
}
]
}'
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', 'https://api.worbo.app/v1/catalogs/fit.classes/items/batch', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
'Idempotency-Key' => bin2hex(random_bytes(16)),
],
'json' => [
'items' => [
[
'external_id' => 'yoga',
'name' => 'Yoga',
'price' => 50,
'currency' => 'RON',
'available' => true,
'recommendable' => true,
],
],
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/catalogs/fit.classes/items/batch', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
'Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify({
"items": [
{
"external_id": "yoga",
"name": "Yoga",
"price": 50,
"currency": "RON",
"available": true,
"recommendable": true,
},
],
}),
});
const data = await response.json();
import os
import uuid
import requests
response = requests.post(
"https://api.worbo.app/v1/catalogs/fit.classes/items/batch",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"items": [
{
"external_id": "yoga",
"name": "Yoga",
"price": 50,
"currency": "RON",
"available": True,
"recommendable": True,
},
],
},
)
data = response.json()
{
"data": [
{
"index": 0,
"status": "created",
"data": {
"id": 310,
"external_id": "yoga",
"sku": "string",
"name": "Yoga",
"price": "50.00",
"currency": "RON",
"stock": 1,
"available": true,
"recommendable": true,
"url": "string",
"image_url": "string",
"fields": {},
"updated_at": "2026-10-15T09:00:00+03:00"
},
"error": {
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "The name field is required.",
"code": "validation_failed",
"errors": {
"name": [
"The name field is required."
]
}
}
}
]
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/not_found",
"title": "Not found",
"status": 404,
"detail": "The resource does not exist or belongs to another organization.",
"code": "not_found"
}
{
"type": "https://worbo.app/docs/api/errors/idempotency_conflict",
"title": "The Idempotency-Key was used for another request",
"status": 409,
"detail": "The Idempotency-Key was used for another request.",
"code": "idempotency_conflict"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Reference
Webhooks#
The endpoints that receive the events of Worbo, their deliveries and replays.
List the webhook endpoints#
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl "https://api.worbo.app/v1/webhook-endpoints" \
-H "Authorization: Bearer $WORBO_API_KEY"
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.worbo.app/v1/webhook-endpoints', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/webhook-endpoints', {
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
},
});
const data = await response.json();
import os
import requests
response = requests.get(
"https://api.worbo.app/v1/webhook-endpoints",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
)
data = response.json()
{
"data": [
{
"id": 4,
"url": "https://crm.example.com/worbo",
"description": "string",
"events": [
"contact.created",
"message.received"
],
"payload": "snapshot",
"enabled": true,
"disabled_reason": "string",
"failing_since": "2026-10-15T09:00:00+03:00",
"last_success_at": "2026-10-15T09:00:00+03:00",
"created_at": "2026-10-15T09:00:00+03:00"
}
]
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Add a webhook endpoint#
The signing secret is in the response, only now.
Query parameters
-
dry_run boolean
Runs every check and returns what would happen, without saving anything.
Headers
-
Idempotency-Key string
A unique key of the request, such as a UUID: a repeated request with the same key gets the first response, without doing the work again.
Max length:255 -
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Request bodyapplication/json
-
url string RequiredFormat:
uriExample:https://crm.example.com/worbo -
description string
-
events string[] Required
-
payload stringPossible values:
snapshotthin
Responses
curl -X POST "https://api.worbo.app/v1/webhook-endpoints" \
-H "Authorization: Bearer $WORBO_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"url": "https://crm.example.com/worbo",
"events": [
"contact.created",
"message.received"
],
"payload": "snapshot"
}'
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', 'https://api.worbo.app/v1/webhook-endpoints', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
'Idempotency-Key' => bin2hex(random_bytes(16)),
],
'json' => [
'url' => 'https://crm.example.com/worbo',
'events' => [
'contact.created',
'message.received',
],
'payload' => 'snapshot',
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/webhook-endpoints', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
'Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify({
"url": "https://crm.example.com/worbo",
"events": [
"contact.created",
"message.received",
],
"payload": "snapshot",
}),
});
const data = await response.json();
import os
import uuid
import requests
response = requests.post(
"https://api.worbo.app/v1/webhook-endpoints",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"url": "https://crm.example.com/worbo",
"events": [
"contact.created",
"message.received",
],
"payload": "snapshot",
},
)
data = response.json()
{
"data": {
"id": 4,
"url": "https://crm.example.com/worbo",
"description": "string",
"events": [
"contact.created",
"message.received"
],
"payload": "snapshot",
"enabled": true,
"disabled_reason": "string",
"failing_since": "2026-10-15T09:00:00+03:00",
"last_success_at": "2026-10-15T09:00:00+03:00",
"created_at": "2026-10-15T09:00:00+03:00",
"secret": "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw"
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/idempotency_conflict",
"title": "The Idempotency-Key was used for another request",
"status": 409,
"detail": "The Idempotency-Key was used for another request.",
"code": "idempotency_conflict"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Change the address or the events of an endpoint#
Path parameters
-
endpoint integer Required
The ID of the webhook endpoint.
Example:4
Query parameters
-
dry_run boolean
Runs every check and returns what would happen, without saving anything.
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Request bodyapplication/json
-
url string RequiredFormat:
uriExample:https://crm.example.com/worbo -
description string
-
events string[] Required
-
payload stringPossible values:
snapshotthin
Responses
curl -X PATCH "https://api.worbo.app/v1/webhook-endpoints/4" \
-H "Authorization: Bearer $WORBO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://crm.example.com/worbo",
"events": [
"contact.created",
"message.received"
],
"payload": "snapshot"
}'
$client = new \GuzzleHttp\Client();
$response = $client->request('PATCH', 'https://api.worbo.app/v1/webhook-endpoints/4', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
'json' => [
'url' => 'https://crm.example.com/worbo',
'events' => [
'contact.created',
'message.received',
],
'payload' => 'snapshot',
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/webhook-endpoints/4', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"url": "https://crm.example.com/worbo",
"events": [
"contact.created",
"message.received",
],
"payload": "snapshot",
}),
});
const data = await response.json();
import os
import requests
response = requests.patch(
"https://api.worbo.app/v1/webhook-endpoints/4",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
json={
"url": "https://crm.example.com/worbo",
"events": [
"contact.created",
"message.received",
],
"payload": "snapshot",
},
)
data = response.json()
{
"data": {
"id": 4,
"url": "https://crm.example.com/worbo",
"description": "string",
"events": [
"contact.created",
"message.received"
],
"payload": "snapshot",
"enabled": true,
"disabled_reason": "string",
"failing_since": "2026-10-15T09:00:00+03:00",
"last_success_at": "2026-10-15T09:00:00+03:00",
"created_at": "2026-10-15T09:00:00+03:00"
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/not_found",
"title": "Not found",
"status": 404,
"detail": "The resource does not exist or belongs to another organization.",
"code": "not_found"
}
{
"type": "https://worbo.app/docs/api/errors/idempotency_conflict",
"title": "The Idempotency-Key was used for another request",
"status": 409,
"detail": "The Idempotency-Key was used for another request.",
"code": "idempotency_conflict"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Delete a webhook endpoint#
Path parameters
-
endpoint integer Required
The ID of the webhook endpoint.
Example:4
Query parameters
-
dry_run boolean
Runs every check and returns what would happen, without saving anything.
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl -X DELETE "https://api.worbo.app/v1/webhook-endpoints/4" \
-H "Authorization: Bearer $WORBO_API_KEY"
$client = new \GuzzleHttp\Client();
$response = $client->request('DELETE', 'https://api.worbo.app/v1/webhook-endpoints/4', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/webhook-endpoints/4', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
},
});
const data = await response.json();
import os
import requests
response = requests.delete(
"https://api.worbo.app/v1/webhook-endpoints/4",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
)
data = response.json()
{
"data": {
"id": 4,
"deleted": true
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/not_found",
"title": "Not found",
"status": 404,
"detail": "The resource does not exist or belongs to another organization.",
"code": "not_found"
}
{
"type": "https://worbo.app/docs/api/errors/idempotency_conflict",
"title": "The Idempotency-Key was used for another request",
"status": 409,
"detail": "The Idempotency-Key was used for another request.",
"code": "idempotency_conflict"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Rotate the signing secret of an endpoint#
The previous secret stays valid for 24 hours; meanwhile, both signatures are sent.
Path parameters
-
endpoint integer Required
The ID of the webhook endpoint.
Example:4
Query parameters
-
dry_run boolean
Runs every check and returns what would happen, without saving anything.
Headers
-
Idempotency-Key string
A unique key of the request, such as a UUID: a repeated request with the same key gets the first response, without doing the work again.
Max length:255 -
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl -X POST "https://api.worbo.app/v1/webhook-endpoints/4/rotate" \
-H "Authorization: Bearer $WORBO_API_KEY" \
-H "Idempotency-Key: $(uuidgen)"
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', 'https://api.worbo.app/v1/webhook-endpoints/4/rotate', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
'Idempotency-Key' => bin2hex(random_bytes(16)),
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/webhook-endpoints/4/rotate', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
'Idempotency-Key': crypto.randomUUID(),
},
});
const data = await response.json();
import os
import uuid
import requests
response = requests.post(
"https://api.worbo.app/v1/webhook-endpoints/4/rotate",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
)
data = response.json()
{
"data": {
"id": 4,
"url": "https://crm.example.com/worbo",
"description": "string",
"events": [
"contact.created",
"message.received"
],
"payload": "snapshot",
"enabled": true,
"disabled_reason": "string",
"failing_since": "2026-10-15T09:00:00+03:00",
"last_success_at": "2026-10-15T09:00:00+03:00",
"created_at": "2026-10-15T09:00:00+03:00",
"secret": "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw"
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/not_found",
"title": "Not found",
"status": 404,
"detail": "The resource does not exist or belongs to another organization.",
"code": "not_found"
}
{
"type": "https://worbo.app/docs/api/errors/idempotency_conflict",
"title": "The Idempotency-Key was used for another request",
"status": 409,
"detail": "The Idempotency-Key was used for another request.",
"code": "idempotency_conflict"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
List the deliveries of the events#
Query parameters
-
endpoint_id integer
Only the deliveries to this endpoint.
Example:4 -
status string
Only the deliveries with this status.
Possible values:pendingdeliveredfailed -
limit integer
How many items a page has, between 1 and 100.
Default:25Minimum:1Maximum:100 -
cursor string
The next_cursor of the previous page.
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl "https://api.worbo.app/v1/webhook-deliveries" \
-H "Authorization: Bearer $WORBO_API_KEY"
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.worbo.app/v1/webhook-deliveries', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/webhook-deliveries', {
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
},
});
const data = await response.json();
import os
import requests
response = requests.get(
"https://api.worbo.app/v1/webhook-deliveries",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
)
data = response.json()
{
"data": [
{
"id": 9001,
"endpoint_id": 4,
"event": {
"id": "msg_2mF0",
"type": "contact.created"
},
"status": "delivered",
"attempts": 1,
"last_status": 200,
"last_error": "string",
"next_attempt_at": "2026-10-15T09:00:00+03:00",
"delivered_at": "2026-10-15T09:00:00+03:00",
"created_at": "2026-10-15T09:00:00+03:00"
}
],
"next_cursor": "string",
"has_more": true
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Send a delivery again#
Path parameters
-
delivery integer Required
The ID of the delivery.
Example:9001
Query parameters
-
dry_run boolean
Runs every check and returns what would happen, without saving anything.
Headers
-
Idempotency-Key string
A unique key of the request, such as a UUID: a repeated request with the same key gets the first response, without doing the work again.
Max length:255 -
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl -X POST "https://api.worbo.app/v1/webhook-deliveries/9001/replay" \
-H "Authorization: Bearer $WORBO_API_KEY" \
-H "Idempotency-Key: $(uuidgen)"
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', 'https://api.worbo.app/v1/webhook-deliveries/9001/replay', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
'Idempotency-Key' => bin2hex(random_bytes(16)),
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/webhook-deliveries/9001/replay', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
'Idempotency-Key': crypto.randomUUID(),
},
});
const data = await response.json();
import os
import uuid
import requests
response = requests.post(
"https://api.worbo.app/v1/webhook-deliveries/9001/replay",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
)
data = response.json()
{
"data": {
"id": 9001,
"endpoint_id": 4,
"event": {
"id": "msg_2mF0",
"type": "contact.created"
},
"status": "delivered",
"attempts": 1,
"last_status": 200,
"last_error": "string",
"next_attempt_at": "2026-10-15T09:00:00+03:00",
"delivered_at": "2026-10-15T09:00:00+03:00",
"created_at": "2026-10-15T09:00:00+03:00"
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/not_found",
"title": "Not found",
"status": 404,
"detail": "The resource does not exist or belongs to another organization.",
"code": "not_found"
}
{
"type": "https://worbo.app/docs/api/errors/idempotency_conflict",
"title": "The Idempotency-Key was used for another request",
"status": 409,
"detail": "The Idempotency-Key was used for another request.",
"code": "idempotency_conflict"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
Reference
Reports#
The results of a period and the conversions of the automations.
Read the results of a period#
The automations with the control group, the baseline, the value and the cost, in the days of the organization.
Query parameters
-
from string Required
The first day of the period.
Format:dateExample:2026-10-01 -
to string Required
The last day of the period.
Format:dateExample:2026-10-15 -
location_id integer
Only the contacts of this home location.
Example:7
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl "https://api.worbo.app/v1/reports/results" \
-H "Authorization: Bearer $WORBO_API_KEY"
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.worbo.app/v1/reports/results', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/reports/results', {
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
},
});
const data = await response.json();
import os
import requests
response = requests.get(
"https://api.worbo.app/v1/reports/results",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
)
data = response.json()
{
"data": {
"from": "2026-09-30T21:00:00+00:00",
"to": "2026-10-15T20:59:59+00:00",
"journeys": [
{
"id": 8,
"name": "Recâștigare",
"conversion": "string",
"entered": 40,
"converted": 6,
"control": {},
"value": 1194,
"sent": 40,
"clicked": 11,
"cost": 2.18
}
],
"conversions": [
{}
],
"baseline": {},
"cost": {
"amount": 2.18,
"currency": "EUR"
},
"value": 1194
}
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}
List the conversions of the automations#
Query parameters
-
automation_id integer
Only the conversions of this automation.
Example:8 -
since string
Only what changed at or after this moment.
Format:date-timeExample:2026-10-01T00:00:00+03:00 -
limit integer
How many items a page has, between 1 and 100.
Default:25Minimum:1Maximum:100 -
cursor string
The next_cursor of the previous page.
Headers
-
Worbo-Version string
The API version, as a date; without it, the version of the key.
Possible values:2026-10-05
Responses
curl "https://api.worbo.app/v1/conversions" \
-H "Authorization: Bearer $WORBO_API_KEY"
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.worbo.app/v1/conversions', [
'headers' => [
'Authorization' => 'Bearer '.getenv('WORBO_API_KEY'),
],
]);
$data = json_decode((string) $response->getBody(), true);
const response = await fetch('https://api.worbo.app/v1/conversions', {
headers: {
Authorization: `Bearer ${process.env.WORBO_API_KEY}`,
},
});
const data = await response.json();
import os
import requests
response = requests.get(
"https://api.worbo.app/v1/conversions",
headers={
"Authorization": f"Bearer {os.environ['WORBO_API_KEY']}",
},
)
data = response.json()
{
"data": [
{
"run_id": 3301,
"automation_id": 8,
"contact_id": 123,
"object_id": 1,
"held_out": false,
"converted_at": "2026-10-15T09:00:00+03:00",
"event_id": 1,
"value": 199,
"currency": "RON"
}
],
"next_cursor": "string",
"has_more": true
}
{
"type": "https://worbo.app/docs/api/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"detail": "The key is not valid; a lost key cannot be recovered, only replaced.",
"code": "invalid_api_key"
}
{
"type": "https://worbo.app/docs/api/errors/insufficient_scope",
"title": "The API key lacks a permission",
"status": 403,
"detail": "The key does not have the scope of the operation.",
"code": "insufficient_scope"
}
{
"type": "https://worbo.app/docs/api/errors/validation_failed",
"title": "The request is not valid",
"status": 422,
"detail": "A parameter is missing, not valid or not known; the errors field says which.",
"code": "validation_failed"
}
{
"type": "https://worbo.app/docs/api/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests; wait the seconds in Retry-After.",
"code": "rate_limited"
}