API documentation
Connect Basketly to your own system, shop or data warehouse: read orders, products, clients and reports, change feeds, send conversions to the Meta queue and receive events through webhooks.
- Version
- 1.3.0
- Base URL
- https://app.basketly.io/api/v1
- OpenAPI
- OpenAPI spec (JSON)
- Markdown
- Markdown for AI agents
On this page
Quick start
- Create a key in the panel: Konfiguracja → API (Settings → API). Owners and admins of the organisation can create keys. The full key is shown only once, so copy it right away.
- Every URL starts with
https://app.basketly.io/api/v1. Responses are JSON. - Check the key with
GET /me. It needs no scope and returns the organisation, the key name, its scopes and expiry.
curl -H "Authorization: Bearer bsk_live_…" \
https://app.basketly.io/api/v1/meThe other examples read the key from the API_KEY environment variable (export API_KEY=bsk_live_…). Never put the key in website or mobile app code: it is for server-to-server calls only.
AI agents (Claude, ChatGPT, Cursor, n8n) connect to the MCP server at https://app.basketly.io/mcp: with the same key, or with "Connect" (OAuth 2.1 with PKCE) and no key — then the agent acts as the person who connected it, with their role. Every agent tool is a call of this API, so the same scopes and limits apply. An agent can add a note, a status or a task, but never sends a conversion.
Authentication
Every request sends an organisation key in the Authorization: Bearer bsk_live_… header. A key belongs to one organisation and sees only its data.
The full key is shown once, when it is created. We store only its hash, so it cannot be recovered: revoke a lost key and create a new one.
You can revoke a key in the panel at any time and give it an expiry date. A revoked key gets 401 key_revoked, an expired one 401 key_expired.
A key has scopes. A :write scope includes :read of the same area. contacts:*, tasks:* and orders:read return personal data. A missing scope answers 403 insufficient_scope with details.required_scope.
The API and the MCP server are included in the Growth, Agency and Custom plans. On Free and Start the account owner gets a 5-day trial from creating the account. After it every request answers 402 plan_required, new keys cannot be created and API webhooks are not delivered. Once you pick Growth or higher under “Plan & billing”, existing keys work again at once.
| Scope | Allows | Endpoints |
|---|---|---|
contacts:read | Read people of the Klienci (Clients) page (names, phones, e-mails — personal data). | |
contacts:write | Create and change people: details, notes, tags, status, SMS block. | |
events:read | Read conversions and the status of their deliveries to Meta. | |
events:write | Send server-to-server conversions to the queue. | |
tasks:read | Open tasks (personal data). | |
tasks:write | Create, complete and postpone tasks. | |
campaigns:read | Campaign report. |
|
creatives:read | Creative report. | |
automations:read | Automations with their last run and errors of the last day. | |
integrations:read | The integrations overview: states and key-based connections, without keys. | |
orders:read | Shop orders with lines and buyers (personal data: buyer names). | |
products:read | Products, their diagnosis and the product record. | |
products:write | Manual fixes of products. | |
feeds:read | Feeds, rules, flows, Google categories and previews. | |
feeds:write | Create and change feeds, save rules, flows and categories, download now. | |
templates:read | Image templates. | |
diagnostics:read | The last self-check. | |
diagnostics:write | Run the self-check. | |
jobs:read | Read background jobs. | |
webhooks:read | Read webhooks and their delivery history. | |
webhooks:write | Create, change, delete and test webhooks. |
Errors
Every error has the same shape: {"error": {"code", "message", "details?"}}.
code is stable and never translated, so base your error handling on it. message is for humans, in the language of the Accept-Language header: Polish by default, en for English. details appears only when there is extra data, e.g. required_scope.
{
"error": {
"code": "insufficient_scope",
"message": "The API key lacks the contacts:write scope.",
"details": {
"required_scope": "contacts:write"
}
}
}| Code | HTTP | Meaning |
|---|---|---|
unauthorized | 401 | Missing key, wrong format or unknown key. |
key_revoked | 401 | The key was revoked. |
key_expired | 401 | The key is past its expiry date. |
insufficient_scope | 403 | The key lacks the required scope (details.required_scope). |
plan_required | 402 | The organisation’s plan does not include the API: it needs Growth or higher after the 5-day trial. |
rate_limited | 429 | Over 120 requests per minute for this key; wait Retry-After seconds. |
invalid_json | 400 | The body is not valid JSON or has an unknown field. |
validation_error | 400 | A field or parameter has an invalid value; the message says what to fix. |
invalid_cursor | 400 | cursor is not from the previous page of the same list. |
not_found | 404 | No such record in your organisation. |
route_not_found | 404 | No such endpoint. |
conflict | 409 | The operation conflicts with the record’s current state. |
contact_exists | 409 | A person with this phone or e-mail already exists (details.contact_id). |
contact_merged | 409 | The card was merged into another one (details.merged_into). |
event_conflict | 409 | The event conflicts with one already accepted. |
source_paused | 409 | The source is paused. |
limit_reached | 409 | A limit was reached, e.g. 10 webhooks per organisation. |
idempotency_key_reused | 422 | The same Idempotency-Key with a different request body. |
idempotency_in_progress | 409 | A request with this Idempotency-Key is still running; retry shortly. |
payload_too_large | 413 | Request body over 64 KB. |
internal_error | 500 | An error on our side; retry later. |
Pagination
Lists return {"data": [...], "next_cursor", "has_more"}. Set the page size with limit (1–100, default 25).
Fetch the next page by passing the previous response’s next_cursor as cursor. When has_more is false, the list is complete.
Records are ordered by updated_at, then id: order=desc newest first (default) or order=asc.
updated_since (ISO 8601) returns only records changed at or after that time. For incremental sync, store the time you start a sync, page through to the end, and use the stored time as updated_since for the next sync.
Every contact changed since a given time
cursor=""
while :; do
page=$(curl -s -G "https://app.basketly.io/api/v1/contacts" \
-H "Authorization: Bearer $API_KEY" \
--data-urlencode "limit=100" \
--data-urlencode "updated_since=2026-10-01T00:00:00Z" \
${cursor:+--data-urlencode "cursor=$cursor"})
echo "$page" | jq -c '.data[]'
[ "$(echo "$page" | jq -r '.has_more')" = "true" ] || break
cursor=$(echo "$page" | jq -r '.next_cursor')
doneconst records = []
let cursor = null
do {
const params = new URLSearchParams({ limit: '100', updated_since: '2026-10-01T00:00:00Z' })
if (cursor) params.set('cursor', cursor)
const response = await fetch(`https://app.basketly.io/api/v1/contacts?${params}`, {
headers: { Authorization: `Bearer ${process.env.API_KEY}` },
})
if (!response.ok) throw new Error((await response.json()).error.message)
const page = await response.json()
records.push(...page.data)
cursor = page.has_more ? page.next_cursor : null
} while (cursor)import os
import requests
records = []
params = {"limit": 100, "updated_since": "2026-10-01T00:00:00Z"}
while True:
response = requests.get(
"https://app.basketly.io/api/v1/contacts",
headers={"Authorization": f"Bearer {os.environ['API_KEY']}"},
params=params,
timeout=30,
)
response.raise_for_status()
page = response.json()
records.extend(page["data"])
if not page["has_more"]:
break
params["cursor"] = page["next_cursor"]<?php
$records = [];
$params = ['limit' => 100, 'updated_since' => '2026-10-01T00:00:00Z'];
do {
$ch = curl_init('https://app.basketly.io/api/v1/contacts?' . http_build_query($params));
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('API_KEY')],
]);
$page = json_decode(curl_exec($ch), true);
curl_close($ch);
if (isset($page['error'])) {
throw new RuntimeException($page['error']['message']);
}
$records = array_merge($records, $page['data']);
$params['cursor'] = $page['next_cursor'];
} while ($page['has_more']);Limits and formats
- Rate limit: 120 requests per minute per key. Every response carries
X-RateLimit-Limit,X-RateLimit-RemainingandX-RateLimit-Reset. - Over the limit you get
429 rate_limitedwith aRetry-Afterheader (seconds). Wait that long, then retry. - A request body can be at most 64 KB, otherwise
413 payload_too_large. - Times in responses are ISO 8601 in UTC, e.g.
2026-10-08T10:00:00Z.
Idempotency
- Every
POSTaccepts anIdempotency-Keyheader (1–255 visible ASCII characters). Use a value tied to the operation, e.g. a UUID stored with the record on your side. - Repeating a request with the same key and the same body within 24 hours returns the stored response with
Idempotent-Replayed: true, without running the operation again. - The same key with a different body answers
422 idempotency_key_reused. While the first request is still running you get409 idempotency_in_progress. - 5xx and 429 responses are not stored, so after them you can safely retry with the same key.
Endpoints
Keys
The calling key.
/meDescribe the calling key
Organisation, key name, scopes and expiry. A safe first call to check a new key; needs no scope.
Scope: no scope
Responses
200OKResponse fieldsName Description organizationobjectorganization.idstringorganization.namestringkeyobjectkey.idstringkey.namestringkey.prefixstringkey.scopesstring[]key.expires_atstring (date-time) | nullrate_limitobjectrate_limit.per_minuteinteger400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/me" \
-H "Authorization: Bearer $API_KEY"const response = await fetch('https://app.basketly.io/api/v1/me', {
headers: {
Authorization: `Bearer ${process.env.API_KEY}`,
},
})
if (!response.ok) throw new Error((await response.json()).error.message)
const data = await response.json()
console.log(data)import os
import requests
response = requests.get(
"https://app.basketly.io/api/v1/me",
headers={
"Authorization": f"Bearer {os.environ['API_KEY']}",
},
timeout=30,
)
response.raise_for_status()
print(response.json())<?php
$ch = curl_init('https://app.basketly.io/api/v1/me');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('API_KEY'),
],
]);
$data = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status >= 400) {
throw new RuntimeException($data['error']['message']);
}
print_r($data);Contacts
People of the Klienci page.
/contactsList contacts
People of the Klienci page (merged cards are left out). Filter by tag or exact e-mail or phone.
Scope: contacts:read
Parameters
| Name | Description |
|---|---|
limitqueryinteger | Page size, 1–100 (default 25).Default: 25Range: 1–100 |
cursorquerystring | next_cursor of the previous page. |
orderquerystring | Sort by updated_at (then id): desc newest first (default) or asc.Values: asc, descDefault: desc |
updated_sincequerystring (date-time) | Only records changed at or after this ISO 8601 time. |
tagquerystring | Tag. |
emailquerystring | Exact e-mail (matched by hash). |
phonequerystring | Exact phone (matched by hash). |
Responses
200OKResponse fieldsName Description dataContact[]Requireddata[].idstringContact id. data[].namestringFull name. data[].phonestringPhone in E.164 form, empty when unknown. data[].emailstringE-mail, empty when unknown. data[].tagsstring[]Tags. data[].sms_optoutbooleanTrue when SMS to this person are blocked. data[].first_sourcestringWhere the person first came from ("Zamówienie w sklepie", "Ręcznie", API…). data[].status_idstring | nullInternal status id (GET /contact-statuses), null when none. Never sent to Meta. data[].owner_idstring | nullUser id of the person's owner (opiekun). data[].last_activity_atstring (date-time) | nullLast activity time. data[].created_atstring (date-time)Creation time. data[].updated_atstring (date-time)Last change; use with updated_since.data[].ordersobjectWhat the person bought in the shop. data[].orders.countintegerBought, not cancelled shop orders. data[].orders.valuenumberTheir value in currency.data[].orders.currencystringThe currency most of the orders use. data[].orders.first_atstring (date-time) | nullFirst order time. data[].orders.last_atstring (date-time) | nullLast order time. next_cursorstring | nullRequiredPass as cursorto get the next page; null on the last page.has_morebooleanRequiredTrue when another page exists. 400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/contacts" \
-H "Authorization: Bearer $API_KEY"const response = await fetch('https://app.basketly.io/api/v1/contacts', {
headers: {
Authorization: `Bearer ${process.env.API_KEY}`,
},
})
if (!response.ok) throw new Error((await response.json()).error.message)
const data = await response.json()
console.log(data)import os
import requests
response = requests.get(
"https://app.basketly.io/api/v1/contacts",
headers={
"Authorization": f"Bearer {os.environ['API_KEY']}",
},
timeout=30,
)
response.raise_for_status()
print(response.json())<?php
$ch = curl_init('https://app.basketly.io/api/v1/contacts');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('API_KEY'),
],
]);
$data = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status >= 400) {
throw new RuntimeException($data['error']['message']);
}
print_r($data);/contactsCreate a contact
Adds a person like „Dodaj osobę” in the panel.
Scope: contacts:write
Parameters
| Name | Description |
|---|---|
Idempotency-Keyheaderstring | Unique key (1–255 visible ASCII characters). Repeating a POST with the same key and body within 24 hours returns the stored response with Idempotent-Replayed: true.Max length: 255 |
Request body application/json
| Name | Description |
|---|---|
namestring | Full name (up to 200 characters).Max length: 200 |
phonestring | Phone with a country code, e.g. +48 600 857 482, or 9 digits (Poland). |
emailstring | E-mail address.Max length: 254 |
tagsstring[] | Tags. |
Responses
201CreatedResponse fieldsName Description idstringContact id. namestringFull name. phonestringPhone in E.164 form, empty when unknown. emailstringE-mail, empty when unknown. tagsstring[]Tags. sms_optoutbooleanTrue when SMS to this person are blocked. first_sourcestringWhere the person first came from ("Zamówienie w sklepie", "Ręcznie", API…). status_idstring | nullInternal status id (GET /contact-statuses), null when none. Never sent to Meta. owner_idstring | nullUser id of the person's owner (opiekun). last_activity_atstring (date-time) | nullLast activity time. created_atstring (date-time)Creation time. updated_atstring (date-time)Last change; use with updated_since.ordersobjectWhat the person bought in the shop. orders.countintegerBought, not cancelled shop orders. orders.valuenumberTheir value in currency.orders.currencystringThe currency most of the orders use. orders.first_atstring (date-time) | nullFirst order time. orders.last_atstring (date-time) | nullLast order time. 400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).409Conflicts with the current state (conflict,contact_exists,contact_merged,event_conflict,source_paused,idempotency_in_progress).413Body over 64 KB (payload_too_large).422Idempotency-Key reused with a different body (idempotency_key_reused).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl -X POST "https://app.basketly.io/api/v1/contacts" \
-H "Authorization: Bearer $API_KEY" \
-H "Idempotency-Key: 5f0c7a2e-8d41-4b6a-9e3f-1c2d3e4f5a6b" \
-H "Content-Type: application/json" \
-d '{
"name": "Anna Nowak",
"phone": "+48600100200",
"email": "anna@example.com",
"tags": [
"vip"
]
}'const response = await fetch('https://app.basketly.io/api/v1/contacts', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.API_KEY}`,
'Idempotency-Key': '5f0c7a2e-8d41-4b6a-9e3f-1c2d3e4f5a6b',
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "Anna Nowak",
"phone": "+48600100200",
"email": "anna@example.com",
"tags": [
"vip"
]
}),
})
if (!response.ok) throw new Error((await response.json()).error.message)
const data = await response.json()
console.log(data)import os
import requests
response = requests.post(
"https://app.basketly.io/api/v1/contacts",
headers={
"Authorization": f"Bearer {os.environ['API_KEY']}",
"Idempotency-Key": "5f0c7a2e-8d41-4b6a-9e3f-1c2d3e4f5a6b",
},
json={
"name": "Anna Nowak",
"phone": "+48600100200",
"email": "anna@example.com",
"tags": ["vip"],
},
timeout=30,
)
response.raise_for_status()
print(response.json())<?php
$ch = curl_init('https://app.basketly.io/api/v1/contacts');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('API_KEY'),
'Idempotency-Key: 5f0c7a2e-8d41-4b6a-9e3f-1c2d3e4f5a6b',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'name' => 'Anna Nowak',
'phone' => '+48600100200',
'email' => 'anna@example.com',
'tags' => ['vip'],
]),
]);
$data = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status >= 400) {
throw new RuntimeException($data['error']['message']);
}
print_r($data);/contacts/{id}Get a contact
A merged card answers 409 contact_merged with details.merged_into.
Scope: contacts:read
Parameters
| Name | Description |
|---|---|
idpathstringRequired | Record id. |
Responses
200OKResponse fieldsName Description idstringContact id. namestringFull name. phonestringPhone in E.164 form, empty when unknown. emailstringE-mail, empty when unknown. tagsstring[]Tags. sms_optoutbooleanTrue when SMS to this person are blocked. first_sourcestringWhere the person first came from ("Zamówienie w sklepie", "Ręcznie", API…). status_idstring | nullInternal status id (GET /contact-statuses), null when none. Never sent to Meta. owner_idstring | nullUser id of the person's owner (opiekun). last_activity_atstring (date-time) | nullLast activity time. created_atstring (date-time)Creation time. updated_atstring (date-time)Last change; use with updated_since.ordersobjectWhat the person bought in the shop. orders.countintegerBought, not cancelled shop orders. orders.valuenumberTheir value in currency.orders.currencystringThe currency most of the orders use. orders.first_atstring (date-time) | nullFirst order time. orders.last_atstring (date-time) | nullLast order time. 400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).404No such record in your organisation (not_found).409Conflicts with the current state (conflict,contact_exists,contact_merged,event_conflict,source_paused,idempotency_in_progress).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/contacts/abc123" \
-H "Authorization: Bearer $API_KEY"/contacts/{id}Update a contact
Changes name, phone, e-mail or tags; every change is a history line.
Scope: contacts:write
Parameters
| Name | Description |
|---|---|
idpathstringRequired | Record id. |
Request body application/json
| Name | Description |
|---|---|
namestring | Full name. |
phonestring | Phone; an empty string removes it (a phone or an e-mail must remain). |
emailstring | E-mail; an empty string removes it. |
tagsstring[] | Replaces all tags. |
Responses
200OKResponse fieldsName Description idstringContact id. namestringFull name. phonestringPhone in E.164 form, empty when unknown. emailstringE-mail, empty when unknown. tagsstring[]Tags. sms_optoutbooleanTrue when SMS to this person are blocked. first_sourcestringWhere the person first came from ("Zamówienie w sklepie", "Ręcznie", API…). status_idstring | nullInternal status id (GET /contact-statuses), null when none. Never sent to Meta. owner_idstring | nullUser id of the person's owner (opiekun). last_activity_atstring (date-time) | nullLast activity time. created_atstring (date-time)Creation time. updated_atstring (date-time)Last change; use with updated_since.ordersobjectWhat the person bought in the shop. orders.countintegerBought, not cancelled shop orders. orders.valuenumberTheir value in currency.orders.currencystringThe currency most of the orders use. orders.first_atstring (date-time) | nullFirst order time. orders.last_atstring (date-time) | nullLast order time. 400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).404No such record in your organisation (not_found).409Conflicts with the current state (conflict,contact_exists,contact_merged,event_conflict,source_paused,idempotency_in_progress).413Body over 64 KB (payload_too_large).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl -X PATCH "https://app.basketly.io/api/v1/contacts/abc123" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Anna Nowak",
"phone": "+48600100200",
"email": "anna@example.com",
"tags": [
"vip"
]
}'/contacts/{id}/notesAdd a note
Adds a note, call or meeting to the person's history.
Scope: contacts:write
Parameters
| Name | Description |
|---|---|
idpathstringRequired | Record id. |
Idempotency-Keyheaderstring | Unique key (1–255 visible ASCII characters). Repeating a POST with the same key and body within 24 hours returns the stored response with Idempotent-Replayed: true.Max length: 255 |
Request body application/json
| Name | Description |
|---|---|
kindstring | Entry kind.Values: note, call, meetingDefault: note |
textstringRequired | Text, 1–5000 characters.Max length: 5000 |
timestring (date-time) | When it happened (default: now); not in the future. |
Responses
201CreatedResponse fieldsName Description idstringHistory entry id. contact_idstringContact id. kindstringEntry kind. textstringText. timestring (date-time)Entry time. 400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).404No such record in your organisation (not_found).409Conflicts with the current state (conflict,contact_exists,contact_merged,event_conflict,source_paused,idempotency_in_progress).413Body over 64 KB (payload_too_large).422Idempotency-Key reused with a different body (idempotency_key_reused).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl -X POST "https://app.basketly.io/api/v1/contacts/abc123/notes" \
-H "Authorization: Bearer $API_KEY" \
-H "Idempotency-Key: 5f0c7a2e-8d41-4b6a-9e3f-1c2d3e4f5a6b" \
-H "Content-Type: application/json" \
-d '{
"kind": "call",
"text": "Asked when order 1001 will ship."
}'/contacts/{id}/sms-optoutBlock or allow SMS
Keeps the „nie wysyłaj” list and every person with the same phone in step.
Scope: contacts:write
Parameters
| Name | Description |
|---|---|
idpathstringRequired | Record id. |
Idempotency-Keyheaderstring | Unique key (1–255 visible ASCII characters). Repeating a POST with the same key and body within 24 hours returns the stored response with Idempotent-Replayed: true.Max length: 255 |
Request body application/json
| Name | Description |
|---|---|
sms_optoutbooleanRequired | True blocks SMS to the person's phone (for every person sharing it), false allows them again. |
Responses
200OKResponse fieldsName Description idstringContact id. namestringFull name. phonestringPhone in E.164 form, empty when unknown. emailstringE-mail, empty when unknown. tagsstring[]Tags. sms_optoutbooleanTrue when SMS to this person are blocked. first_sourcestringWhere the person first came from ("Zamówienie w sklepie", "Ręcznie", API…). status_idstring | nullInternal status id (GET /contact-statuses), null when none. Never sent to Meta. owner_idstring | nullUser id of the person's owner (opiekun). last_activity_atstring (date-time) | nullLast activity time. created_atstring (date-time)Creation time. updated_atstring (date-time)Last change; use with updated_since.ordersobjectWhat the person bought in the shop. orders.countintegerBought, not cancelled shop orders. orders.valuenumberTheir value in currency.orders.currencystringThe currency most of the orders use. orders.first_atstring (date-time) | nullFirst order time. orders.last_atstring (date-time) | nullLast order time. 400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).404No such record in your organisation (not_found).409Conflicts with the current state (conflict,contact_exists,contact_merged,event_conflict,source_paused,idempotency_in_progress).413Body over 64 KB (payload_too_large).422Idempotency-Key reused with a different body (idempotency_key_reused).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl -X POST "https://app.basketly.io/api/v1/contacts/abc123/sms-optout" \
-H "Authorization: Bearer $API_KEY" \
-H "Idempotency-Key: 5f0c7a2e-8d41-4b6a-9e3f-1c2d3e4f5a6b" \
-H "Content-Type: application/json" \
-d '{
"sms_optout": true
}'/contacts/searchSearch contacts with panel filters
The Klienci list with its filters: values of one filter are alternatives (comma list), filters combine. Every bad value is a 400. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: contacts:read
Parameters
| Name | Description |
|---|---|
qquerystring | Name, e-mail or phone. |
pagequeryinteger | Page, 1 = first. |
per_pagequeryinteger | People per page: 10, 25 (default), 50 or 100. |
ownerquerystring | me, any, none or member ids. |
statusquerystring | Status ids or none. |
tagquerystring | Tag. |
sourcequerystring | First source name. |
buyerquerystring | yes = has a bought shop order, no = none.Values: yes, no |
orders_minquerystring | At least this many bought orders. |
orders_maxquerystring | At most this many bought orders. |
spent_minquerystring | Spent at least. |
spent_maxquerystring | Spent at most. |
last_orderquerystring | Last purchase within.Values: 30d, 90d, 180d, 365d |
inactive_buyerquerystring | Bought before but not in the last N days.Values: 30, 60, 90, 180, 365 |
first_orderquerystring | First purchase within.Values: 30d, 90d |
productquerystring | Bought this catalogue id (repeatable). |
createdquerystring | today, 7d, 30d or 90d (or created_from / created_to days). |
activityquerystring | Last activity: today, 7d, 30d, 90d (or activity_from / activity_to). |
inactive_daysqueryinteger | No activity for at least this many days. |
tasksquerystring | open, overdue or none. |
phonequerystring | yes or no. |
emailquerystring | yes or no. |
optoutquerystring | yes or no. |
duplicatequerystring | yes or no. |
sortquerystring | activity, activity_asc, created, created_asc, spent, spent_asc, orders, orders_asc, aov (average order), aov_asc, last_order, last_order_asc, first_order or first_order_asc. Ascending purchase sorts put people without orders last. |
Responses
200OKResponse fieldsName Description dataobject[]People: id, name, status, owner, tags, last activity, added (created), orders (count, value, first and last), shops (source kinds of bought orders), duplicate (a possible duplicate hint), open_tasks and overdue_tasks (members who see tasks). per_pageintegerPeople per page. totalintegerAll matching people. pageintegerPage number. 400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/contacts/search" \
-H "Authorization: Bearer $API_KEY"/contact-statusesList statuses
The organisation's internal statuses. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: contacts:read
Responses
200OKResponse fieldsName Description statusesobject[]Statuses: id, name, archived, sort, people. 400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/contact-statuses" \
-H "Authorization: Bearer $API_KEY"/contacts/{id}/statusSet the status
Sets an internal status (a history line). Never sent to Meta; status automations start. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: contacts:write
Parameters
| Name | Description |
|---|---|
idpathstringRequired | Record id. |
Idempotency-Keyheaderstring | Unique key (1–255 visible ASCII characters). Repeating a POST with the same key and body within 24 hours returns the stored response with Idempotent-Replayed: true.Max length: 255 |
Request body application/json
| Name | Description |
|---|---|
statusstringRequired | Status id, or "" to clear. |
Responses
200OKResponse fieldsName Description statusstringStatus id ("" = none). ownerstringOwner id. 400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).404No such record in your organisation (not_found).409Conflicts with the current state (conflict,contact_exists,contact_merged,event_conflict,source_paused,idempotency_in_progress).413Body over 64 KB (payload_too_large).422Idempotency-Key reused with a different body (idempotency_key_reused).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl -X POST "https://app.basketly.io/api/v1/contacts/abc123/status" \
-H "Authorization: Bearer $API_KEY" \
-H "Idempotency-Key: 5f0c7a2e-8d41-4b6a-9e3f-1c2d3e4f5a6b" \
-H "Content-Type: application/json" \
-d '{
"status": "purchase"
}'/contacts/{id}/historyContact history
Orders, calls, notes, statuses, tasks and SMS of the person. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: contacts:read
Parameters
| Name | Description |
|---|---|
idpathstringRequired | Record id. |
pagequeryinteger | Page, 1 = newest. |
Responses
200OKResponse fieldsName Description dataobject[]History lines: kind, time, actor, summary, details. pageintegerPage number ( ?page=).morebooleanAn older page exists. 400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).404No such record in your organisation (not_found).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/contacts/abc123/history" \
-H "Authorization: Bearer $API_KEY"/segmentsList segments
Saved customer segments (Klienci filters with a name) and the ready-made shop segments, each with the number of people the member may see and, for saved ones, the Meta audience and Klaviyo list state. A segment's filters are /contacts/search parameters. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: contacts:read
Responses
200OK400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/segments" \
-H "Authorization: Bearer $API_KEY"Conversions
Conversions sent to Meta and their deliveries.
A 202 from POST /events only means the conversion is queued for Meta. A delivery status of accepted means Meta acknowledged receipt. Neither proves that a campaign optimises on this data or that the conversion will be attributed to an ad.
/eventsList events
Conversions with their deliveries; payloads are never returned.
Scope: events:read
Parameters
| Name | Description |
|---|---|
limitqueryinteger | Page size, 1–100 (default 25).Default: 25Range: 1–100 |
cursorquerystring | next_cursor of the previous page. |
orderquerystring | Sort by updated_at (then id): desc newest first (default) or asc.Values: asc, descDefault: desc |
updated_sincequerystring (date-time) | Only records changed at or after this ISO 8601 time. |
sourcequerystring | Source id. |
statusquerystring | Your status. |
external_idquerystring | Your record id. |
Responses
200OKResponse fieldsName Description dataEvent[]Requireddata[].idstringEvent id. data[].source_idstringSource id. data[].external_idstringYour record id. data[].statusstringYour status. data[].meta_event_idstringMeta deduplication id. data[].event_timestring (date-time) | nullEvent time. data[].valuenumber | nullValue. data[].currencystringCurrency. data[].deliveriesDelivery[]Deliveries of this event. data[].deliveries[].idstringDelivery id. data[].deliveries[].event_idstringBasketly event id. data[].deliveries[].target_idstringMeta receiver (dataset connection) id. data[].deliveries[].dataset_idstringMeta dataset (pixel) id. data[].deliveries[].event_namestringMeta event name, e.g. Purchase, AddToCart. data[].deliveries[].meta_event_idstringevent_id sent to Meta. data[].deliveries[].modestringtestorlive.data[].deliveries[].statusstringDelivery status.Values: pending,sending,retry,accepted,failed,cancelleddata[].deliveries[].attemptsintegerAttempts so far. data[].deliveries[].last_errorstringLast error, empty when none. data[].deliveries[].accepted_atstring (date-time) | nullWhen Meta accepted it (receipt, not proof of attribution). data[].deliveries[].next_attempt_atstring (date-time) | nullNext attempt. data[].deliveries[].created_atstring (date-time)Creation time. data[].deliveries[].updated_atstring (date-time)Last change. data[].created_atstring (date-time)Creation time. data[].updated_atstring (date-time)Last change. next_cursorstring | nullRequiredPass as cursorto get the next page; null on the last page.has_morebooleanRequiredTrue when another page exists. 400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/events" \
-H "Authorization: Bearer $API_KEY"/eventsSend a conversion
Server-to-server conversion ingest. The source's active flows map status to Meta events and queue one delivery per dataset; dedupe rules are unchanged. Answers 202: queued, not yet sent.
Scope: events:write
Parameters
| Name | Description |
|---|---|
Idempotency-Keyheaderstring | Unique key (1–255 visible ASCII characters). Repeating a POST with the same key and body within 24 hours returns the stored response with Idempotent-Replayed: true.Max length: 255 |
Request body application/json
| Name | Description |
|---|---|
sourcestringRequired | Id of a Google Sheets or shop source of your organisation (Połączenia → source details). |
external_idstringRequired | Your id of the record (order number…), max 128 characters.Max length: 128 |
statusstringRequired | Your status mapped to a Meta event by the source's flow, e.g. QUALIFIED or purchase.Max length: 80 |
consentbooleanRequired | Must be true: you confirm a legal basis for sending the data. |
event_timeinteger | string (date-time) | Unix seconds or ISO 8601; at most 7 days old. |
event_idstring | Meta deduplication id; generated from source + external_id + status when omitted. |
emailstring | Customer e-mail (hashed with SHA-256 before storage). |
phonestring | Customer phone in E.164 (hashed). |
lead_idstring | Meta lead id of the customer, when you have one; sent to Meta as customer information. |
user_idstring | Your customer id (hashed as external_id). |
fbpstring | _fbp cookie. |
fbcstring | _fbc cookie. |
event_source_urlstring | Page URL of the conversion. |
tracking_idstring | Collector tracking id (shop sources). |
client_ip_addressstring | Customer IP. |
client_user_agentstring | Customer user agent. |
valuenumber | Conversion value (required for Purchase). |
currencystring | ISO 4217 currency (required with value). |
content_idsstring[] | Product ids. |
Responses
202Accepted into the queueResponse fieldsName Description event_idstringBasketly event id. deliveries_createdintegerNew deliveries queued for Meta datasets. duplicatesintegerDeliveries skipped as duplicates. sending_enabledbooleanFalse when the server does not send to Meta (queued only). 400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).404No such record in your organisation (not_found).409Conflicts with the current state (conflict,contact_exists,contact_merged,event_conflict,source_paused,idempotency_in_progress).413Body over 64 KB (payload_too_large).422Idempotency-Key reused with a different body (idempotency_key_reused).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl -X POST "https://app.basketly.io/api/v1/events" \
-H "Authorization: Bearer $API_KEY" \
-H "Idempotency-Key: 5f0c7a2e-8d41-4b6a-9e3f-1c2d3e4f5a6b" \
-H "Content-Type: application/json" \
-d '{
"source": "src_abc123",
"external_id": "order-1001",
"status": "purchase",
"consent": true,
"event_time": "2026-10-08T10:00:00Z",
"email": "anna@example.com",
"phone": "+48600100200",
"value": 1200,
"currency": "PLN"
}'const response = await fetch('https://app.basketly.io/api/v1/events', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.API_KEY}`,
'Idempotency-Key': '5f0c7a2e-8d41-4b6a-9e3f-1c2d3e4f5a6b',
'Content-Type': 'application/json',
},
body: JSON.stringify({
"source": "src_abc123",
"external_id": "order-1001",
"status": "purchase",
"consent": true,
"event_time": "2026-10-08T10:00:00Z",
"email": "anna@example.com",
"phone": "+48600100200",
"value": 1200,
"currency": "PLN"
}),
})
if (!response.ok) throw new Error((await response.json()).error.message)
const data = await response.json()
console.log(data)import os
import requests
response = requests.post(
"https://app.basketly.io/api/v1/events",
headers={
"Authorization": f"Bearer {os.environ['API_KEY']}",
"Idempotency-Key": "5f0c7a2e-8d41-4b6a-9e3f-1c2d3e4f5a6b",
},
json={
"source": "src_abc123",
"external_id": "order-1001",
"status": "purchase",
"consent": True,
"event_time": "2026-10-08T10:00:00Z",
"email": "anna@example.com",
"phone": "+48600100200",
"value": 1200,
"currency": "PLN",
},
timeout=30,
)
response.raise_for_status()
print(response.json())<?php
$ch = curl_init('https://app.basketly.io/api/v1/events');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('API_KEY'),
'Idempotency-Key: 5f0c7a2e-8d41-4b6a-9e3f-1c2d3e4f5a6b',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'source' => 'src_abc123',
'external_id' => 'order-1001',
'status' => 'purchase',
'consent' => true,
'event_time' => '2026-10-08T10:00:00Z',
'email' => 'anna@example.com',
'phone' => '+48600100200',
'value' => 1200,
'currency' => 'PLN',
]),
]);
$data = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status >= 400) {
throw new RuntimeException($data['error']['message']);
}
print_r($data);/events/{id}Get an event
One conversion with the status of each delivery.
Scope: events:read
Parameters
| Name | Description |
|---|---|
idpathstringRequired | Record id. |
Responses
200OKResponse fieldsName Description idstringEvent id. source_idstringSource id. external_idstringYour record id. statusstringYour status. meta_event_idstringMeta deduplication id. event_timestring (date-time) | nullEvent time. valuenumber | nullValue. currencystringCurrency. deliveriesDelivery[]Deliveries of this event. deliveries[].idstringDelivery id. deliveries[].event_idstringBasketly event id. deliveries[].target_idstringMeta receiver (dataset connection) id. deliveries[].dataset_idstringMeta dataset (pixel) id. deliveries[].event_namestringMeta event name, e.g. Purchase, AddToCart. deliveries[].meta_event_idstringevent_id sent to Meta. deliveries[].modestringtestorlive.deliveries[].statusstringDelivery status.Values: pending,sending,retry,accepted,failed,cancelleddeliveries[].attemptsintegerAttempts so far. deliveries[].last_errorstringLast error, empty when none. deliveries[].accepted_atstring (date-time) | nullWhen Meta accepted it (receipt, not proof of attribution). deliveries[].next_attempt_atstring (date-time) | nullNext attempt. deliveries[].created_atstring (date-time)Creation time. deliveries[].updated_atstring (date-time)Last change. created_atstring (date-time)Creation time. updated_atstring (date-time)Last change. 400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).404No such record in your organisation (not_found).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/events/abc123" \
-H "Authorization: Bearer $API_KEY"/deliveriesList deliveries
Delivery status to Meta, e.g. status=failed&updated_since=… to follow failures.
Scope: events:read
Parameters
| Name | Description |
|---|---|
limitqueryinteger | Page size, 1–100 (default 25).Default: 25Range: 1–100 |
cursorquerystring | next_cursor of the previous page. |
orderquerystring | Sort by updated_at (then id): desc newest first (default) or asc.Values: asc, descDefault: desc |
updated_sincequerystring (date-time) | Only records changed at or after this ISO 8601 time. |
statusquerystring | Delivery status.Values: pending, sending, retry, accepted, failed, cancelled |
eventquerystring | Event id. |
targetquerystring | Receiver id. |
Responses
200OKResponse fieldsName Description dataDelivery[]Requireddata[].idstringDelivery id. data[].event_idstringBasketly event id. data[].target_idstringMeta receiver (dataset connection) id. data[].dataset_idstringMeta dataset (pixel) id. data[].event_namestringMeta event name, e.g. Purchase, AddToCart. data[].meta_event_idstringevent_id sent to Meta. data[].modestringtestorlive.data[].statusstringDelivery status.Values: pending,sending,retry,accepted,failed,cancelleddata[].attemptsintegerAttempts so far. data[].last_errorstringLast error, empty when none. data[].accepted_atstring (date-time) | nullWhen Meta accepted it (receipt, not proof of attribution). data[].next_attempt_atstring (date-time) | nullNext attempt. data[].created_atstring (date-time)Creation time. data[].updated_atstring (date-time)Last change. next_cursorstring | nullRequiredPass as cursorto get the next page; null on the last page.has_morebooleanRequiredTrue when another page exists. 400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/deliveries" \
-H "Authorization: Bearer $API_KEY"/conversions/balancePurchase balance
Day by day: the shop's bought orders, purchase events Basketly accepted and, per destination (each Meta receiver, GA4, TikTok, Google Ads, Microsoft Ads, Pinterest, Klaviyo — Klaviyo gets only purchases with an e-mail), purchases accepted, failed and waiting, with the most common failure reasons. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: events:read
Parameters
| Name | Description |
|---|---|
daysquerystring | Period: 7, 14, 30 (default) or 90 days.Values: 7, 14, 30, 90 |
Responses
200OK400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/conversions/balance" \
-H "Authorization: Bearer $API_KEY"Tasks
Tasks.
/tasksList tasks
Open tasks by due time. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: tasks:read
Parameters
| Name | Description |
|---|---|
scopequerystring | Whose work: mine, user (with user) or all.Values: mine, user, allDefault: mine |
userquerystring | Member id for scope=user. |
Responses
200OKResponse fieldsName Description dataobject[]Tasks by due time. countsobjectCounts per section (overdue, today, later, none). truncatedbooleanMore than 500 open tasks. 400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/tasks" \
-H "Authorization: Bearer $API_KEY"/contacts/{id}/tasksCreate a task
Adds a task to the person, like „Dodaj zadanie” on the card. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: tasks:write
Parameters
| Name | Description |
|---|---|
idpathstringRequired | Record id. |
Idempotency-Keyheaderstring | Unique key (1–255 visible ASCII characters). Repeating a POST with the same key and body within 24 hours returns the stored response with Idempotent-Replayed: true.Max length: 255 |
Request body application/json
| Name | Description |
|---|---|
titlestringRequired | Task title.Max length: 200 |
notestring | Note. |
due_datestring | Due day YYYY-MM-DD (organisation's zone); without it no due date. |
due_timestring | Due time HH:MM; without it the end of the day. |
assigneestring | Member id (default: the person's owner or the key's member). |
Responses
201CreatedResponse fieldsName Description idstringRequiredNew record id. 400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).404No such record in your organisation (not_found).409Conflicts with the current state (conflict,contact_exists,contact_merged,event_conflict,source_paused,idempotency_in_progress).413Body over 64 KB (payload_too_large).422Idempotency-Key reused with a different body (idempotency_key_reused).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl -X POST "https://app.basketly.io/api/v1/contacts/abc123/tasks" \
-H "Authorization: Bearer $API_KEY" \
-H "Idempotency-Key: 5f0c7a2e-8d41-4b6a-9e3f-1c2d3e4f5a6b" \
-H "Content-Type: application/json" \
-d '{
"title": "example"
}'/tasks/{id}/doneComplete a task
Closes the task, optionally with a history entry. Send {} for none. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: tasks:write
Parameters
| Name | Description |
|---|---|
idpathstringRequired | Record id. |
Idempotency-Keyheaderstring | Unique key (1–255 visible ASCII characters). Repeating a POST with the same key and body within 24 hours returns the stored response with Idempotent-Replayed: true.Max length: 255 |
Request body application/json
| Name | Description |
|---|---|
entryobject | Optional history entry written with the task (e.g. the call that closed it). |
entry.kindstringRequired | Entry kind.Values: note, call, meeting |
entry.textstringRequired | Text. |
Responses
204Done400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).404No such record in your organisation (not_found).409Conflicts with the current state (conflict,contact_exists,contact_merged,event_conflict,source_paused,idempotency_in_progress).413Body over 64 KB (payload_too_large).422Idempotency-Key reused with a different body (idempotency_key_reused).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl -X POST "https://app.basketly.io/api/v1/tasks/abc123/done" \
-H "Authorization: Bearer $API_KEY" \
-H "Idempotency-Key: 5f0c7a2e-8d41-4b6a-9e3f-1c2d3e4f5a6b" \
-H "Content-Type: application/json" \
-d '{
"entry": {
"kind": "call",
"text": "Asked when order 1001 will ship."
}
}'/tasks/{id}/postponePostpone a task
Moves the due time by days or to a date. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: tasks:write
Parameters
| Name | Description |
|---|---|
idpathstringRequired | Record id. |
Idempotency-Keyheaderstring | Unique key (1–255 visible ASCII characters). Repeating a POST with the same key and body within 24 hours returns the stored response with Idempotent-Replayed: true.Max length: 255 |
Request body application/json
| Name | Description |
|---|---|
daysinteger | Move by 1–30 days.Range: 1–30 |
due_datestring | Or a new day YYYY-MM-DD. |
due_timestring | With due_date: time HH:MM. |
Responses
204Moved400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).404No such record in your organisation (not_found).409Conflicts with the current state (conflict,contact_exists,contact_merged,event_conflict,source_paused,idempotency_in_progress).413Body over 64 KB (payload_too_large).422Idempotency-Key reused with a different body (idempotency_key_reused).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl -X POST "https://app.basketly.io/api/v1/tasks/abc123/postpone" \
-H "Authorization: Bearer $API_KEY" \
-H "Idempotency-Key: 5f0c7a2e-8d41-4b6a-9e3f-1c2d3e4f5a6b" \
-H "Content-Type: application/json" \
-d '{
"days": 1,
"due_date": "example",
"due_time": "example"
}'Integrations
Where the shop sells, advertises and sends its catalogue.
/integrationsList integrations
Every place the shop connects, by category, with its state; connecting stays in the panel. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: integrations:read
Responses
200OK400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/integrations" \
-H "Authorization: Bearer $API_KEY"/getting-startedGetting started
The organisation's setup checklist: shop connected, Meta receiver, a running flow to Meta, a product feed, a Meta ad account read, other platforms (optional) — each with its state counted from the organisation's records and the panel address where it is done. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: integrations:read
Responses
200OK400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/getting-started" \
-H "Authorization: Bearer $API_KEY"/mapConnection map
The whole shop setup read-only: where data comes from (shop sources, feeds, logins), what Basketly does with it (orders, products, conversions, segments, automations, templates) and where it goes (Meta receivers, GA4/TikTok/Pinterest/Klaviyo, Meta audiences and Klaviyo lists, feed formats, API, agents), with the health of every node and edge. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: integrations:read
Responses
200OK400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/map" \
-H "Authorization: Bearer $API_KEY"Orders
Shop orders with their lines and buyers.
/ordersList orders
Shop orders of a period (days in the organisation's zone, default the last 30), bought by default, with the Zamówienia filters and sorts. Values of one filter mean any of them, filters combine; a wrong value is a 400. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: orders:read
Parameters
| Name | Description |
|---|---|
fromquerystring | First day YYYY-MM-DD. |
toquerystring | Last day YYYY-MM-DD (range up to 366 days). |
statusquerystring | Which orders: bought (default), unpaid, cancelled or all; a comma list means any of them.Default: bought |
contactquerystring | Orders of this person (all dates unless from/to are given). |
qquerystring | Order number fragment, or the buyer's full e-mail or phone (matched by hash). |
sourcequerystring | Comma list of shop source ids (facets.sources). |
shop_statusquerystring | The shop's own status name; repeat for several (facets.shop_statuses). |
status_typequerystring | Comma list of new, open, closed, cancelled. |
paidquerystring | The shop's paid flag.Values: yes, no |
total_minquerystring | Order value at least (in the order's currency). |
total_maxquerystring | Order value at most. |
platformquerystring | Comma list of ad source platforms: meta, google_ads, microsoft_ads, tiktok, pinterest, openai_ads, email, comparison, other, direct, unknown (unknown includes orders without one). |
campaignquerystring | Campaign key (a campaign id, or utm:<utm_campaign> — keys come from ad_facets); repeat for several. |
ad_sourcequerystring | yes = a known ad platform, no = direct or unknown.Values: yes, no |
meta_sendquerystring | Comma list of accepted, failed, waiting, none: what happened to the purchase sent to Meta. |
productquerystring | A line whose name contains this text, or whose code, EAN or catalogue id equals it. |
countryquerystring | Comma list of delivery country codes. |
sortquerystring | Order of the list.Values: placed, placed_asc, total, total_asc, items, items_asc, paid, paid_ascDefault: placed |
per_pagequeryinteger | Orders per page.Values: 10, 25, 50, 100Default: 50 |
pagequeryinteger | Page, 1 = first. |
Responses
200OKResponse fieldsName Description ordersobject[]Orders: id, number, placed_at, status, status_type (new, open, closed, cancelled), paid, bought, cancelled, currency, total, shipping, items, city, buyer {id, name}, source_name, source_kind (shoper, idosell, baselinker, woocommerce, shopify…), lines (line count), first_product {name, variant, content_id, quantity, product (record id or empty), image}, sends ([{destination meta | ga4 | tiktok | pinterest | klaviyo | openai_ads | google_ads | microsoft_ads, status accepted | failed | waiting | skipped}] — the worst outcome per destination), ad_source. ad_source= Basketly's own last-click reading of where the order came from, never a platform's attribution: platform (meta | google_ads | microsoft_ads | tiktok | pinterest | openai_ads | email | comparison | other | direct | unknown), method (click_id | utm | ids | feed | referrer | none), link (server = tracking_id in the order, browser = thank-you page linkOrder, shop = the shop's landing data, none), deduced, campaign / adset / ad {id, name}, resolved (ids | names | empty), utm {source, medium, campaign, content, term, id}, click_at, clicks (click ids of the visit: fbc, gclid, gbraid, wbraid, msclkid, ttclid, ttp, epik, oppref, obref), referrer (host), landing (path), first_touch, feed {id, name, channel, product} (the served feed and catalogue product of the tagged link the visit landed on).ad_facetsobjectOrders of the selection (before the platform / campaign filters) per ad platform ( platforms: platform, orders) and per campaign (campaigns: platform, key, name, orders; at most 100).facetsobjectThe period's orders (all states, before the other filters) per shop ( sources: id, name, kind, orders), shop status (shop_statuses: status, status_type, orders) and delivery country (countries: country, orders); at most 100 each.page_sizeintegerOrders per page (per_page). totalintegerAll matching orders. summaryobjectBought orders, unpaid, cancelled, items, revenue per currency, average basket, buyers, new and returning buyers. 400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/orders" \
-H "Authorization: Bearer $API_KEY"/orders/{id}Get an order
One order with its lines, buyer, ad source and the destinations its purchase went to. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: orders:read
Parameters
| Name | Description |
|---|---|
idpathstringRequired | Record id. |
Responses
200OK400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).404No such record in your organisation (not_found).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/orders/abc123" \
-H "Authorization: Bearer $API_KEY"Products
Products with their diagnosis and manual fixes.
/productsList products
Active products of the organisation or of one feed, 50 per page (or per_page), with their diagnosis, origin, Google category, fixes, the period's results and a sales trend. Values of one filter OR, filters AND. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: products:read
Parameters
| Name | Description |
|---|---|
feedquerystring | Only products of this feed. |
qquerystring | Title, SKU, exact EAN or exact catalogue id. |
originquerystring | Comma list of shop source ids or feed ids the products come from (facets.origins). |
categoryquerystring | Shop category, exact; repeat for several (facets.categories). |
brandquerystring | Brand, exact; repeat for several (facets.brands). |
availabilityquerystring | Comma list of in_stock, out_of_stock, preorder, backorder, unknown. |
price_minquerystring | Lowest price a shopper pays (the sale price when there is one). |
price_maxquerystring | Highest price a shopper pays. |
score_minqueryinteger | Lowest quality score 0–100. |
score_maxqueryinteger | Highest quality score 0–100. |
issuequerystring | Comma list of problem codes; any of them matches. |
statusquerystring | Comma list of worst problems: error, warning, clean. |
fixesquerystring | Comma list: auto (Basketly's automatic fixes repair a problem), manual (a person typed a fix), none. |
googlequerystring | Comma list: set (leaves the feeds with a Google category: a person's, its own or a sure automatic one), review (automatic, confidence under 60), none (Basketly picks one by the title when building the feed). |
exposurequerystring | Comma list: none = not shown in catalogue ads in the period, low = under 100 impressions (not shown included), shown. |
salesquerystring | yes = bought in the period, no = not bought.Values: yes, no |
sortquerystring | title (default), title_desc, score (worst first), score_desc, price, stock, issues, synced, or the period's impressions, clicks, ctr, units, revenue, views — most first; _asc = least first.Values: title, title_desc, score, score_desc, price, price_asc, stock, stock_asc, issues, issues_asc, synced, synced_asc, impressions, impressions_asc, clicks, clicks_asc, ctr, ctr_asc, units, units_asc, revenue, revenue_asc, views, views_asc |
daysquerystring | Statistics period: 7, 14, 30 (default) or 90 days.Values: 7, 14, 30, 90 |
per_pagequeryinteger | Products per page: 10, 25, 50 (default) or 100. |
facetsquerystring | 1 adds facets: origins, categories, brands, availability, google and fixes with product counts.Values: 1 |
pagequeryinteger | Page, 1 = first. |
Responses
200OKResponse fieldsName Description productsobject[]Products: id, external_id, title, price, sale_price, availability, stock, score 0–100, worst problem, issues, errors, warnings, origin_name, origin_kind, google {id, path, by person|own|auto|title|none, confidence, review — an automatic or by-title pick under 60 to check}, fixes {auto, manual}, stats of the period, trend (bought units per day, per week over 30 days). totalintegerAll matching products. summaryobjectCounts of the selection: errors, warnings, average score, problems by code. per_pageintegerProducts per page. facetsobjectWith facets=1: origins (id, name, kind, products), categories and brands (name, products; top 500), availability, google (set, review, none) and fixes (auto, manual) counts of the whole selection. 400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/products" \
-H "Authorization: Bearer $API_KEY"/products/performanceCatalogue performance
Products not shown, barely shown and shown in catalogue ads, best sellers and most shown without a sale. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: products:read
Parameters
| Name | Description |
|---|---|
daysquerystring | Statistics period: 7, 14, 30 (default) or 90 days.Values: 7, 14, 30, 90 |
Responses
200OK400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/products/performance" \
-H "Authorization: Bearer $API_KEY"/products/{product}/statsProduct results
One product in ads (Meta per-product breakdown), in the shop (Basketly script) and in sales (orders), per day. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: products:read
Parameters
| Name | Description |
|---|---|
productpathstringRequired | Product record id. |
daysquerystring | Statistics period: 7, 14, 30 (default) or 90 days.Values: 7, 14, 30, 90 |
Responses
200OK400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).404No such record in your organisation (not_found).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/products/abc123/stats" \
-H "Authorization: Bearer $API_KEY"/products/{product}Get a product
The product record of the panel. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: products:read
Parameters
| Name | Description |
|---|---|
productpathstringRequired | Product record id. |
Responses
200OK400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).404No such record in your organisation (not_found).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/products/abc123" \
-H "Authorization: Bearer $API_KEY"/products/{product}/overridesSet manual fixes
Values typed by hand win over fixes, rules and flows in every feed. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: products:write
Parameters
| Name | Description |
|---|---|
productpathstringRequired | Product record id. |
Request body application/json
| Name | Description |
|---|---|
overridesobjectRequired | Attribute → value typed by hand; they win in every feed and survive the next read. "" removes the attribute. Replaces all manual fixes (send the ones to keep). id cannot be changed. |
Responses
200OK400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).404No such record in your organisation (not_found).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl -X PUT "https://app.basketly.io/api/v1/products/abc123/overrides" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"overrides": {}
}'/guidelinesList guidelines
Vendor rules for product data with their sources and how many products break each. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: products:read
Responses
200OK400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/guidelines" \
-H "Authorization: Bearer $API_KEY"/products/{product}/title-suggestionsSuggest titles
Asks the AI (MiniMax) for 1–3 better titles following the title guidelines; only the product's own attributes are sent, every proposal is rechecked by Basketly's title checks. Nothing is saved. Uses the organisation's daily AI budget (429 when spent, also over 30 calls per hour). Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: products:write
Parameters
| Name | Description |
|---|---|
productpathstringRequired | Product record id. |
Responses
200OK400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).404No such record in your organisation (not_found).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.502MiniMax did not answer or gave no title that passes the guidelines (upstream_error).503AI suggestions are off on this server: no MiniMax key (unavailable).
Example
curl -X POST "https://app.basketly.io/api/v1/products/abc123/title-suggestions" \
-H "Authorization: Bearer $API_KEY"/meta-catalog/statusCatalogue status of products
For up to 500 content ids: review status, availability, visibility and product set membership in the chosen Meta catalogues, with the catalogue reason the product gets no delivery. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: products:read
Parameters
| Name | Description |
|---|---|
content_idsquerystringRequired | Comma list of content ids (catalogue retailer_id), at most 500. |
Responses
200OK400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/meta-catalog/status" \
-H "Authorization: Bearer $API_KEY"Feeds
Product feeds, their rules, flows and Google categories.
/products/unseen-flowFeed of products not shown
Creates a flow feed of the products catalogue ads showed fewer than below times in the last days days. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: feeds:write
Parameters
| Name | Description |
|---|---|
Idempotency-Keyheaderstring | Unique key (1–255 visible ASCII characters). Repeating a POST with the same key and body within 24 hours returns the stored response with Idempotent-Replayed: true.Max length: 255 |
Request body application/json
| Name | Description |
|---|---|
daysintegerRequired | Period: 7, 14, 30 or 90 days.Values: 7, 14, 30, 90 |
belownumberRequired | Impressions under which a product counts as barely shown (≥ 1). |
Responses
200OK400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).409Conflicts with the current state (conflict,contact_exists,contact_merged,event_conflict,source_paused,idempotency_in_progress).413Body over 64 KB (payload_too_large).422Idempotency-Key reused with a different body (idempotency_key_reused).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl -X POST "https://app.basketly.io/api/v1/products/unseen-flow" \
-H "Authorization: Bearer $API_KEY" \
-H "Idempotency-Key: 5f0c7a2e-8d41-4b6a-9e3f-1c2d3e4f5a6b" \
-H "Content-Type: application/json" \
-d '{
"days": 7,
"below": 1
}'/feedsList feeds
Every feed with its state and output address. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: feeds:read
Responses
200OKResponse fieldsName Description feedsobject[]Feeds as in GET /feeds/{id} without fields, plus category_coverage{covered, products}: products that leave the feed with a Google category (as on the Kategorie Google tab), null when it cannot be counted.400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/feeds" \
-H "Authorization: Bearer $API_KEY"/feedsCreate a feed
Like „Dodaj feed”: from an address (download queued), a shop, other feeds or as a flow (starts with the chosen feeds joined in the output, or with a whole canvas). Uploading a file stays in the panel. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: feeds:write
Parameters
| Name | Description |
|---|---|
Idempotency-Keyheaderstring | Unique key (1–255 visible ASCII characters). Repeating a POST with the same key and body within 24 hours returns the stored response with Idempotent-Replayed: true.Max length: 255 |
Request body application/json
| Name | Description |
|---|---|
namestringRequired | 2–120 characters. |
inputstringRequired | Where products come from.Values: url, source, feeds, flow |
urlstring | input=url: the feed address (http/https). |
sourcestring | input=source: a connected shop source id. |
refresh_hoursinteger | input=url: download every 0 (never), 1, 3, 6, 12 or 24 hours.Values: 0, 1, 3, 6, 12, 24 |
parentsstring[] | input=feeds or flow: 1–10 feeds with their own products, in priority order. |
graphobject | input=flow: a whole canvas (as in PUT /feeds/{id}/flow) instead of parents. |
outputstring | Served format: "" Google/Meta XML, ceneo, csv, openai (ChatGPT Ads CSV).Values: `, ceneo, csv, openai` |
Responses
200OK400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).409Conflicts with the current state (conflict,contact_exists,contact_merged,event_conflict,source_paused,idempotency_in_progress).413Body over 64 KB (payload_too_large).422Idempotency-Key reused with a different body (idempotency_key_reused).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl -X POST "https://app.basketly.io/api/v1/feeds" \
-H "Authorization: Bearer $API_KEY" \
-H "Idempotency-Key: 5f0c7a2e-8d41-4b6a-9e3f-1c2d3e4f5a6b" \
-H "Content-Type: application/json" \
-d '{
"name": "Anna Nowak",
"input": "url",
"url": "https://example.com/webhooks"
}'/feeds/{id}Get a feed
A feed with its attributes; a flow with its canvas. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: feeds:read
Parameters
| Name | Description |
|---|---|
idpathstringRequired | Record id. |
Responses
200OK400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).404No such record in your organisation (not_found).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/feeds/abc123" \
-H "Authorization: Bearer $API_KEY"/feeds/{id}Update a feed
Name, address, download interval, automatic fixes, parents, output format, link tagging (fields of link_tagging left out stay). Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: feeds:write
Parameters
| Name | Description |
|---|---|
idpathstringRequired | Record id. |
Request body application/json
| Name | Description |
|---|---|
namestring | 2–120 characters. |
urlstring | input=url only. |
refresh_hoursinteger | input=url only.Values: 0, 1, 3, 6, 12, 24 |
autofixboolean | Automatic fixes on or off (feeds with rules). |
parentsstring[] | input=feeds only. |
outputstring | Served format (a flow's output block follows).Values: `, ceneo, csv, openai` |
link_taggingLinkTagging | |
link_tagging.enabledboolean | Tag served product links (the last step before serving). New feeds start on; feeds made before the setting stay off until switched on. |
link_tagging.presetstring | marker = only Basketly's bk_f/bk_pid; meta, google, ceneo = channel parameters; custom = your own. A named preset without params takes its own parameters.Values: marker, meta, google, ceneo, custom |
link_tagging.paramsstring | Query template added to links, e.g. utm_source=facebook&utm_content={id}. Placeholders {id}, {item_group_id}, {brand}, {product_type}, {feed} (the feed's name as a slug) are filled from the product and URL-encoded; any other {…} or {{…}} is a channel macro (Google ValueTrack, Meta) served untouched. bk_f/bk_pid are reserved. Parameters a link already has are never replaced. |
link_tagging.ads_redirectboolean | Google: the parameters go only into ads_redirect (Shopping ad clicks; built from link when the product has none), link and mobile_link get only the marker. Ignored for Ceneo. |
Responses
200OK400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).404No such record in your organisation (not_found).413Body over 64 KB (payload_too_large).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl -X PATCH "https://app.basketly.io/api/v1/feeds/abc123" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Anna Nowak",
"url": "https://example.com/webhooks"
}'/feeds/{id}/link-tagging/previewPreview link tagging
Tags one served product's links with a draft setting (fields left out = the saved ones). Nothing is saved. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: feeds:read
Parameters
| Name | Description |
|---|---|
idpathstringRequired | Record id. |
Request body application/json
| Name | Description |
|---|---|
enabledboolean | Tag served product links (the last step before serving). New feeds start on; feeds made before the setting stay off until switched on. |
presetstring | marker = only Basketly's bk_f/bk_pid; meta, google, ceneo = channel parameters; custom = your own. A named preset without params takes its own parameters.Values: marker, meta, google, ceneo, custom |
paramsstring | Query template added to links, e.g. utm_source=facebook&utm_content={id}. Placeholders {id}, {item_group_id}, {brand}, {product_type}, {feed} (the feed's name as a slug) are filled from the product and URL-encoded; any other {…} or {{…}} is a channel macro (Google ValueTrack, Meta) served untouched. bk_f/bk_pid are reserved. Parameters a link already has are never replaced. |
ads_redirectboolean | Google: the parameters go only into ads_redirect (Shopping ad clicks; built from link when the product has none), link and mobile_link get only the marker. Ignored for Ceneo. |
Responses
200OK400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).404No such record in your organisation (not_found).413Body over 64 KB (payload_too_large).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl -X POST "https://app.basketly.io/api/v1/feeds/abc123/link-tagging/preview" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"enabled": true,
"preset": "marker",
"params": "example",
"ads_redirect": true
}'/feeds/{id}/rulesSave feed rules
Replaces the rules of a feed (not a flow); the served feed uses them at once. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: feeds:write
Parameters
| Name | Description |
|---|---|
idpathstringRequired | Record id. |
Request body application/json
| Name | Description |
|---|---|
rulesobject[]Required | Ordered rules: id, name, off, match (""/all/any), when [{field, op, value, to}], then and else [{type, field, value, with, from}]. Replaces all rules. Refused for flows. |
Responses
200OK400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).404No such record in your organisation (not_found).413Body over 64 KB (payload_too_large).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl -X PUT "https://app.basketly.io/api/v1/feeds/abc123/rules" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"rules": [
{}
]
}'/feeds/{id}/flowSave a feed flow
Replaces the canvas of a flow; the served feed and its format follow at once. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: feeds:write
Parameters
| Name | Description |
|---|---|
idpathstringRequired | Record id. |
Request body application/json
| Name | Description |
|---|---|
graphobjectRequired | The canvas {nodes: [{id, type, label, config, x, y}], edges: [{from, port, to}]}. Node types: input {feed, processed}, filter and branch {match, when}, change {actions}, autofix, categories, output {format}. Ports: out; branch yes/no. Exactly one output; every node on a path from an input to the output; no loops; each feed once. A refused canvas answers 400 with node_id. |
Responses
200OK400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).404No such record in your organisation (not_found).413Body over 64 KB (payload_too_large).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl -X PUT "https://app.basketly.io/api/v1/feeds/abc123/flow" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"graph": {}
}'/feeds/{id}/previewPreview a feed
Runs unsaved rules or an unsaved canvas over every product and returns what would change. Nothing is saved. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: feeds:read
Parameters
| Name | Description |
|---|---|
idpathstringRequired | Record id. |
Request body application/json
| Name | Description |
|---|---|
rulesobject[] | Rules to try (feeds with rules). |
graphobject | A canvas to try (flows). |
Responses
200OK400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).404No such record in your organisation (not_found).413Body over 64 KB (payload_too_large).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl -X POST "https://app.basketly.io/api/v1/feeds/abc123/preview" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"rules": [
{}
],
"graph": {}
}'/feeds/{id}/fetchDownload now
Queues a download of the feed (feeds made from feeds and flows: of their inputs). Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: feeds:write
Parameters
| Name | Description |
|---|---|
idpathstringRequired | Record id. |
Idempotency-Keyheaderstring | Unique key (1–255 visible ASCII characters). Repeating a POST with the same key and body within 24 hours returns the stored response with Idempotent-Replayed: true.Max length: 255 |
Responses
204Queued400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).404No such record in your organisation (not_found).409Conflicts with the current state (conflict,contact_exists,contact_merged,event_conflict,source_paused,idempotency_in_progress).422Idempotency-Key reused with a different body (idempotency_key_reused).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl -X POST "https://app.basketly.io/api/v1/feeds/abc123/fetch" \
-H "Authorization: Bearer $API_KEY" \
-H "Idempotency-Key: 5f0c7a2e-8d41-4b6a-9e3f-1c2d3e4f5a6b"/feeds/{id}/categoriesFeed categories
Shop categories with the Google category each gets — a person's, chosen automatically with its confidence, or by each product's title — and suggestions. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: feeds:read
Parameters
| Name | Description |
|---|---|
idpathstringRequired | Record id. |
Responses
200OK400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).404No such record in your organisation (not_found).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/feeds/abc123/categories" \
-H "Authorization: Bearer $API_KEY"/feeds/{id}/categoriesSave feed categories
Saves the Google category and own name per shop category; entries without auto are a person's and win over the automatic choice. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: feeds:write
Parameters
| Name | Description |
|---|---|
idpathstringRequired | Record id. |
Request body application/json
| Name | Description |
|---|---|
categoriesobject[]Required | [{name, google, own, auto, confidence, broad}] — shop category, Google category number (0 = none), own name ("" = keep); without auto the Google category is a person's choice and is never changed automatically; with auto: true it stays Basketly's to recalculate. Replaces the whole list. |
Responses
200OK400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).404No such record in your organisation (not_found).413Body over 64 KB (payload_too_large).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl -X PUT "https://app.basketly.io/api/v1/feeds/abc123/categories" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"categories": [
{}
]
}'/feeds/{id}/categoriesChange some feed categories
Changes only the named rows: a person's Google category, confirming the automatic one, back to automatic, own name. Answers with the changed rows and the summary of all rows. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: feeds:write
Parameters
| Name | Description |
|---|---|
idpathstringRequired | Record id. |
Request body application/json
| Name | Description |
|---|---|
changesobject[]Required | 1–500 rows to change: [{name, google?, confirm?, restore?, own?}] — name = shop category as GET gives it; one of google (a Google category id: a person's choice, wins from then on), confirm: true (the current automatic choice becomes a person's) or restore: true (back to Basketly's automatic choice, chosen again), and/or own (own name, "" removes it). Other rows stay as they are. |
Responses
200OK400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).404No such record in your organisation (not_found).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl -X PATCH "https://app.basketly.io/api/v1/feeds/abc123/categories" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"changes": [
{}
]
}'/google-categoriesSearch Google categories
Finds categories of Google's product taxonomy by Polish or English words or by id (for the google of feed categories). Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: feeds:read
Parameters
| Name | Description |
|---|---|
qquerystring | Words or a category id, up to 100 characters. |
Responses
200OK400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/google-categories" \
-H "Authorization: Bearer $API_KEY"/google-taxonomyGoogle taxonomy version
Which version of Google's product taxonomy Basketly uses, how many categories it has and when Google was last checked (weekly by itself). Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: feeds:read
Responses
200OK400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/google-taxonomy" \
-H "Authorization: Bearer $API_KEY"/google-taxonomy/refreshCheck Google taxonomy now
Queues a background job that downloads Google's taxonomy (Polish and English) and puts a valid newer version in use. Owner or admin of the organisation only. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: feeds:write
Responses
202OK400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl -X POST "https://app.basketly.io/api/v1/google-taxonomy/refresh" \
-H "Authorization: Bearer $API_KEY"/feeds/{id}/categories/autoRecalculate automatic categories
Chooses the Google category of every shop category a person has not chosen again (Basketly does it by itself after every product read; this redoes the existing choices too). A person's choices stay. Answers like GET. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: feeds:write
Parameters
| Name | Description |
|---|---|
idpathstringRequired | Record id. |
Responses
200OK400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).404No such record in your organisation (not_found).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl -X POST "https://app.basketly.io/api/v1/feeds/abc123/categories/auto" \
-H "Authorization: Bearer $API_KEY"Image templates
Image templates for catalogue ads.
/templatesList image templates
Templates a feed rule or flow block can draw product images with. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: templates:read
Responses
200OKResponse fieldsName Description templatesobject[]Templates: id, name, format, updated. 400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/templates" \
-H "Authorization: Bearer $API_KEY"/templates/performanceTemplate results
Ad results of the products of feeds that use each template. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: templates:read
Parameters
| Name | Description |
|---|---|
fromquerystring | First day YYYY-MM-DD (default: 30 days ago). |
toquerystring | Last day YYYY-MM-DD. |
Responses
200OK400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/templates/performance" \
-H "Authorization: Bearer $API_KEY"/templates/{id}Get an image template
One template with its layers. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: templates:read
Parameters
| Name | Description |
|---|---|
idpathstringRequired | Record id. |
Responses
200OK400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).404No such record in your organisation (not_found).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/templates/abc123" \
-H "Authorization: Bearer $API_KEY"Reports
Campaign and creative numbers.
/campaigns/reportCampaign report
Spend, purchases, purchase value, cost per purchase and ROAS per campaign, ad set or ad — the numbers of the Kampanie page.
Scope: campaigns:read
Parameters
| Name | Description |
|---|---|
fromquerystring | First day YYYY-MM-DD (default: 30 days ago). |
toquerystring | Last day YYYY-MM-DD (range up to 366 days). |
levelquerystring | Row level.Values: campaign, adset, adDefault: campaign |
campaignquerystring | Only this campaign (for adset/ad level). |
adsetquerystring | Only this ad set (ad level). |
ad_accountquerystring | Only this Meta ad account (act_… or the bare number); the shop's totals then come only when it is the organisation's only account. |
Responses
200OKResponse fieldsName Description fromstringFirst day (organisation time zone). tostringLast day. levelstringRow level.Values: campaign,adset,adtotalsobject[]Totals per currency: currentand thepreviousperiod of the same length. Without a campaign/adset filter they also carry the shop's bought orders of the period (shop_orders,shop_revenue) andmer= shop revenue / spend. Totals and rows also carrybaskety_orders,baskety_revenue,baskety_currency: bought orders whose ad source (Basketly's last-click reading of the buyer's visits) is a Meta campaign / ad set / ad of the row — a different count from Meta's purchases, never added to them.rowsarray Each row also names its Meta ad account: `ad_account_id` and `ad_account_name` (empty until read).One row per campaign / ad set / ad with status(Meta's effective status),optimization(what its ad sets optimize for:labelin Polish — Meta's standard events named in Polish, e.g. „Zdarzenie: Zakup (zbiór …)” —eventand, for an event goal,event_name= Meta's technical name such as Purchase,short= the goal in words without the dataset for a badge, e.g. „Zakup”) andmetrics: spend, purchases, purchase_value, cost_per_purchase, roas (purchases and their value as Meta reports them); delivery summed over the period's days — impressions, clicks (all clicks), cpm (spend / impressions × 1000), link_clicks, ctr (link clicks / impressions, a fraction), cpc (spend / link click; link figures count only days read with link clicks, null before), video plays_3s, thruplays, p25, p50, p75, p100, hook_rate (3 s plays / impressions) and hold_rate (ThruPlays / 3 s plays; null for images); costs and ratios are null when the divisor is 0. Reach and frequency are not offered (daily reach cannot be added up). Plusbaskety_orders/baskety_revenuefrom the orders' ad source. Totals carry the same delivery fields. Każdy wiersz podaje też konto reklamowe Mety:ad_account_idiad_account_name(puste, dopóki nie odczytane).syncsobject[]Spend sync state per ad account: ad_account_id(act_…),ad_account_name(the account's name in Meta; empty until the next spend read stores it),currency,last_success,last_errorandjobwhile it waits or is read.comparisonobjectHow the totals compare with the previous period: {mode whole|same_hour, until, previous_until, spend_without_today, spend_to, previous_spend_to}. A period that ends today compares Meta's figures and MER over whole days (today left out of both periods) and the shop's and Basketly's orders up to the same hour; each total's compareholds the current figures to set againstprevious(currentstill counts today).shopsstring[]Kinds of the connected shops (shoper, idosell, baselinker…) whose orders give the shop's numbers. 400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/campaigns/report" \
-H "Authorization: Bearer $API_KEY"/creatives/reportCreative report
Metrics per creative group with weekly momentum and the fatigue rule — the numbers of the Kreacje page.
Scope: creatives:read
Parameters
| Name | Description |
|---|---|
fromquerystring | First day YYYY-MM-DD. |
toquerystring | Last day YYYY-MM-DD. |
formatquerystring | Creative format. |
campaignquerystring | Meta campaign id. |
ad_accountquerystring | Ad account id. |
tagquerystring | Tag filter dimension:value (hook, creator, offer). |
weeksqueryinteger | Weeks of momentum, 4–8. |
channelquerystring | meta (default) = Meta's creative groups; google_ads, microsoft_ads, tiktok, pinterest or openai_ads = that platform's ads (and Google's assets) with the platform's figures — the answer is then a ChannelCreatives object.Values: meta, google_ads, microsoft_ads, tiktok, pinterest, openai_ads |
activequerystring | 1 = only active ads. |
Responses
200OKResponse fieldsName Description fromstringFirst day. tostringLast day. weeksarray[]Week ranges used for momentum. rowsarray `ad_accounts` lists the Meta ad account ids of the group and `ad_account_names` their names in the same order (empty until read).One row per creative group (same video, image or creative) with metrics, weekly trend, fatigue verdict and tags. baskety_orders/baskety_revenue(inbaskety_currency): bought shop orders of the period whose ad source (Basketly's last-click reading of the buyer's visits) is one of the creative's ads — a different count from Meta's purchases, never added to them.ad_accountsto identyfikatory kont reklamowych Mety grupy, aad_account_namesich nazwy w tej samej kolejności (puste, dopóki nie odczytane).totalsobject[]Totals per currency. compare_totalsobject[]The current totals to set against previous_totals: when the period ends today, the same creatives over whole days without today (Meta reports per day); otherwise equal tototals. Empty for a period of today only (nothing to compare).comparisonobjectHow the totals compare with the previous period: {mode whole|same_hour, until, previous_until, spend_without_today, spend_to, previous_spend_to}; previous_totals then leave out the matching last day. previous_totalsobject[]Totals per currency of the same filtered creatives over the period of the same length just before from(the comparison under the Kreacje summary; when the period ends today, without its last day — seecomparison).syncsobject[]Spend sync state per ad account: ad_account_id(act_…),ad_account_name(the account's name in Meta; empty until the next spend read stores it),currency,last_success,last_errorandjobwhile it waits or is read.400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/creatives/report" \
-H "Authorization: Bearer $API_KEY"/reports/salesSales report
The shop's sales of a period next to the previous one: orders, revenue in the main currency, average order, buyers new/returning, Meta spend, purchases, ROAS, MER, spend per order, purchases accepted for sending, Google Ads / Microsoft Ads spend as each reports it, per day, the top 10 products and verdict — one sentence about the period (start here when reading Raporty). Sources are never mixed. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: campaigns:read
Parameters
| Name | Description |
|---|---|
fromquerystring | First day YYYY-MM-DD (default: 30 days ago). |
toquerystring | Last day YYYY-MM-DD (up to 366 days). |
Responses
200OK400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/reports/sales" \
-H "Authorization: Bearer $API_KEY"/reports/channelsChannels and the shop
Every channel of the period next to the shop: blended spend, MER and cost per order over all channels, each channel's figures as its platform reports them (never added across platforms), Basketly's last-click orders per source (shares add up to 100%), the sum of the platforms' own conversions next to the shop's orders, per day revenue and spend per channel — Raporty → Sprzedaż. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: campaigns:read
Parameters
| Name | Description |
|---|---|
fromquerystring | First day YYYY-MM-DD (default: 30 days ago). |
toquerystring | Last day YYYY-MM-DD (up to 366 days). |
Responses
200OK400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/reports/channels" \
-H "Authorization: Bearer $API_KEY"/reports/product-channelsProducts × channels
Every product against every channel that shows it — Raporty → Kanały × produkty: per channel impressions, clicks, spend, CTR and the platform's own per-product conversions where the platform reports them (Meta does not: null), Basketly's last-click orders containing the product, units per 100 clicks and the channel's impression share; the shop's units, revenue, views, carts, stock and days of cover; best channel, spend split and source split. Never adds platform conversions together. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: campaigns:read
Parameters
| Name | Description |
|---|---|
fromquerystring | First day YYYY-MM-DD (default: 30 days ago). |
toquerystring | Last day YYYY-MM-DD (up to 366 days). |
channelquerystring | Only products with figures in this channel; spend and orders sorting read it.Values: meta, google_ads, microsoft_ads, tiktok, pinterest, openai_ads |
sortquerystring | Sort order (add _asc for ascending).Values: spend, orders, best, units, revenue, spend_asc, orders_asc, best_asc, units_asc, revenue_ascDefault: spend |
pagequerystring | Page from 1. |
per_pagequerystring | Rows per page: 10, 25, 50 or 100.Default: 50 |
content_idquerystring | One product by its catalogue id. |
Responses
200OK400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/reports/product-channels" \
-H "Authorization: Bearer $API_KEY"/channels/reportChannel report
Without channel every channel's totals (Kampanie → Wszystkie kanały); with channel that channel's campaigns (ad groups, ads where read) with the platform's figures and Basketly's last-click orders — one vocabulary for Meta, Google Ads, Microsoft Ads, TikTok and Pinterest. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: campaigns:read
Parameters
| Name | Description |
|---|---|
fromquerystring | First day YYYY-MM-DD (default: 30 days ago). |
toquerystring | Last day YYYY-MM-DD (up to 366 days). |
channelquerystring | One paid channel; empty = every channel's totals.Values: meta, google_ads, microsoft_ads, tiktok, pinterest, openai_ads |
levelquerystring | Row level.Values: campaign, adgroup, adDefault: campaign |
parentquerystring | The campaign of ad groups / the ad group of ads. |
Responses
200OK400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/channels/report" \
-H "Authorization: Bearer $API_KEY"/manual-spendCosts outside ad platforms
Costs the team typed in (comparison sites, e-mail tool, agency, other) per month. Each month's amount is spread evenly over its days and counted in the blended spend, MER and cost per order (GET /reports/channels: lines with manual), never as a platform's figures. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: campaigns:read
Responses
200OK400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/manual-spend" \
-H "Authorization: Bearer $API_KEY"/manual-spendAdd a cost outside ad platforms
Adds one month's cost. Nothing is sent anywhere; only the blended spend and MER change. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: campaigns:write
Parameters
| Name | Description |
|---|---|
Idempotency-Keyheaderstring | Unique key (1–255 visible ASCII characters). Repeating a POST with the same key and body within 24 hours returns the stored response with Idempotent-Replayed: true.Max length: 255 |
Request body application/json
| Name | Description |
|---|---|
channelstring | comparison, email, agency or other.Values: comparison, email, agency, other |
namestring | What the cost is, up to 80 characters (required for other). |
monthstring | YYYY-MM. |
amountnumber | More than 0, at most 1 000 000 000. |
currencystring | ISO code (default: the shop's main currency). |
notestring | Up to 500 characters. |
Responses
201CreatedResponse fieldsName Description idstringEntry id. channelstringKind of cost: comparison (comparison sites), email (e-mail tool), agency, other.Values: comparison,email,agency,othernamestringWhat the cost is (required for other). monthstringMonth YYYY-MM; the amount is spread evenly over its days. amountnumberThe month's amount. currencystringISO currency code. notestringNote. createdstring (date-time)Created. updatedstring (date-time)Changed. 400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl -X POST "https://app.basketly.io/api/v1/manual-spend" \
-H "Authorization: Bearer $API_KEY" \
-H "Idempotency-Key: 5f0c7a2e-8d41-4b6a-9e3f-1c2d3e4f5a6b" \
-H "Content-Type: application/json" \
-d '{
"name": "Anna Nowak",
"currency": "PLN"
}'/manual-spend/{id}Change a cost outside ad platforms
Changes any field of one entry. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: campaigns:write
Parameters
| Name | Description |
|---|---|
idpathstringRequired | Record id. |
Request body application/json
| Name | Description |
|---|---|
channelstring | comparison, email, agency or other.Values: comparison, email, agency, other |
namestring | What the cost is. |
monthstring | YYYY-MM. |
amountnumber | The month's amount. |
currencystring | ISO code. |
notestring | Note. |
Responses
200OKResponse fieldsName Description idstringEntry id. channelstringKind of cost: comparison (comparison sites), email (e-mail tool), agency, other.Values: comparison,email,agency,othernamestringWhat the cost is (required for other). monthstringMonth YYYY-MM; the amount is spread evenly over its days. amountnumberThe month's amount. currencystringISO currency code. notestringNote. createdstring (date-time)Created. updatedstring (date-time)Changed. 400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).404No such record in your organisation (not_found).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl -X PATCH "https://app.basketly.io/api/v1/manual-spend/abc123" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Anna Nowak",
"currency": "PLN"
}'/manual-spend/{id}Remove a cost outside ad platforms
Removes one entry for good. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: campaigns:write
Parameters
| Name | Description |
|---|---|
idpathstringRequired | Record id. |
Responses
200OKResponse fieldsName Description deletedstringRequiredId of the removed entry. 400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).404No such record in your organisation (not_found).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl -X DELETE "https://app.basketly.io/api/v1/manual-spend/abc123" \
-H "Authorization: Bearer $API_KEY"/reports/insightsReport conclusions
Every rule-based conclusion of the period (sales, product trajectories, product magnets, stock, tagged feeds) with the numbers behind it and a suggested action, most important first. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: campaigns:read
Parameters
| Name | Description |
|---|---|
fromquerystring | First day YYYY-MM-DD (default: 30 days ago). |
toquerystring | Last day YYYY-MM-DD (up to 366 days). |
channelquerystring | Only conclusions about this channel (the channels counts still cover every conclusion).Values: meta, google_ads, microsoft_ads, tiktok, pinterest, email, comparison, other, direct, unknown |
Responses
200OK400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/reports/insights" \
-H "Authorization: Bearer $API_KEY"/reports/insights/{key}Mark a conclusion
Marks one conclusion for the key's creator only, like the ⋯ menu on Raporty: done, snoozed until a day, dismissed (does not apply) or open again. Other members keep seeing it; repeating the same state changes nothing. It changes no organisation data, so the read scope of the reports is enough. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: campaigns:read
Parameters
| Name | Description |
|---|---|
keypathstringRequired | The conclusion's key from GET /reports/insights. |
Request body application/json
| Name | Description |
|---|---|
statestringRequired | done, snoozed, dismissed, or open to bring the conclusion back.Values: open, done, snoozed, dismissed |
untilstring (date) | snoozed only: the day the conclusion comes back, YYYY-MM-DD (tomorrow up to a year; default 7 days from today). |
Responses
200OKResponse fieldsName Description keystringThe conclusion's key. statestringThe state now.Values: open,done,snoozed,dismisseduntilstringThe day a snooze ends, or empty. 400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl -X PUT "https://app.basketly.io/api/v1/reports/insights/abc123" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"state": "open"
}'/reports/productsProduct trajectories
Each product's impressions, clicks and spend in Meta catalogue ads next to its units and revenue from shop orders, day by day (week by week above 31 days), with trends, week-over-week change, pattern and conclusions — every product with impressions, units or views in the period (up to 5000), searched, filtered, sorted and paged on the server (total, listed, matched, capped). Clicks and sales are side by side, not attributed. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: campaigns:read
Parameters
| Name | Description |
|---|---|
fromquerystring | First day YYYY-MM-DD (default: 30 days ago). |
toquerystring | Last day YYYY-MM-DD (up to 366 days). |
bucketquerystring | day or week (default: day up to 31 days, week above).Values: day, week |
qquerystring | Text in the product's title or catalogue id; searches every product of the period, not only the first page. |
patternquerystring | Only these patterns (repeat or separate with commas); patterns in the answer still counts every product.Values: clicks_up_sales_flat, sales_rising, interest_without_sales, both_rising, sales_without_ads, sales_falling, both_falling, clicks_falling, steady, thin |
sortquerystring | Sort order (add _asc for ascending).Values: impressions, clicks, ctr, spend, units, revenue, clicks_trend, units_trend, name, impressions_asc, clicks_asc, ctr_asc, spend_asc, units_asc, revenue_asc, clicks_trend_asc, units_trend_asc, name_ascDefault: impressions |
pagequerystring | Page from 1. |
per_pagequerystring | Products per page, 1–500.Default: 150 |
Responses
200OK400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/reports/products" \
-H "Authorization: Bearer $API_KEY"/reports/products/detailProduct trajectory
One product's trajectory, every day of the period, with its conclusions. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: campaigns:read
Parameters
| Name | Description |
|---|---|
idquerystringRequired | Catalogue product id (content_id). |
fromquerystring | First day YYYY-MM-DD (default: 30 days ago). |
toquerystring | Last day YYYY-MM-DD (up to 366 days). |
Responses
200OK400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).404No such record in your organisation (not_found).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/reports/products/detail" \
-H "Authorization: Bearer $API_KEY"/reports/magnetsProduct magnets
Products people look at or click but rarely buy while the same people buy other products — from linked visits of the shop script and from orders of catalogue ads. Co-occurrence, not proof of cause. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: campaigns:read
Parameters
| Name | Description |
|---|---|
fromquerystring | First day YYYY-MM-DD (default: 30 days ago). |
toquerystring | Last day YYYY-MM-DD (up to 366 days). |
Responses
200OK400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/reports/magnets" \
-H "Authorization: Bearer $API_KEY"/reports/unseenProducts not shown
Products in the catalogue (a Basketly feed Meta reads, or every active product without one), available and without Meta errors, that Meta catalogue ads did not show in the period — with the reason, days in the feed, shop demand and stock value. One page of the list; summary and conclusions cover the whole catalogue. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: campaigns:read
Parameters
| Name | Description |
|---|---|
fromquerystring | First day YYYY-MM-DD (default: 30 days ago). |
toquerystring | Last day YYYY-MM-DD (up to 366 days). |
reasonquerystring | Comma list: not_in_feed, excluded_by_rule, out_of_stock, meta_error_check, meta_rejected, meta_pending, pending_sync, not_in_active_set, not_picked. |
exposurequerystring | Comma list of none, low, shown (default none,low). |
eligiblequerystring | yes or no. |
feedquerystring | Feed id: products served or left out by it. |
categoryquerystring | Shop category, exact (repeatable). |
brandquerystring | Brand, exact (repeatable). |
availabilityquerystring | Comma list: in_stock, out_of_stock, preorder, backorder, unknown. |
salesquerystring | yes or no: units sold in the period. |
viewsquerystring | yes or no: shop views in the period. |
qquerystring | Title, SKU or catalogue id. |
sortquerystring | stock_value (default), units, views, revenue, impressions, days_in_feed, last_impression, title; _asc reverses. |
pagequerystring | Page, from 1. |
per_pagequerystring | 10, 25 (default), 50 or 100. |
Responses
200OK400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/reports/unseen" \
-H "Authorization: Bearer $API_KEY"/reports/stock-adsSold out but shown
Products with impressions and spend on days the shop said out of stock (from the availability changes of the nightly product reads), the catalogue's lag, and products back in stock for at least 3 days without an impression since. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: campaigns:read
Parameters
| Name | Description |
|---|---|
fromquerystring | First day YYYY-MM-DD (default: 30 days ago). |
toquerystring | Last day YYYY-MM-DD (up to 366 days). |
Responses
200OK400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/reports/stock-ads" \
-H "Authorization: Bearer $API_KEY"/reports/concentrationDelivery concentration
How unevenly Meta spreads catalogue-ad impressions over the products of a scope (all, ad account, campaign, ad set or ad): rank table, top shares, Gini, HHI and effective products, weekly series, before/after sell-outs of top products and conclusions. Meta purchases only for the whole scope, never per product. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: campaigns:read
Parameters
| Name | Description |
|---|---|
scopequerystring | all (default), account, campaign, adset or ad.Values: all, account, campaign, adset, ad |
idquerystring | Id of the ad account (act_…), campaign, ad set or ad; required unless scope=all. The answer's options list them. |
fromquerystring | First day YYYY-MM-DD (default: 30 days ago). |
toquerystring | Last day YYYY-MM-DD (up to 366 days). |
Responses
200OK400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/reports/concentration" \
-H "Authorization: Bearer $API_KEY"/reports/stockStock signals
Which products run out before a restock can arrive: velocity of the last 7/14/28 days, days of cover, stockout date, reorder point, suggested quantity, level against the lead time and demand from catalogue ads and the shop script. Stock is the shop's number at the last read, plus Basketly's labelled estimate; the forecast is linear. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: campaigns:read
Parameters
| Name | Description |
|---|---|
fromquerystring | First day YYYY-MM-DD (default: 30 days ago). |
toquerystring | Last day YYYY-MM-DD (up to 366 days). |
levelquerystring | Comma list: sold_out, critical, warning, watch, ok, overstock, thin. |
risingquerystring | 1 = only products with rising clicks or carts. |
in_adsquerystring | 1 = only products with impressions in the last 7 days. |
brandquerystring | Only this brand. |
categoryquerystring | Category contains this text. |
sourcequerystring | Shop kind or feed. |
lead_timequerystring | What-if lead time in days (1–180); default: the organisation's setting. |
Responses
200OK400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/reports/stock" \
-H "Authorization: Bearer $API_KEY"/reports/feed-ordersOrders from tagged feeds
Orders whose visit landed on a tagged catalogue link, per feed and per landing product, with orders per 100 clicks of the feed's products. Basketly's own reading from the shop script (needs link tagging and the script), not Meta's purchases. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: campaigns:read
Parameters
| Name | Description |
|---|---|
fromquerystring | First day YYYY-MM-DD (default: 30 days ago). |
toquerystring | Last day YYYY-MM-DD (up to 366 days). |
Responses
200OK400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/reports/feed-orders" \
-H "Authorization: Bearer $API_KEY"/reports/product-setsProduct set health
Per product set of the chosen Meta catalogues: product count, share in stock, approved, rejected and pending, spend of the ad sets promoting it and warnings; plus Meta's catalogue diagnostics. Needs the optional catalogue connection (catalog_management); without it connected is false and the lists are empty. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: campaigns:read
Parameters
| Name | Description |
|---|---|
fromquerystring | First day YYYY-MM-DD (default: 30 days ago). |
toquerystring | Last day YYYY-MM-DD (up to 366 days). |
Responses
200OK400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/reports/product-sets" \
-H "Authorization: Bearer $API_KEY"/meta-catalogMeta catalogue connection
Whether the organisation connected a Meta catalogue (optional), which catalogues it chose and how their last read went. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: campaigns:read
Responses
200OK400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/meta-catalog" \
-H "Authorization: Bearer $API_KEY"/campaigns/productsProducts in catalogue ads
Per-product results of a campaign, ad group or ad from the platform's per-product report (Meta by default; Google Ads, Microsoft Ads, TikTok with platform), with the product's shop sales. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: campaigns:read
Parameters
| Name | Description |
|---|---|
fromquerystring | First day YYYY-MM-DD (default: 30 days ago). |
toquerystring | Last day YYYY-MM-DD. |
platformquerystring | meta (default), google_ads, microsoft_ads or tiktok. |
campaignquerystring | Only this campaign. |
adgroupquerystring | Only this ad group (Google Ads, Microsoft Ads). |
adquerystring | Only this ad. |
Responses
200OK400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/campaigns/products" \
-H "Authorization: Bearer $API_KEY"Automations
The organisation's automations and their state.
/automationsList automations
The organisation's automations: name, on/off, trigger type, version, author, last run, failed steps of the last 24 hours, runs, sends and failed steps of the last 30 days, runs in progress and what each can send. Editing and switching stay in the panel. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: automations:read
Responses
200OK400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/automations" \
-H "Authorization: Bearer $API_KEY"Self-check
The organisation's self-check.
/diagnosticsLast self-check
The last self-check run with every check. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: diagnostics:read
Responses
200OK400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/diagnostics" \
-H "Authorization: Bearer $API_KEY"/diagnostics/runRun the self-check
Like „Sprawdź teraz”: queues a run (or starts a waiting one at once). Read the result with GET /diagnostics. Runs as the member who created the key: their role applies, scope=mine means their own work.
Scope: diagnostics:write
Parameters
| Name | Description |
|---|---|
Idempotency-Keyheaderstring | Unique key (1–255 visible ASCII characters). Repeating a POST with the same key and body within 24 hours returns the stored response with Idempotent-Replayed: true.Max length: 255 |
Responses
202QueuedResponse fieldsName Description jobobjectThe job (number, status). 400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).409Conflicts with the current state (conflict,contact_exists,contact_merged,event_conflict,source_paused,idempotency_in_progress).422Idempotency-Key reused with a different body (idempotency_key_reused).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl -X POST "https://app.basketly.io/api/v1/diagnostics/run" \
-H "Authorization: Bearer $API_KEY" \
-H "Idempotency-Key: 5f0c7a2e-8d41-4b6a-9e3f-1c2d3e4f5a6b"Background jobs
Background work.
/jobsList background jobs
Order reads, spend syncs, sheet syncs and other background work of the organisation.
Scope: jobs:read
Parameters
| Name | Description |
|---|---|
limitqueryinteger | Page size, 1–100 (default 25).Default: 25Range: 1–100 |
cursorquerystring | next_cursor of the previous page. |
orderquerystring | Sort by updated_at (then id): desc newest first (default) or asc.Values: asc, descDefault: desc |
updated_sincequerystring (date-time) | Only records changed at or after this ISO 8601 time. |
statusquerystring | Job status.Values: queued, running, done, failed, cancelled |
kindquerystring | Job kinds, comma-separated (the kind field, e.g. shoper_orders,feed_fetch). |
Responses
200OKResponse fieldsName Description dataJob[]Requireddata[].numberintegerJob number within the organisation. data[].kindstringJob kind, e.g. ad_spend, shoper_orders, sheets_sync. data[].labelstringDescription. data[].statusstringStatus.Values: queued,running,done,failed,cancelleddata[].prioritystringuserorbackground.data[].triggerstringWhat started it. data[].progressobjectdonecount andtext.data[].attemptsintegerAttempts. data[].errorstringError text of a failed job. data[].summarystringResult summary. data[].run_afterstring (date-time) | nullNot before. data[].started_atstring (date-time) | nullStart. data[].finished_atstring (date-time) | nullEnd. data[].createdstring (date-time)Creation time. data[].updated_atstring (date-time)Last change. next_cursorstring | nullRequiredPass as cursorto get the next page; null on the last page.has_morebooleanRequiredTrue when another page exists. 400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/jobs" \
-H "Authorization: Bearer $API_KEY"/jobs/{number}Get a job
One job by its number.
Scope: jobs:read
Parameters
| Name | Description |
|---|---|
numberpathintegerRequired | Job number. |
Responses
200OKResponse fieldsName Description numberintegerJob number within the organisation. kindstringJob kind, e.g. ad_spend, shoper_orders, sheets_sync. labelstringDescription. statusstringStatus.Values: queued,running,done,failed,cancelledprioritystringuserorbackground.triggerstringWhat started it. progressobjectdonecount andtext.attemptsintegerAttempts. errorstringError text of a failed job. summarystringResult summary. run_afterstring (date-time) | nullNot before. started_atstring (date-time) | nullStart. finished_atstring (date-time) | nullEnd. createdstring (date-time)Creation time. updated_atstring (date-time)Last change. 400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).404No such record in your organisation (not_found).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/jobs/42" \
-H "Authorization: Bearer $API_KEY"Webhooks
Events pushed to your server.
/webhooksList webhooks
Webhook subscriptions of the organisation (up to 10).
Scope: webhooks:read
Responses
200OKResponse fieldsName Description dataWebhook[]Requireddata[].idstringWebhook id. data[].urlstringHTTPS endpoint that receives POSTs. data[].descriptionstringYour note. data[].eventsstring[]Subscribed events.Values: contact.created,delivery.faileddata[].activebooleanFalse pauses deliveries. data[].last_statusintegerHTTP status of the last attempt (0 = no answer). data[].last_errorstringLast error. data[].last_delivery_atstring (date-time) | nullLast attempt. data[].consecutive_failuresintegerFailed attempts in a row. data[].created_atstring (date-time)Creation time. data[].updated_atstring (date-time)Last change. next_cursorstring | nullRequiredPass as cursorto get the next page; null on the last page.has_morebooleanRequiredTrue when another page exists. 400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/webhooks" \
-H "Authorization: Bearer $API_KEY"/webhooksCreate a webhook
Subscribes an HTTPS URL to events. The signing secret is returned only here.
Scope: webhooks:write
Parameters
| Name | Description |
|---|---|
Idempotency-Keyheaderstring | Unique key (1–255 visible ASCII characters). Repeating a POST with the same key and body within 24 hours returns the stored response with Idempotent-Replayed: true.Max length: 255 |
Request body application/json
| Name | Description |
|---|---|
urlstringRequired | HTTPS URL (no credentials, no private or local addresses).Max length: 2000 |
eventsstring[]Required | Events to receive.Values: contact.created, delivery.failed |
descriptionstring | Your note.Max length: 200 |
Responses
201CreatedResponse fieldsName Description idstringWebhook id. urlstringHTTPS endpoint that receives POSTs. descriptionstringYour note. eventsstring[]Subscribed events.Values: contact.created,delivery.failedactivebooleanFalse pauses deliveries. last_statusintegerHTTP status of the last attempt (0 = no answer). last_errorstringLast error. last_delivery_atstring (date-time) | nullLast attempt. consecutive_failuresintegerFailed attempts in a row. created_atstring (date-time)Creation time. updated_atstring (date-time)Last change. secretstringRequiredSigning secret ( whsec_…). Shown only in this response — store it.400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).409Conflicts with the current state (conflict,contact_exists,contact_merged,event_conflict,source_paused,idempotency_in_progress).413Body over 64 KB (payload_too_large).422Idempotency-Key reused with a different body (idempotency_key_reused).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl -X POST "https://app.basketly.io/api/v1/webhooks" \
-H "Authorization: Bearer $API_KEY" \
-H "Idempotency-Key: 5f0c7a2e-8d41-4b6a-9e3f-1c2d3e4f5a6b" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/webhooks",
"events": [
"contact.created",
"delivery.failed"
]
}'/webhooks/{id}Get a webhook
One subscription with its last result.
Scope: webhooks:read
Parameters
| Name | Description |
|---|---|
idpathstringRequired | Record id. |
Responses
200OKResponse fieldsName Description idstringWebhook id. urlstringHTTPS endpoint that receives POSTs. descriptionstringYour note. eventsstring[]Subscribed events.Values: contact.created,delivery.failedactivebooleanFalse pauses deliveries. last_statusintegerHTTP status of the last attempt (0 = no answer). last_errorstringLast error. last_delivery_atstring (date-time) | nullLast attempt. consecutive_failuresintegerFailed attempts in a row. created_atstring (date-time)Creation time. updated_atstring (date-time)Last change. 400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).404No such record in your organisation (not_found).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/webhooks/abc123" \
-H "Authorization: Bearer $API_KEY"/webhooks/{id}Update a webhook
Changes URL, events, description or pauses it; resuming resets the failure count.
Scope: webhooks:write
Parameters
| Name | Description |
|---|---|
idpathstringRequired | Record id. |
Request body application/json
| Name | Description |
|---|---|
urlstring | New URL. |
eventsstring[] | New event list.Values: contact.created, delivery.failed |
descriptionstring | Your note. |
activeboolean | Pause or resume. |
Responses
200OKResponse fieldsName Description idstringWebhook id. urlstringHTTPS endpoint that receives POSTs. descriptionstringYour note. eventsstring[]Subscribed events.Values: contact.created,delivery.failedactivebooleanFalse pauses deliveries. last_statusintegerHTTP status of the last attempt (0 = no answer). last_errorstringLast error. last_delivery_atstring (date-time) | nullLast attempt. consecutive_failuresintegerFailed attempts in a row. created_atstring (date-time)Creation time. updated_atstring (date-time)Last change. 400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).404No such record in your organisation (not_found).413Body over 64 KB (payload_too_large).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl -X PATCH "https://app.basketly.io/api/v1/webhooks/abc123" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/webhooks",
"events": [
"contact.created",
"delivery.failed"
],
"active": false
}'/webhooks/{id}Delete a webhook
Removes the subscription and its queued deliveries.
Scope: webhooks:write
Parameters
| Name | Description |
|---|---|
idpathstringRequired | Record id. |
Responses
204Deleted400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).404No such record in your organisation (not_found).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl -X DELETE "https://app.basketly.io/api/v1/webhooks/abc123" \
-H "Authorization: Bearer $API_KEY"/webhooks/{id}/testSend a test ping
Queues a signed ping event to check your endpoint and signature verification.
Scope: webhooks:write
Parameters
| Name | Description |
|---|---|
idpathstringRequired | Record id. |
Idempotency-Keyheaderstring | Unique key (1–255 visible ASCII characters). Repeating a POST with the same key and body within 24 hours returns the stored response with Idempotent-Replayed: true.Max length: 255 |
Responses
202QueuedResponse fieldsName Description delivery_idstringQueued delivery of a pingevent.400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).404No such record in your organisation (not_found).409Conflicts with the current state (conflict,contact_exists,contact_merged,event_conflict,source_paused,idempotency_in_progress).422Idempotency-Key reused with a different body (idempotency_key_reused).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl -X POST "https://app.basketly.io/api/v1/webhooks/abc123/test" \
-H "Authorization: Bearer $API_KEY" \
-H "Idempotency-Key: 5f0c7a2e-8d41-4b6a-9e3f-1c2d3e4f5a6b"/webhooks/{id}/rotate-secretRotate the signing secret
Replaces the signing secret at once; the new secret is returned only here. Deliveries still queued are signed with the new secret.
Scope: webhooks:write
Parameters
| Name | Description |
|---|---|
idpathstringRequired | Record id. |
Idempotency-Keyheaderstring | Unique key (1–255 visible ASCII characters). Repeating a POST with the same key and body within 24 hours returns the stored response with Idempotent-Replayed: true.Max length: 255 |
Responses
200OKResponse fieldsName Description idstringWebhook id. urlstringHTTPS endpoint that receives POSTs. descriptionstringYour note. eventsstring[]Subscribed events.Values: contact.created,delivery.failedactivebooleanFalse pauses deliveries. last_statusintegerHTTP status of the last attempt (0 = no answer). last_errorstringLast error. last_delivery_atstring (date-time) | nullLast attempt. consecutive_failuresintegerFailed attempts in a row. created_atstring (date-time)Creation time. updated_atstring (date-time)Last change. secretstringRequiredSigning secret ( whsec_…). Shown only in this response — store it.400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).404No such record in your organisation (not_found).409Conflicts with the current state (conflict,contact_exists,contact_merged,event_conflict,source_paused,idempotency_in_progress).422Idempotency-Key reused with a different body (idempotency_key_reused).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl -X POST "https://app.basketly.io/api/v1/webhooks/abc123/rotate-secret" \
-H "Authorization: Bearer $API_KEY" \
-H "Idempotency-Key: 5f0c7a2e-8d41-4b6a-9e3f-1c2d3e4f5a6b"/webhooks/{id}/deliveriesList webhook deliveries
Recent attempts (kept 30 days), newest first.
Scope: webhooks:read
Parameters
| Name | Description |
|---|---|
idpathstringRequired | Record id. |
limitqueryinteger | Page size, 1–100 (default 25).Default: 25Range: 1–100 |
cursorquerystring | next_cursor of the previous page. |
orderquerystring | Sort by updated_at (then id): desc newest first (default) or asc.Values: asc, descDefault: desc |
updated_sincequerystring (date-time) | Only records changed at or after this ISO 8601 time. |
Responses
200OKResponse fieldsName Description dataWebhookDelivery[]Requireddata[].idstringDelivery id (header X-Baskety-Delivery). data[].webhook_idstringWebhook id. data[].eventstringEvent type. data[].event_idstringEvent id ( evt_…, the same for every webhook of one event).data[].statusstringStatus.Values: pending,sending,retry,delivered,failed,cancelleddata[].attemptsintegerAttempts. data[].response_statusintegerHTTP status of the last attempt. data[].last_errorstringLast error. data[].next_attempt_atstring (date-time) | nullNext attempt. data[].delivered_atstring (date-time) | nullDelivered at. data[].created_atstring (date-time)Creation time. data[].updated_atstring (date-time)Last change. next_cursorstring | nullRequiredPass as cursorto get the next page; null on the last page.has_morebooleanRequiredTrue when another page exists. 400Invalid input (validation_error,invalid_json,invalid_cursor).401Missing, unknown, revoked or expired key (unauthorized,key_revoked,key_expired).403The key lacks the scope (insufficient_scope,details.required_scope).404No such record in your organisation (not_found).429Over 120 requests per minute for this key (rate_limited); waitRetry-Afterseconds.
Example
curl "https://app.basketly.io/api/v1/webhooks/abc123/deliveries" \
-H "Authorization: Bearer $API_KEY"Webhooks
A webhook sends a JSON POST to your HTTPS URL when something changes in the organisation. An organisation can have up to 10 webhooks. The signing secret (whsec_…) is returned only in the response that creates the webhook. The same webhooks can be added, tested, paused and removed in the panel, on the API page under Webhooks, with the delivery history of each.
Delivery is at least once: the same event can arrive more than once, so skip any id you have already handled. Payloads carry ids and states only, never names, phones or e-mails; fetch details with GET.
Events
contact.created
A person was added (shop order, manual, API, automation).
{
"id": "evt_9f2c4b1a7d3e5f60a1b2c3d4",
"type": "contact.created",
"created_at": "2026-10-08T10:00:00Z",
"organization_id": "org123",
"data": {
"contact_id": "abc123",
"first_source": "Zamówienie w sklepie"
}
}delivery.failed
A conversion delivery to Meta failed for good.
{
"id": "evt_9f2c4b1a7d3e5f60a1b2c3d4",
"type": "delivery.failed",
"created_at": "2026-10-08T10:00:00Z",
"organization_id": "org123",
"data": {
"delivery_id": "d123",
"event_id": "e123",
"target_id": "t123",
"dataset_id": "123456789",
"event_name": "Purchase",
"mode": "live",
"error": "Połącz konto Meta ponownie lub sprawdź token."
}
}Headers
| Header | Description |
|---|---|
X-Baskety-Signature | sha256= + hex HMAC-SHA256 of {X-Baskety-Timestamp}.{raw body} with the webhook secret. |
X-Baskety-Timestamp | Unix seconds of the attempt; reject values older than 5 minutes. |
X-Baskety-Event | Event type. |
X-Baskety-Delivery | Delivery id (changes per webhook, stays across retries). |
Acknowledgement and retries
- Answer with any 2xx code within 15 seconds. Store the event first and do longer work later.
- Any other answer, or none, is retried: up to 8 attempts in total, after 1 min, 5 min, 30 min, 2 h, 6 h, 12 h and 24 h.
X-Baskety-Deliverystays the same across attempts of one delivery;GET /webhooks/{id}/deliveriesshows the attempt history.
Verifying the signature
- Read the raw request body before anything parses it.
- Reject the request when the timestamp is older than 5 minutes.
- Compute HMAC-SHA256 of
{timestamp}.{raw body}with the secret and compare it with the signature header (sha256=<hex>) in constant time.
import crypto from 'node:crypto'
import http from 'node:http'
const secret = process.env.WEBHOOK_SECRET // whsec_…
function verify(rawBody, timestamp, signature) {
const age = Math.abs(Date.now() / 1000 - Number(timestamp))
if (!timestamp || !(age <= 300)) return false
const expected = 'sha256=' + crypto.createHmac('sha256', secret)
.update(`${timestamp}.`)
.update(rawBody)
.digest('hex')
const a = Buffer.from(String(signature ?? ''))
const b = Buffer.from(expected)
return a.length === b.length && crypto.timingSafeEqual(a, b)
}
http.createServer((req, res) => {
const chunks = []
req.on('data', chunk => chunks.push(chunk))
req.on('end', () => {
const raw = Buffer.concat(chunks) // raw body, before JSON parsing
if (!verify(raw, req.headers['x-baskety-timestamp'], req.headers['x-baskety-signature'])) {
res.writeHead(401).end()
return
}
const event = JSON.parse(raw.toString('utf8'))
// skip an event.id you have already handled, and answer fast
res.writeHead(200).end()
})
}).listen(3000)import hashlib
import hmac
import os
import time
from flask import Flask, abort, request
app = Flask(__name__)
SECRET = os.environ["WEBHOOK_SECRET"].encode() # whsec_…
@app.post("/webhooks")
def webhook():
raw = request.get_data() # raw body, before JSON parsing
timestamp = request.headers.get("X-Baskety-Timestamp", "")
signature = request.headers.get("X-Baskety-Signature", "")
if not timestamp.isdigit() or abs(time.time() - int(timestamp)) > 300:
abort(401)
expected = "sha256=" + hmac.new(SECRET, timestamp.encode() + b"." + raw, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, signature):
abort(401)
event = request.get_json()
# skip an event.id you have already handled, and answer fast
return "", 200<?php
$secret = getenv('WEBHOOK_SECRET'); // whsec_…
$raw = file_get_contents('php://input'); // raw body, before JSON parsing
$timestamp = $_SERVER['HTTP_X_BASKETY_TIMESTAMP'] ?? '';
$signature = $_SERVER['HTTP_X_BASKETY_SIGNATURE'] ?? '';
if (!ctype_digit($timestamp) || abs(time() - (int) $timestamp) > 300) {
http_response_code(401);
exit;
}
$expected = 'sha256=' . hash_hmac('sha256', $timestamp . '.' . $raw, $secret);
if (!hash_equals($expected, $signature)) {
http_response_code(401);
exit;
}
$event = json_decode($raw, true);
// skip an event.id you have already handled, and answer fast
http_response_code(200);Creating and testing
Create a webhook, store the secret from the response, then send a test ping to check your endpoint and signature verification.
curl -X POST "https://app.basketly.io/api/v1/webhooks" \
-H "Authorization: Bearer $API_KEY" \
-H "Idempotency-Key: 5f0c7a2e-8d41-4b6a-9e3f-1c2d3e4f5a6b" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/webhooks",
"events": [
"contact.created",
"delivery.failed"
]
}'curl -X POST "https://app.basketly.io/api/v1/webhooks/abc123/test" \
-H "Authorization: Bearer $API_KEY" \
-H "Idempotency-Key: 5f0c7a2e-8d41-4b6a-9e3f-1c2d3e4f5a6b"Changelog
- 1.3.0 · : Multiple ad channels: GET /channels/report (every channel, or one platform’s campaigns: Meta, Google Ads, Microsoft Ads, TikTok, Pinterest), GET /reports/channels (the Channels table with blended MER and the sum of platform claims), GET /reports/product-channels (products × channels), costs outside ad platforms (GET and POST /manual-spend, PATCH and DELETE /manual-spend/{id}), GET /getting-started (the setup checklist); a `channel` parameter on GET /creatives/report and GET /reports/insights, `platform` and `adgroup` on GET /campaigns/products. Additive changes only.
- 1.2.0 · : Lead features removed: GET /leads, GET /leads/{id}, POST /contacts/{id}/stage, the leads:read and leads:answers scopes, the lead.created and contact.stage_changed webhook events, and the stage, cycle, first ad and owner candidate fields of people. A key holding a removed scope keeps working for its other scopes.
- 1.1.0 · : My day, tasks, statuses, contact history, search with the Klienci filters, team reports, lead response, campaign readiness and self-check; tasks, reports and diagnostics scopes; MCP server for AI agents.
- 1.0.0 · : First public version.