Skip to content
For developers

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
On this page

Quick start

  1. 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.
  2. Every URL starts with https://app.basketly.io/api/v1. Responses are JSON.
  3. Check the key with GET /me. It needs no scope and returns the organisation, the key name, its scopes and expiry.
curl
curl -H "Authorization: Bearer bsk_live_…" \
  https://app.basketly.io/api/v1/me

The 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.

ScopeAllowsEndpoints
contacts:readRead people of the Klienci (Clients) page (names, phones, e-mails — personal data).
contacts:writeCreate and change people: details, notes, tags, status, SMS block.
events:readRead conversions and the status of their deliveries to Meta.
events:writeSend server-to-server conversions to the queue.
tasks:readOpen tasks (personal data).
tasks:writeCreate, complete and postpone tasks.
campaigns:readCampaign report.
creatives:readCreative report.
automations:readAutomations with their last run and errors of the last day.
integrations:readThe integrations overview: states and key-based connections, without keys.
orders:readShop orders with lines and buyers (personal data: buyer names).
products:readProducts, their diagnosis and the product record.
products:writeManual fixes of products.
feeds:readFeeds, rules, flows, Google categories and previews.
feeds:writeCreate and change feeds, save rules, flows and categories, download now.
templates:readImage templates.
diagnostics:readThe last self-check.
diagnostics:writeRun the self-check.
jobs:readRead background jobs.
webhooks:readRead webhooks and their delivery history.
webhooks:writeCreate, 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.

JSON
{
  "error": {
    "code": "insufficient_scope",
    "message": "The API key lacks the contacts:write scope.",
    "details": {
      "required_scope": "contacts:write"
    }
  }
}
CodeHTTPMeaning
unauthorized401Missing key, wrong format or unknown key.
key_revoked401The key was revoked.
key_expired401The key is past its expiry date.
insufficient_scope403The key lacks the required scope (details.required_scope).
plan_required402The organisation’s plan does not include the API: it needs Growth or higher after the 5-day trial.
rate_limited429Over 120 requests per minute for this key; wait Retry-After seconds.
invalid_json400The body is not valid JSON or has an unknown field.
validation_error400A field or parameter has an invalid value; the message says what to fix.
invalid_cursor400cursor is not from the previous page of the same list.
not_found404No such record in your organisation.
route_not_found404No such endpoint.
conflict409The operation conflicts with the record’s current state.
contact_exists409A person with this phone or e-mail already exists (details.contact_id).
contact_merged409The card was merged into another one (details.merged_into).
event_conflict409The event conflicts with one already accepted.
source_paused409The source is paused.
limit_reached409A limit was reached, e.g. 10 webhooks per organisation.
idempotency_key_reused422The same Idempotency-Key with a different request body.
idempotency_in_progress409A request with this Idempotency-Key is still running; retry shortly.
payload_too_large413Request body over 64 KB.
internal_error500An 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

curl
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')
done
JavaScript
const 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)
Python
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
<?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-Remaining and X-RateLimit-Reset.
  • Over the limit you get 429 rate_limited with a Retry-After header (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 POST accepts an Idempotency-Key header (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 get 409 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.

GET/me

Describe 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 fields
    NameDescription
    organizationobject
    organization.idstring
    organization.namestring
    keyobject
    key.idstring
    key.namestring
    key.prefixstring
    key.scopesstring[]
    key.expires_atstring (date-time) | null
    rate_limitobject
    rate_limit.per_minuteinteger
  • 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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/me" \
  -H "Authorization: Bearer $API_KEY"
JavaScript
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)
Python
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
<?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.

GET/contacts

List contacts

People of the Klienci page (merged cards are left out). Filter by tag or exact e-mail or phone.

Scope: contacts:read

Parameters
NameDescription
limitqueryintegerPage size, 1–100 (default 25).Default: 25Range: 1–100
cursorquerystringnext_cursor of the previous page.
orderquerystringSort 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.
tagquerystringTag.
emailquerystringExact e-mail (matched by hash).
phonequerystringExact phone (matched by hash).
Responses
  • 200OKResponse fields
    NameDescription
    dataContact[]Required
    data[].idstring
    Contact id.
    data[].namestring
    Full name.
    data[].phonestring
    Phone in E.164 form, empty when unknown.
    data[].emailstring
    E-mail, empty when unknown.
    data[].tagsstring[]
    Tags.
    data[].sms_optoutboolean
    True when SMS to this person are blocked.
    data[].first_sourcestring
    Where the person first came from ("Zamówienie w sklepie", "Ręcznie", API…).
    data[].status_idstring | null
    Internal status id (GET /contact-statuses), null when none. Never sent to Meta.
    data[].owner_idstring | null
    User id of the person's owner (opiekun).
    data[].last_activity_atstring (date-time) | null
    Last activity time.
    data[].created_atstring (date-time)
    Creation time.
    data[].updated_atstring (date-time)
    Last change; use with updated_since.
    data[].ordersobject
    What the person bought in the shop.
    data[].orders.countinteger
    Bought, not cancelled shop orders.
    data[].orders.valuenumber
    Their value in currency.
    data[].orders.currencystring
    The currency most of the orders use.
    data[].orders.first_atstring (date-time) | null
    First order time.
    data[].orders.last_atstring (date-time) | null
    Last order time.
    next_cursorstring | nullRequired
    Pass as cursor to get the next page; null on the last page.
    has_morebooleanRequired
    True 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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/contacts" \
  -H "Authorization: Bearer $API_KEY"
JavaScript
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)
Python
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
<?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);
POST/contacts

Create a contact

Adds a person like „Dodaj osobę” in the panel.

Scope: contacts:write

Parameters
NameDescription
Idempotency-KeyheaderstringUnique 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
NameDescription
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 fields
    NameDescription
    idstring
    Contact id.
    namestring
    Full name.
    phonestring
    Phone in E.164 form, empty when unknown.
    emailstring
    E-mail, empty when unknown.
    tagsstring[]
    Tags.
    sms_optoutboolean
    True when SMS to this person are blocked.
    first_sourcestring
    Where the person first came from ("Zamówienie w sklepie", "Ręcznie", API…).
    status_idstring | null
    Internal status id (GET /contact-statuses), null when none. Never sent to Meta.
    owner_idstring | null
    User id of the person's owner (opiekun).
    last_activity_atstring (date-time) | null
    Last activity time.
    created_atstring (date-time)
    Creation time.
    updated_atstring (date-time)
    Last change; use with updated_since.
    ordersobject
    What the person bought in the shop.
    orders.countinteger
    Bought, not cancelled shop orders.
    orders.valuenumber
    Their value in currency.
    orders.currencystring
    The currency most of the orders use.
    orders.first_atstring (date-time) | null
    First order time.
    orders.last_atstring (date-time) | null
    Last 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); wait Retry-After seconds.
Example
curl
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"
    ]
  }'
JavaScript
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)
Python
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
<?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);
GET/contacts/{id}

Get a contact

A merged card answers 409 contact_merged with details.merged_into.

Scope: contacts:read

Parameters
NameDescription
idpathstringRequiredRecord id.
Responses
  • 200OKResponse fields
    NameDescription
    idstring
    Contact id.
    namestring
    Full name.
    phonestring
    Phone in E.164 form, empty when unknown.
    emailstring
    E-mail, empty when unknown.
    tagsstring[]
    Tags.
    sms_optoutboolean
    True when SMS to this person are blocked.
    first_sourcestring
    Where the person first came from ("Zamówienie w sklepie", "Ręcznie", API…).
    status_idstring | null
    Internal status id (GET /contact-statuses), null when none. Never sent to Meta.
    owner_idstring | null
    User id of the person's owner (opiekun).
    last_activity_atstring (date-time) | null
    Last activity time.
    created_atstring (date-time)
    Creation time.
    updated_atstring (date-time)
    Last change; use with updated_since.
    ordersobject
    What the person bought in the shop.
    orders.countinteger
    Bought, not cancelled shop orders.
    orders.valuenumber
    Their value in currency.
    orders.currencystring
    The currency most of the orders use.
    orders.first_atstring (date-time) | null
    First order time.
    orders.last_atstring (date-time) | null
    Last 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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/contacts/abc123" \
  -H "Authorization: Bearer $API_KEY"
PATCH/contacts/{id}

Update a contact

Changes name, phone, e-mail or tags; every change is a history line.

Scope: contacts:write

Parameters
NameDescription
idpathstringRequiredRecord id.
Request body application/json
NameDescription
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 fields
    NameDescription
    idstring
    Contact id.
    namestring
    Full name.
    phonestring
    Phone in E.164 form, empty when unknown.
    emailstring
    E-mail, empty when unknown.
    tagsstring[]
    Tags.
    sms_optoutboolean
    True when SMS to this person are blocked.
    first_sourcestring
    Where the person first came from ("Zamówienie w sklepie", "Ręcznie", API…).
    status_idstring | null
    Internal status id (GET /contact-statuses), null when none. Never sent to Meta.
    owner_idstring | null
    User id of the person's owner (opiekun).
    last_activity_atstring (date-time) | null
    Last activity time.
    created_atstring (date-time)
    Creation time.
    updated_atstring (date-time)
    Last change; use with updated_since.
    ordersobject
    What the person bought in the shop.
    orders.countinteger
    Bought, not cancelled shop orders.
    orders.valuenumber
    Their value in currency.
    orders.currencystring
    The currency most of the orders use.
    orders.first_atstring (date-time) | null
    First order time.
    orders.last_atstring (date-time) | null
    Last 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); wait Retry-After seconds.
Example
curl
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"
    ]
  }'
POST/contacts/{id}/notes

Add a note

Adds a note, call or meeting to the person's history.

Scope: contacts:write

Parameters
NameDescription
idpathstringRequiredRecord id.
Idempotency-KeyheaderstringUnique 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
NameDescription
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 fields
    NameDescription
    idstring
    History entry id.
    contact_idstring
    Contact id.
    kindstring
    Entry kind.
    textstring
    Text.
    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); wait Retry-After seconds.
Example
curl
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."
  }'
POST/contacts/{id}/tags

Add or remove tags

Adds and removes tags without sending the whole list.

Scope: contacts:write

Parameters
NameDescription
idpathstringRequiredRecord id.
Idempotency-KeyheaderstringUnique 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
NameDescription
addstring[]
Tags to add.
removestring[]
Tags to remove.
Responses
  • 200OKResponse fields
    NameDescription
    idstring
    Contact id.
    namestring
    Full name.
    phonestring
    Phone in E.164 form, empty when unknown.
    emailstring
    E-mail, empty when unknown.
    tagsstring[]
    Tags.
    sms_optoutboolean
    True when SMS to this person are blocked.
    first_sourcestring
    Where the person first came from ("Zamówienie w sklepie", "Ręcznie", API…).
    status_idstring | null
    Internal status id (GET /contact-statuses), null when none. Never sent to Meta.
    owner_idstring | null
    User id of the person's owner (opiekun).
    last_activity_atstring (date-time) | null
    Last activity time.
    created_atstring (date-time)
    Creation time.
    updated_atstring (date-time)
    Last change; use with updated_since.
    ordersobject
    What the person bought in the shop.
    orders.countinteger
    Bought, not cancelled shop orders.
    orders.valuenumber
    Their value in currency.
    orders.currencystring
    The currency most of the orders use.
    orders.first_atstring (date-time) | null
    First order time.
    orders.last_atstring (date-time) | null
    Last 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); wait Retry-After seconds.
Example
curl
curl -X POST "https://app.basketly.io/api/v1/contacts/abc123/tags" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Idempotency-Key: 5f0c7a2e-8d41-4b6a-9e3f-1c2d3e4f5a6b" \
  -H "Content-Type: application/json" \
  -d '{
    "add": [
      "vip"
    ],
    "remove": [
      "cold"
    ]
  }'
POST/contacts/{id}/sms-optout

Block or allow SMS

Keeps the „nie wysyłaj” list and every person with the same phone in step.

Scope: contacts:write

Parameters
NameDescription
idpathstringRequiredRecord id.
Idempotency-KeyheaderstringUnique 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
NameDescription
sms_optoutbooleanRequired
True blocks SMS to the person's phone (for every person sharing it), false allows them again.
Responses
  • 200OKResponse fields
    NameDescription
    idstring
    Contact id.
    namestring
    Full name.
    phonestring
    Phone in E.164 form, empty when unknown.
    emailstring
    E-mail, empty when unknown.
    tagsstring[]
    Tags.
    sms_optoutboolean
    True when SMS to this person are blocked.
    first_sourcestring
    Where the person first came from ("Zamówienie w sklepie", "Ręcznie", API…).
    status_idstring | null
    Internal status id (GET /contact-statuses), null when none. Never sent to Meta.
    owner_idstring | null
    User id of the person's owner (opiekun).
    last_activity_atstring (date-time) | null
    Last activity time.
    created_atstring (date-time)
    Creation time.
    updated_atstring (date-time)
    Last change; use with updated_since.
    ordersobject
    What the person bought in the shop.
    orders.countinteger
    Bought, not cancelled shop orders.
    orders.valuenumber
    Their value in currency.
    orders.currencystring
    The currency most of the orders use.
    orders.first_atstring (date-time) | null
    First order time.
    orders.last_atstring (date-time) | null
    Last 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); wait Retry-After seconds.
Example
curl
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
  }'
GET/contacts/search

Search 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
NameDescription
qquerystringName, e-mail or phone.
pagequeryintegerPage, 1 = first.
per_pagequeryintegerPeople per page: 10, 25 (default), 50 or 100.
ownerquerystringme, any, none or member ids.
statusquerystringStatus ids or none.
tagquerystringTag.
sourcequerystringFirst source name.
buyerquerystringyes = has a bought shop order, no = none.Values: yes, no
orders_minquerystringAt least this many bought orders.
orders_maxquerystringAt most this many bought orders.
spent_minquerystringSpent at least.
spent_maxquerystringSpent at most.
last_orderquerystringLast purchase within.Values: 30d, 90d, 180d, 365d
inactive_buyerquerystringBought before but not in the last N days.Values: 30, 60, 90, 180, 365
first_orderquerystringFirst purchase within.Values: 30d, 90d
productquerystringBought this catalogue id (repeatable).
createdquerystringtoday, 7d, 30d or 90d (or created_from / created_to days).
activityquerystringLast activity: today, 7d, 30d, 90d (or activity_from / activity_to).
inactive_daysqueryintegerNo activity for at least this many days.
tasksquerystringopen, overdue or none.
phonequerystringyes or no.
emailquerystringyes or no.
optoutquerystringyes or no.
duplicatequerystringyes or no.
sortquerystringactivity, 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 fields
    NameDescription
    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_pageinteger
    People per page.
    totalinteger
    All matching people.
    pageinteger
    Page 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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/contacts/search" \
  -H "Authorization: Bearer $API_KEY"
GET/contact-statuses

List 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 fields
    NameDescription
    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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/contact-statuses" \
  -H "Authorization: Bearer $API_KEY"
POST/contacts/{id}/status

Set 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
NameDescription
idpathstringRequiredRecord id.
Idempotency-KeyheaderstringUnique 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
NameDescription
statusstringRequired
Status id, or "" to clear.
Responses
  • 200OKResponse fields
    NameDescription
    statusstring
    Status id ("" = none).
    ownerstring
    Owner 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); wait Retry-After seconds.
Example
curl
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"
  }'
GET/contacts/{id}/history

Contact 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
NameDescription
idpathstringRequiredRecord id.
pagequeryintegerPage, 1 = newest.
Responses
  • 200OKResponse fields
    NameDescription
    dataobject[]
    History lines: kind, time, actor, summary, details.
    pageinteger
    Page number (?page=).
    moreboolean
    An 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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/contacts/abc123/history" \
  -H "Authorization: Bearer $API_KEY"
GET/segments

List 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
  • 200OK
  • 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); wait Retry-After seconds.
Example
curl
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.

GET/events

List events

Conversions with their deliveries; payloads are never returned.

Scope: events:read

Parameters
NameDescription
limitqueryintegerPage size, 1–100 (default 25).Default: 25Range: 1–100
cursorquerystringnext_cursor of the previous page.
orderquerystringSort 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.
sourcequerystringSource id.
statusquerystringYour status.
external_idquerystringYour record id.
Responses
  • 200OKResponse fields
    NameDescription
    dataEvent[]Required
    data[].idstring
    Event id.
    data[].source_idstring
    Source id.
    data[].external_idstring
    Your record id.
    data[].statusstring
    Your status.
    data[].meta_event_idstring
    Meta deduplication id.
    data[].event_timestring (date-time) | null
    Event time.
    data[].valuenumber | null
    Value.
    data[].currencystring
    Currency.
    data[].deliveriesDelivery[]
    Deliveries of this event.
    data[].deliveries[].idstring
    Delivery id.
    data[].deliveries[].event_idstring
    Basketly event id.
    data[].deliveries[].target_idstring
    Meta receiver (dataset connection) id.
    data[].deliveries[].dataset_idstring
    Meta dataset (pixel) id.
    data[].deliveries[].event_namestring
    Meta event name, e.g. Purchase, AddToCart.
    data[].deliveries[].meta_event_idstring
    event_id sent to Meta.
    data[].deliveries[].modestring
    test or live.
    data[].deliveries[].statusstring
    Delivery status.Values: pending, sending, retry, accepted, failed, cancelled
    data[].deliveries[].attemptsinteger
    Attempts so far.
    data[].deliveries[].last_errorstring
    Last error, empty when none.
    data[].deliveries[].accepted_atstring (date-time) | null
    When Meta accepted it (receipt, not proof of attribution).
    data[].deliveries[].next_attempt_atstring (date-time) | null
    Next 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 | nullRequired
    Pass as cursor to get the next page; null on the last page.
    has_morebooleanRequired
    True 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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/events" \
  -H "Authorization: Bearer $API_KEY"
POST/events

Send 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
NameDescription
Idempotency-KeyheaderstringUnique 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
NameDescription
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 fields
    NameDescription
    event_idstring
    Basketly event id.
    deliveries_createdinteger
    New deliveries queued for Meta datasets.
    duplicatesinteger
    Deliveries skipped as duplicates.
    sending_enabledboolean
    False 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); wait Retry-After seconds.
Example
curl
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"
  }'
JavaScript
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)
Python
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
<?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);
GET/events/{id}

Get an event

One conversion with the status of each delivery.

Scope: events:read

Parameters
NameDescription
idpathstringRequiredRecord id.
Responses
  • 200OKResponse fields
    NameDescription
    idstring
    Event id.
    source_idstring
    Source id.
    external_idstring
    Your record id.
    statusstring
    Your status.
    meta_event_idstring
    Meta deduplication id.
    event_timestring (date-time) | null
    Event time.
    valuenumber | null
    Value.
    currencystring
    Currency.
    deliveriesDelivery[]
    Deliveries of this event.
    deliveries[].idstring
    Delivery id.
    deliveries[].event_idstring
    Basketly event id.
    deliveries[].target_idstring
    Meta receiver (dataset connection) id.
    deliveries[].dataset_idstring
    Meta dataset (pixel) id.
    deliveries[].event_namestring
    Meta event name, e.g. Purchase, AddToCart.
    deliveries[].meta_event_idstring
    event_id sent to Meta.
    deliveries[].modestring
    test or live.
    deliveries[].statusstring
    Delivery status.Values: pending, sending, retry, accepted, failed, cancelled
    deliveries[].attemptsinteger
    Attempts so far.
    deliveries[].last_errorstring
    Last error, empty when none.
    deliveries[].accepted_atstring (date-time) | null
    When Meta accepted it (receipt, not proof of attribution).
    deliveries[].next_attempt_atstring (date-time) | null
    Next 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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/events/abc123" \
  -H "Authorization: Bearer $API_KEY"
GET/deliveries

List deliveries

Delivery status to Meta, e.g. status=failed&updated_since=… to follow failures.

Scope: events:read

Parameters
NameDescription
limitqueryintegerPage size, 1–100 (default 25).Default: 25Range: 1–100
cursorquerystringnext_cursor of the previous page.
orderquerystringSort 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.
statusquerystringDelivery status.Values: pending, sending, retry, accepted, failed, cancelled
eventquerystringEvent id.
targetquerystringReceiver id.
Responses
  • 200OKResponse fields
    NameDescription
    dataDelivery[]Required
    data[].idstring
    Delivery id.
    data[].event_idstring
    Basketly event id.
    data[].target_idstring
    Meta receiver (dataset connection) id.
    data[].dataset_idstring
    Meta dataset (pixel) id.
    data[].event_namestring
    Meta event name, e.g. Purchase, AddToCart.
    data[].meta_event_idstring
    event_id sent to Meta.
    data[].modestring
    test or live.
    data[].statusstring
    Delivery status.Values: pending, sending, retry, accepted, failed, cancelled
    data[].attemptsinteger
    Attempts so far.
    data[].last_errorstring
    Last error, empty when none.
    data[].accepted_atstring (date-time) | null
    When Meta accepted it (receipt, not proof of attribution).
    data[].next_attempt_atstring (date-time) | null
    Next attempt.
    data[].created_atstring (date-time)
    Creation time.
    data[].updated_atstring (date-time)
    Last change.
    next_cursorstring | nullRequired
    Pass as cursor to get the next page; null on the last page.
    has_morebooleanRequired
    True 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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/deliveries" \
  -H "Authorization: Bearer $API_KEY"
GET/conversions/balance

Purchase 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
NameDescription
daysquerystringPeriod: 7, 14, 30 (default) or 90 days.Values: 7, 14, 30, 90
Responses
  • 200OK
  • 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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/conversions/balance" \
  -H "Authorization: Bearer $API_KEY"

Tasks

Tasks.

GET/tasks

List 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
NameDescription
scopequerystringWhose work: mine, user (with user) or all.Values: mine, user, allDefault: mine
userquerystringMember id for scope=user.
Responses
  • 200OKResponse fields
    NameDescription
    dataobject[]
    Tasks by due time.
    countsobject
    Counts per section (overdue, today, later, none).
    truncatedboolean
    More 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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/tasks" \
  -H "Authorization: Bearer $API_KEY"
POST/contacts/{id}/tasks

Create 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
NameDescription
idpathstringRequiredRecord id.
Idempotency-KeyheaderstringUnique 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
NameDescription
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 fields
    NameDescription
    idstringRequired
    New 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); wait Retry-After seconds.
Example
curl
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"
  }'
POST/tasks/{id}/done

Complete 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
NameDescription
idpathstringRequiredRecord id.
Idempotency-KeyheaderstringUnique 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
NameDescription
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
  • 204Done
  • 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); wait Retry-After seconds.
Example
curl
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."
    }
  }'
POST/tasks/{id}/postpone

Postpone 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
NameDescription
idpathstringRequiredRecord id.
Idempotency-KeyheaderstringUnique 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
NameDescription
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
  • 204Moved
  • 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); wait Retry-After seconds.
Example
curl
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.

GET/integrations

List 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
  • 200OK
  • 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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/integrations" \
  -H "Authorization: Bearer $API_KEY"
GET/getting-started

Getting 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
  • 200OK
  • 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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/getting-started" \
  -H "Authorization: Bearer $API_KEY"
GET/map

Connection 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
  • 200OK
  • 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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/map" \
  -H "Authorization: Bearer $API_KEY"

Orders

Shop orders with their lines and buyers.

GET/orders

List 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
NameDescription
fromquerystringFirst day YYYY-MM-DD.
toquerystringLast day YYYY-MM-DD (range up to 366 days).
statusquerystringWhich orders: bought (default), unpaid, cancelled or all; a comma list means any of them.Default: bought
contactquerystringOrders of this person (all dates unless from/to are given).
qquerystringOrder number fragment, or the buyer's full e-mail or phone (matched by hash).
sourcequerystringComma list of shop source ids (facets.sources).
shop_statusquerystringThe shop's own status name; repeat for several (facets.shop_statuses).
status_typequerystringComma list of new, open, closed, cancelled.
paidquerystringThe shop's paid flag.Values: yes, no
total_minquerystringOrder value at least (in the order's currency).
total_maxquerystringOrder value at most.
platformquerystringComma list of ad source platforms: meta, google_ads, microsoft_ads, tiktok, pinterest, openai_ads, email, comparison, other, direct, unknown (unknown includes orders without one).
campaignquerystringCampaign key (a campaign id, or utm:<utm_campaign> — keys come from ad_facets); repeat for several.
ad_sourcequerystringyes = a known ad platform, no = direct or unknown.Values: yes, no
meta_sendquerystringComma list of accepted, failed, waiting, none: what happened to the purchase sent to Meta.
productquerystringA line whose name contains this text, or whose code, EAN or catalogue id equals it.
countryquerystringComma list of delivery country codes.
sortquerystringOrder of the list.Values: placed, placed_asc, total, total_asc, items, items_asc, paid, paid_ascDefault: placed
per_pagequeryintegerOrders per page.Values: 10, 25, 50, 100Default: 50
pagequeryintegerPage, 1 = first.
Responses
  • 200OKResponse fields
    NameDescription
    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_facetsobject
    Orders 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).
    facetsobject
    The 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_sizeinteger
    Orders per page (per_page).
    totalinteger
    All matching orders.
    summaryobject
    Bought 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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/orders" \
  -H "Authorization: Bearer $API_KEY"
GET/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
NameDescription
idpathstringRequiredRecord id.
Responses
  • 200OK
  • 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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/orders/abc123" \
  -H "Authorization: Bearer $API_KEY"

Products

Products with their diagnosis and manual fixes.

GET/products

List 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
NameDescription
feedquerystringOnly products of this feed.
qquerystringTitle, SKU, exact EAN or exact catalogue id.
originquerystringComma list of shop source ids or feed ids the products come from (facets.origins).
categoryquerystringShop category, exact; repeat for several (facets.categories).
brandquerystringBrand, exact; repeat for several (facets.brands).
availabilityquerystringComma list of in_stock, out_of_stock, preorder, backorder, unknown.
price_minquerystringLowest price a shopper pays (the sale price when there is one).
price_maxquerystringHighest price a shopper pays.
score_minqueryintegerLowest quality score 0–100.
score_maxqueryintegerHighest quality score 0–100.
issuequerystringComma list of problem codes; any of them matches.
statusquerystringComma list of worst problems: error, warning, clean.
fixesquerystringComma list: auto (Basketly's automatic fixes repair a problem), manual (a person typed a fix), none.
googlequerystringComma 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).
exposurequerystringComma list: none = not shown in catalogue ads in the period, low = under 100 impressions (not shown included), shown.
salesquerystringyes = bought in the period, no = not bought.Values: yes, no
sortquerystringtitle (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
daysquerystringStatistics period: 7, 14, 30 (default) or 90 days.Values: 7, 14, 30, 90
per_pagequeryintegerProducts per page: 10, 25, 50 (default) or 100.
facetsquerystring1 adds facets: origins, categories, brands, availability, google and fixes with product counts.Values: 1
pagequeryintegerPage, 1 = first.
Responses
  • 200OKResponse fields
    NameDescription
    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).
    totalinteger
    All matching products.
    summaryobject
    Counts of the selection: errors, warnings, average score, problems by code.
    per_pageinteger
    Products per page.
    facetsobject
    With 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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/products" \
  -H "Authorization: Bearer $API_KEY"
GET/products/performance

Catalogue 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
NameDescription
daysquerystringStatistics period: 7, 14, 30 (default) or 90 days.Values: 7, 14, 30, 90
Responses
  • 200OK
  • 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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/products/performance" \
  -H "Authorization: Bearer $API_KEY"
GET/products/{product}/stats

Product 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
NameDescription
productpathstringRequiredProduct record id.
daysquerystringStatistics period: 7, 14, 30 (default) or 90 days.Values: 7, 14, 30, 90
Responses
  • 200OK
  • 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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/products/abc123/stats" \
  -H "Authorization: Bearer $API_KEY"
GET/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
NameDescription
productpathstringRequiredProduct record id.
Responses
  • 200OK
  • 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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/products/abc123" \
  -H "Authorization: Bearer $API_KEY"
PUT/products/{product}/overrides

Set 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
NameDescription
productpathstringRequiredProduct record id.
Request body application/json
NameDescription
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
  • 200OK
  • 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); wait Retry-After seconds.
Example
curl
curl -X PUT "https://app.basketly.io/api/v1/products/abc123/overrides" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "overrides": {}
  }'
GET/guidelines

List 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
  • 200OK
  • 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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/guidelines" \
  -H "Authorization: Bearer $API_KEY"
POST/products/{product}/title-suggestions

Suggest 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
NameDescription
productpathstringRequiredProduct record id.
Responses
  • 200OK
  • 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); wait Retry-After seconds.
  • 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
curl -X POST "https://app.basketly.io/api/v1/products/abc123/title-suggestions" \
  -H "Authorization: Bearer $API_KEY"
GET/meta-catalog/status

Catalogue 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
NameDescription
content_idsquerystringRequiredComma list of content ids (catalogue retailer_id), at most 500.
Responses
  • 200OK
  • 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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/meta-catalog/status" \
  -H "Authorization: Bearer $API_KEY"

Feeds

Product feeds, their rules, flows and Google categories.

POST/products/unseen-flow

Feed 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
NameDescription
Idempotency-KeyheaderstringUnique 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
NameDescription
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
  • 200OK
  • 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); wait Retry-After seconds.
Example
curl
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
  }'
GET/feeds

List 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 fields
    NameDescription
    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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/feeds" \
  -H "Authorization: Bearer $API_KEY"
POST/feeds

Create 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
NameDescription
Idempotency-KeyheaderstringUnique 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
NameDescription
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
  • 200OK
  • 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); wait Retry-After seconds.
Example
curl
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"
  }'
GET/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
NameDescription
idpathstringRequiredRecord id.
Responses
  • 200OK
  • 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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/feeds/abc123" \
  -H "Authorization: Bearer $API_KEY"
PATCH/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
NameDescription
idpathstringRequiredRecord id.
Request body application/json
NameDescription
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
  • 200OK
  • 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); wait Retry-After seconds.
Example
curl
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"
  }'
PUT/feeds/{id}/rules

Save 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
NameDescription
idpathstringRequiredRecord id.
Request body application/json
NameDescription
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
  • 200OK
  • 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); wait Retry-After seconds.
Example
curl
curl -X PUT "https://app.basketly.io/api/v1/feeds/abc123/rules" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "rules": [
      {}
    ]
  }'
PUT/feeds/{id}/flow

Save 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
NameDescription
idpathstringRequiredRecord id.
Request body application/json
NameDescription
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
  • 200OK
  • 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); wait Retry-After seconds.
Example
curl
curl -X PUT "https://app.basketly.io/api/v1/feeds/abc123/flow" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "graph": {}
  }'
POST/feeds/{id}/preview

Preview 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
NameDescription
idpathstringRequiredRecord id.
Request body application/json
NameDescription
rulesobject[]
Rules to try (feeds with rules).
graphobject
A canvas to try (flows).
Responses
  • 200OK
  • 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); wait Retry-After seconds.
Example
curl
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": {}
  }'
POST/feeds/{id}/fetch

Download 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
NameDescription
idpathstringRequiredRecord id.
Idempotency-KeyheaderstringUnique 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
  • 204Queued
  • 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); wait Retry-After seconds.
Example
curl
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"
GET/feeds/{id}/categories

Feed 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
NameDescription
idpathstringRequiredRecord id.
Responses
  • 200OK
  • 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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/feeds/abc123/categories" \
  -H "Authorization: Bearer $API_KEY"
PUT/feeds/{id}/categories

Save 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
NameDescription
idpathstringRequiredRecord id.
Request body application/json
NameDescription
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
  • 200OK
  • 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); wait Retry-After seconds.
Example
curl
curl -X PUT "https://app.basketly.io/api/v1/feeds/abc123/categories" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "categories": [
      {}
    ]
  }'
PATCH/feeds/{id}/categories

Change 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
NameDescription
idpathstringRequiredRecord id.
Request body application/json
NameDescription
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
  • 200OK
  • 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); wait Retry-After seconds.
Example
curl
curl -X PATCH "https://app.basketly.io/api/v1/feeds/abc123/categories" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "changes": [
      {}
    ]
  }'
GET/google-categories

Search 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
NameDescription
qquerystringWords or a category id, up to 100 characters.
Responses
  • 200OK
  • 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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/google-categories" \
  -H "Authorization: Bearer $API_KEY"
GET/google-taxonomy

Google 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
  • 200OK
  • 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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/google-taxonomy" \
  -H "Authorization: Bearer $API_KEY"
POST/google-taxonomy/refresh

Check 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
  • 202OK
  • 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); wait Retry-After seconds.
Example
curl
curl -X POST "https://app.basketly.io/api/v1/google-taxonomy/refresh" \
  -H "Authorization: Bearer $API_KEY"
POST/feeds/{id}/categories/auto

Recalculate 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
NameDescription
idpathstringRequiredRecord id.
Responses
  • 200OK
  • 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); wait Retry-After seconds.
Example
curl
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.

GET/templates

List 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 fields
    NameDescription
    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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/templates" \
  -H "Authorization: Bearer $API_KEY"
GET/templates/performance

Template 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
NameDescription
fromquerystringFirst day YYYY-MM-DD (default: 30 days ago).
toquerystringLast day YYYY-MM-DD.
Responses
  • 200OK
  • 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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/templates/performance" \
  -H "Authorization: Bearer $API_KEY"
GET/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
NameDescription
idpathstringRequiredRecord id.
Responses
  • 200OK
  • 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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/templates/abc123" \
  -H "Authorization: Bearer $API_KEY"

Reports

Campaign and creative numbers.

GET/campaigns/report

Campaign 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
NameDescription
fromquerystringFirst day YYYY-MM-DD (default: 30 days ago).
toquerystringLast day YYYY-MM-DD (range up to 366 days).
levelquerystringRow level.Values: campaign, adset, adDefault: campaign
campaignquerystringOnly this campaign (for adset/ad level).
adsetquerystringOnly this ad set (ad level).
ad_accountquerystringOnly 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 fields
    NameDescription
    fromstring
    First day (organisation time zone).
    tostring
    Last day.
    levelstring
    Row level.Values: campaign, adset, ad
    totalsobject[]
    Totals per currency: current and the previous period of the same length. Without a campaign/adset filter they also carry the shop's bought orders of the period (shop_orders, shop_revenue) and mer = shop revenue / spend. Totals and rows also carry baskety_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: label in Polish — Meta's standard events named in Polish, e.g. „Zdarzenie: Zakup (zbiór …)” — event and, 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”) and metrics: 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). Plus baskety_orders / baskety_revenue from the orders' ad source. Totals carry the same delivery fields. Każdy wiersz podaje też konto reklamowe Mety: ad_account_id i ad_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_error and job while it waits or is read.
    comparisonobject
    How 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 compare holds the current figures to set against previous (current still 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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/campaigns/report" \
  -H "Authorization: Bearer $API_KEY"
GET/creatives/report

Creative report

Metrics per creative group with weekly momentum and the fatigue rule — the numbers of the Kreacje page.

Scope: creatives:read

Parameters
NameDescription
fromquerystringFirst day YYYY-MM-DD.
toquerystringLast day YYYY-MM-DD.
formatquerystringCreative format.
campaignquerystringMeta campaign id.
ad_accountquerystringAd account id.
tagquerystringTag filter dimension:value (hook, creator, offer).
weeksqueryintegerWeeks of momentum, 4–8.
channelquerystringmeta (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
activequerystring1 = only active ads.
Responses
  • 200OKResponse fields
    NameDescription
    fromstring
    First day.
    tostring
    Last 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 (in baskety_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_accounts to identyfikatory kont reklamowych Mety grupy, a ad_account_names ich 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 to totals. Empty for a period of today only (nothing to compare).
    comparisonobject
    How 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 — see comparison).
    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_error and job while 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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/creatives/report" \
  -H "Authorization: Bearer $API_KEY"
GET/reports/sales

Sales 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
NameDescription
fromquerystringFirst day YYYY-MM-DD (default: 30 days ago).
toquerystringLast day YYYY-MM-DD (up to 366 days).
Responses
  • 200OK
  • 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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/reports/sales" \
  -H "Authorization: Bearer $API_KEY"
GET/reports/channels

Channels 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
NameDescription
fromquerystringFirst day YYYY-MM-DD (default: 30 days ago).
toquerystringLast day YYYY-MM-DD (up to 366 days).
Responses
  • 200OK
  • 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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/reports/channels" \
  -H "Authorization: Bearer $API_KEY"
GET/reports/product-channels

Products × 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
NameDescription
fromquerystringFirst day YYYY-MM-DD (default: 30 days ago).
toquerystringLast day YYYY-MM-DD (up to 366 days).
channelquerystringOnly products with figures in this channel; spend and orders sorting read it.Values: meta, google_ads, microsoft_ads, tiktok, pinterest, openai_ads
sortquerystringSort order (add _asc for ascending).Values: spend, orders, best, units, revenue, spend_asc, orders_asc, best_asc, units_asc, revenue_ascDefault: spend
pagequerystringPage from 1.
per_pagequerystringRows per page: 10, 25, 50 or 100.Default: 50
content_idquerystringOne product by its catalogue id.
Responses
  • 200OK
  • 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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/reports/product-channels" \
  -H "Authorization: Bearer $API_KEY"
GET/channels/report

Channel 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
NameDescription
fromquerystringFirst day YYYY-MM-DD (default: 30 days ago).
toquerystringLast day YYYY-MM-DD (up to 366 days).
channelquerystringOne paid channel; empty = every channel's totals.Values: meta, google_ads, microsoft_ads, tiktok, pinterest, openai_ads
levelquerystringRow level.Values: campaign, adgroup, adDefault: campaign
parentquerystringThe campaign of ad groups / the ad group of ads.
Responses
  • 200OK
  • 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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/channels/report" \
  -H "Authorization: Bearer $API_KEY"
GET/manual-spend

Costs 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
  • 200OK
  • 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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/manual-spend" \
  -H "Authorization: Bearer $API_KEY"
POST/manual-spend

Add 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
NameDescription
Idempotency-KeyheaderstringUnique 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
NameDescription
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 fields
    NameDescription
    idstring
    Entry id.
    channelstring
    Kind of cost: comparison (comparison sites), email (e-mail tool), agency, other.Values: comparison, email, agency, other
    namestring
    What the cost is (required for other).
    monthstring
    Month YYYY-MM; the amount is spread evenly over its days.
    amountnumber
    The month's amount.
    currencystring
    ISO currency code.
    notestring
    Note.
    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); wait Retry-After seconds.
Example
curl
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"
  }'
PATCH/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
NameDescription
idpathstringRequiredRecord id.
Request body application/json
NameDescription
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 fields
    NameDescription
    idstring
    Entry id.
    channelstring
    Kind of cost: comparison (comparison sites), email (e-mail tool), agency, other.Values: comparison, email, agency, other
    namestring
    What the cost is (required for other).
    monthstring
    Month YYYY-MM; the amount is spread evenly over its days.
    amountnumber
    The month's amount.
    currencystring
    ISO currency code.
    notestring
    Note.
    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); wait Retry-After seconds.
Example
curl
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"
  }'
DELETE/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
NameDescription
idpathstringRequiredRecord id.
Responses
  • 200OKResponse fields
    NameDescription
    deletedstringRequired
    Id 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); wait Retry-After seconds.
Example
curl
curl -X DELETE "https://app.basketly.io/api/v1/manual-spend/abc123" \
  -H "Authorization: Bearer $API_KEY"
GET/reports/insights

Report 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
NameDescription
fromquerystringFirst day YYYY-MM-DD (default: 30 days ago).
toquerystringLast day YYYY-MM-DD (up to 366 days).
channelquerystringOnly 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
  • 200OK
  • 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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/reports/insights" \
  -H "Authorization: Bearer $API_KEY"
PUT/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
NameDescription
keypathstringRequiredThe conclusion's key from GET /reports/insights.
Request body application/json
NameDescription
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 fields
    NameDescription
    keystring
    The conclusion's key.
    statestring
    The state now.Values: open, done, snoozed, dismissed
    untilstring
    The 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); wait Retry-After seconds.
Example
curl
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"
  }'
GET/reports/products

Product 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
NameDescription
fromquerystringFirst day YYYY-MM-DD (default: 30 days ago).
toquerystringLast day YYYY-MM-DD (up to 366 days).
bucketquerystringday or week (default: day up to 31 days, week above).Values: day, week
qquerystringText in the product's title or catalogue id; searches every product of the period, not only the first page.
patternquerystringOnly 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
sortquerystringSort 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
pagequerystringPage from 1.
per_pagequerystringProducts per page, 1–500.Default: 150
Responses
  • 200OK
  • 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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/reports/products" \
  -H "Authorization: Bearer $API_KEY"
GET/reports/products/detail

Product 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
NameDescription
idquerystringRequiredCatalogue product id (content_id).
fromquerystringFirst day YYYY-MM-DD (default: 30 days ago).
toquerystringLast day YYYY-MM-DD (up to 366 days).
Responses
  • 200OK
  • 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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/reports/products/detail" \
  -H "Authorization: Bearer $API_KEY"
GET/reports/magnets

Product 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
NameDescription
fromquerystringFirst day YYYY-MM-DD (default: 30 days ago).
toquerystringLast day YYYY-MM-DD (up to 366 days).
Responses
  • 200OK
  • 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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/reports/magnets" \
  -H "Authorization: Bearer $API_KEY"
GET/reports/unseen

Products 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
NameDescription
fromquerystringFirst day YYYY-MM-DD (default: 30 days ago).
toquerystringLast day YYYY-MM-DD (up to 366 days).
reasonquerystringComma 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.
exposurequerystringComma list of none, low, shown (default none,low).
eligiblequerystringyes or no.
feedquerystringFeed id: products served or left out by it.
categoryquerystringShop category, exact (repeatable).
brandquerystringBrand, exact (repeatable).
availabilityquerystringComma list: in_stock, out_of_stock, preorder, backorder, unknown.
salesquerystringyes or no: units sold in the period.
viewsquerystringyes or no: shop views in the period.
qquerystringTitle, SKU or catalogue id.
sortquerystringstock_value (default), units, views, revenue, impressions, days_in_feed, last_impression, title; _asc reverses.
pagequerystringPage, from 1.
per_pagequerystring10, 25 (default), 50 or 100.
Responses
  • 200OK
  • 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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/reports/unseen" \
  -H "Authorization: Bearer $API_KEY"
GET/reports/stock-ads

Sold 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
NameDescription
fromquerystringFirst day YYYY-MM-DD (default: 30 days ago).
toquerystringLast day YYYY-MM-DD (up to 366 days).
Responses
  • 200OK
  • 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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/reports/stock-ads" \
  -H "Authorization: Bearer $API_KEY"
GET/reports/concentration

Delivery 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
NameDescription
scopequerystringall (default), account, campaign, adset or ad.Values: all, account, campaign, adset, ad
idquerystringId of the ad account (act_…), campaign, ad set or ad; required unless scope=all. The answer's options list them.
fromquerystringFirst day YYYY-MM-DD (default: 30 days ago).
toquerystringLast day YYYY-MM-DD (up to 366 days).
Responses
  • 200OK
  • 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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/reports/concentration" \
  -H "Authorization: Bearer $API_KEY"
GET/reports/stock

Stock 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
NameDescription
fromquerystringFirst day YYYY-MM-DD (default: 30 days ago).
toquerystringLast day YYYY-MM-DD (up to 366 days).
levelquerystringComma list: sold_out, critical, warning, watch, ok, overstock, thin.
risingquerystring1 = only products with rising clicks or carts.
in_adsquerystring1 = only products with impressions in the last 7 days.
brandquerystringOnly this brand.
categoryquerystringCategory contains this text.
sourcequerystringShop kind or feed.
lead_timequerystringWhat-if lead time in days (1–180); default: the organisation's setting.
Responses
  • 200OK
  • 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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/reports/stock" \
  -H "Authorization: Bearer $API_KEY"
GET/reports/feed-orders

Orders 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
NameDescription
fromquerystringFirst day YYYY-MM-DD (default: 30 days ago).
toquerystringLast day YYYY-MM-DD (up to 366 days).
Responses
  • 200OK
  • 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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/reports/feed-orders" \
  -H "Authorization: Bearer $API_KEY"
GET/reports/product-sets

Product 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
NameDescription
fromquerystringFirst day YYYY-MM-DD (default: 30 days ago).
toquerystringLast day YYYY-MM-DD (up to 366 days).
Responses
  • 200OK
  • 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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/reports/product-sets" \
  -H "Authorization: Bearer $API_KEY"
GET/meta-catalog

Meta 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
  • 200OK
  • 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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/meta-catalog" \
  -H "Authorization: Bearer $API_KEY"
GET/campaigns/products

Products 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
NameDescription
fromquerystringFirst day YYYY-MM-DD (default: 30 days ago).
toquerystringLast day YYYY-MM-DD.
platformquerystringmeta (default), google_ads, microsoft_ads or tiktok.
campaignquerystringOnly this campaign.
adgroupquerystringOnly this ad group (Google Ads, Microsoft Ads).
adquerystringOnly this ad.
Responses
  • 200OK
  • 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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/campaigns/products" \
  -H "Authorization: Bearer $API_KEY"

Automations

The organisation's automations and their state.

GET/automations

List 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
  • 200OK
  • 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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/automations" \
  -H "Authorization: Bearer $API_KEY"

Self-check

The organisation's self-check.

GET/diagnostics

Last 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
  • 200OK
  • 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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/diagnostics" \
  -H "Authorization: Bearer $API_KEY"
POST/diagnostics/run

Run 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
NameDescription
Idempotency-KeyheaderstringUnique 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 fields
    NameDescription
    jobobject
    The 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); wait Retry-After seconds.
Example
curl
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.

GET/jobs

List background jobs

Order reads, spend syncs, sheet syncs and other background work of the organisation.

Scope: jobs:read

Parameters
NameDescription
limitqueryintegerPage size, 1–100 (default 25).Default: 25Range: 1–100
cursorquerystringnext_cursor of the previous page.
orderquerystringSort 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.
statusquerystringJob status.Values: queued, running, done, failed, cancelled
kindquerystringJob kinds, comma-separated (the kind field, e.g. shoper_orders,feed_fetch).
Responses
  • 200OKResponse fields
    NameDescription
    dataJob[]Required
    data[].numberinteger
    Job number within the organisation.
    data[].kindstring
    Job kind, e.g. ad_spend, shoper_orders, sheets_sync.
    data[].labelstring
    Description.
    data[].statusstring
    Status.Values: queued, running, done, failed, cancelled
    data[].prioritystring
    user or background.
    data[].triggerstring
    What started it.
    data[].progressobject
    done count and text.
    data[].attemptsinteger
    Attempts.
    data[].errorstring
    Error text of a failed job.
    data[].summarystring
    Result summary.
    data[].run_afterstring (date-time) | null
    Not before.
    data[].started_atstring (date-time) | null
    Start.
    data[].finished_atstring (date-time) | null
    End.
    data[].createdstring (date-time)
    Creation time.
    data[].updated_atstring (date-time)
    Last change.
    next_cursorstring | nullRequired
    Pass as cursor to get the next page; null on the last page.
    has_morebooleanRequired
    True 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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/jobs" \
  -H "Authorization: Bearer $API_KEY"
GET/jobs/{number}

Get a job

One job by its number.

Scope: jobs:read

Parameters
NameDescription
numberpathintegerRequiredJob number.
Responses
  • 200OKResponse fields
    NameDescription
    numberinteger
    Job number within the organisation.
    kindstring
    Job kind, e.g. ad_spend, shoper_orders, sheets_sync.
    labelstring
    Description.
    statusstring
    Status.Values: queued, running, done, failed, cancelled
    prioritystring
    user or background.
    triggerstring
    What started it.
    progressobject
    done count and text.
    attemptsinteger
    Attempts.
    errorstring
    Error text of a failed job.
    summarystring
    Result summary.
    run_afterstring (date-time) | null
    Not before.
    started_atstring (date-time) | null
    Start.
    finished_atstring (date-time) | null
    End.
    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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/jobs/42" \
  -H "Authorization: Bearer $API_KEY"

Webhooks

Events pushed to your server.

GET/webhooks

List webhooks

Webhook subscriptions of the organisation (up to 10).

Scope: webhooks:read

Responses
  • 200OKResponse fields
    NameDescription
    dataWebhook[]Required
    data[].idstring
    Webhook id.
    data[].urlstring
    HTTPS endpoint that receives POSTs.
    data[].descriptionstring
    Your note.
    data[].eventsstring[]
    Subscribed events.Values: contact.created, delivery.failed
    data[].activeboolean
    False pauses deliveries.
    data[].last_statusinteger
    HTTP status of the last attempt (0 = no answer).
    data[].last_errorstring
    Last error.
    data[].last_delivery_atstring (date-time) | null
    Last attempt.
    data[].consecutive_failuresinteger
    Failed attempts in a row.
    data[].created_atstring (date-time)
    Creation time.
    data[].updated_atstring (date-time)
    Last change.
    next_cursorstring | nullRequired
    Pass as cursor to get the next page; null on the last page.
    has_morebooleanRequired
    True 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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/webhooks" \
  -H "Authorization: Bearer $API_KEY"
POST/webhooks

Create a webhook

Subscribes an HTTPS URL to events. The signing secret is returned only here.

Scope: webhooks:write

Parameters
NameDescription
Idempotency-KeyheaderstringUnique 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
NameDescription
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 fields
    NameDescription
    idstring
    Webhook id.
    urlstring
    HTTPS endpoint that receives POSTs.
    descriptionstring
    Your note.
    eventsstring[]
    Subscribed events.Values: contact.created, delivery.failed
    activeboolean
    False pauses deliveries.
    last_statusinteger
    HTTP status of the last attempt (0 = no answer).
    last_errorstring
    Last error.
    last_delivery_atstring (date-time) | null
    Last attempt.
    consecutive_failuresinteger
    Failed attempts in a row.
    created_atstring (date-time)
    Creation time.
    updated_atstring (date-time)
    Last change.
    secretstringRequired
    Signing 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); wait Retry-After seconds.
Example
curl
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"
    ]
  }'
GET/webhooks/{id}

Get a webhook

One subscription with its last result.

Scope: webhooks:read

Parameters
NameDescription
idpathstringRequiredRecord id.
Responses
  • 200OKResponse fields
    NameDescription
    idstring
    Webhook id.
    urlstring
    HTTPS endpoint that receives POSTs.
    descriptionstring
    Your note.
    eventsstring[]
    Subscribed events.Values: contact.created, delivery.failed
    activeboolean
    False pauses deliveries.
    last_statusinteger
    HTTP status of the last attempt (0 = no answer).
    last_errorstring
    Last error.
    last_delivery_atstring (date-time) | null
    Last attempt.
    consecutive_failuresinteger
    Failed 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); wait Retry-After seconds.
Example
curl
curl "https://app.basketly.io/api/v1/webhooks/abc123" \
  -H "Authorization: Bearer $API_KEY"
PATCH/webhooks/{id}

Update a webhook

Changes URL, events, description or pauses it; resuming resets the failure count.

Scope: webhooks:write

Parameters
NameDescription
idpathstringRequiredRecord id.
Request body application/json
NameDescription
urlstring
New URL.
eventsstring[]
New event list.Values: contact.created, delivery.failed
descriptionstring
Your note.
activeboolean
Pause or resume.
Responses
  • 200OKResponse fields
    NameDescription
    idstring
    Webhook id.
    urlstring
    HTTPS endpoint that receives POSTs.
    descriptionstring
    Your note.
    eventsstring[]
    Subscribed events.Values: contact.created, delivery.failed
    activeboolean
    False pauses deliveries.
    last_statusinteger
    HTTP status of the last attempt (0 = no answer).
    last_errorstring
    Last error.
    last_delivery_atstring (date-time) | null
    Last attempt.
    consecutive_failuresinteger
    Failed 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); wait Retry-After seconds.
Example
curl
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
  }'
DELETE/webhooks/{id}

Delete a webhook

Removes the subscription and its queued deliveries.

Scope: webhooks:write

Parameters
NameDescription
idpathstringRequiredRecord id.
Responses
  • 204Deleted
  • 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); wait Retry-After seconds.
Example
curl
curl -X DELETE "https://app.basketly.io/api/v1/webhooks/abc123" \
  -H "Authorization: Bearer $API_KEY"
POST/webhooks/{id}/test

Send a test ping

Queues a signed ping event to check your endpoint and signature verification.

Scope: webhooks:write

Parameters
NameDescription
idpathstringRequiredRecord id.
Idempotency-KeyheaderstringUnique 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 fields
    NameDescription
    delivery_idstring
    Queued delivery of a ping event.
  • 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); wait Retry-After seconds.
Example
curl
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"
POST/webhooks/{id}/rotate-secret

Rotate 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
NameDescription
idpathstringRequiredRecord id.
Idempotency-KeyheaderstringUnique 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 fields
    NameDescription
    idstring
    Webhook id.
    urlstring
    HTTPS endpoint that receives POSTs.
    descriptionstring
    Your note.
    eventsstring[]
    Subscribed events.Values: contact.created, delivery.failed
    activeboolean
    False pauses deliveries.
    last_statusinteger
    HTTP status of the last attempt (0 = no answer).
    last_errorstring
    Last error.
    last_delivery_atstring (date-time) | null
    Last attempt.
    consecutive_failuresinteger
    Failed attempts in a row.
    created_atstring (date-time)
    Creation time.
    updated_atstring (date-time)
    Last change.
    secretstringRequired
    Signing 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); wait Retry-After seconds.
Example
curl
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"
GET/webhooks/{id}/deliveries

List webhook deliveries

Recent attempts (kept 30 days), newest first.

Scope: webhooks:read

Parameters
NameDescription
idpathstringRequiredRecord id.
limitqueryintegerPage size, 1–100 (default 25).Default: 25Range: 1–100
cursorquerystringnext_cursor of the previous page.
orderquerystringSort 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 fields
    NameDescription
    dataWebhookDelivery[]Required
    data[].idstring
    Delivery id (header X-Baskety-Delivery).
    data[].webhook_idstring
    Webhook id.
    data[].eventstring
    Event type.
    data[].event_idstring
    Event id (evt_…, the same for every webhook of one event).
    data[].statusstring
    Status.Values: pending, sending, retry, delivered, failed, cancelled
    data[].attemptsinteger
    Attempts.
    data[].response_statusinteger
    HTTP status of the last attempt.
    data[].last_errorstring
    Last error.
    data[].next_attempt_atstring (date-time) | null
    Next attempt.
    data[].delivered_atstring (date-time) | null
    Delivered at.
    data[].created_atstring (date-time)
    Creation time.
    data[].updated_atstring (date-time)
    Last change.
    next_cursorstring | nullRequired
    Pass as cursor to get the next page; null on the last page.
    has_morebooleanRequired
    True 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); wait Retry-After seconds.
Example
curl
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).

Example payload · JSON
{
  "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.

Example payload · JSON
{
  "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

HeaderDescription
X-Baskety-Signaturesha256= + hex HMAC-SHA256 of {X-Baskety-Timestamp}.{raw body} with the webhook secret.
X-Baskety-TimestampUnix seconds of the attempt; reject values older than 5 minutes.
X-Baskety-EventEvent type.
X-Baskety-DeliveryDelivery 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-Delivery stays the same across attempts of one delivery; GET /webhooks/{id}/deliveries shows the attempt history.

Verifying the signature

  1. Read the raw request body before anything parses it.
  2. Reject the request when the timestamp is older than 5 minutes.
  3. Compute HMAC-SHA256 of {timestamp}.{raw body} with the secret and compare it with the signature header (sha256=<hex>) in constant time.
Node.js
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)
Python
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
<?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
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
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.