Files
analytics/documentation/sklik.md
T
2026-07-20 08:29:23 +02:00

12 KiB

Sklik (Seznam)

Proxy over the Sklik Drak JSON API (https://api.sklik.cz/drak/json/v5/{method}).

Protocol (verified against seznam/api-examples)

  • HTTP POST to the base URL with the method name appended to the path.
  • Body is a JSON array of positional arguments.
  • client.loginByToken takes the API token and returns {"status":200,"session":"...","statusMessage":"OK"}.
  • Every authenticated method takes the user struct {"session": ...} (optionally "userId") as its first argument, followed by the method's own arguments.
  • Every response is an object with status (HTTP-style), statusMessage, a refreshed session, and method-specific data. 200, 206 and 301 are treated as success.

The proxy performs client.loginByToken per request from X-Sklik-Token and injects the session — callers never handle the session.

Credentials

Header Required Meaning
X-Sklik-Token yes API token from Sklik → account settings → API.
X-Sklik-User-Id no Managed account userId (agency/MCC access).

Kde získat token (návod pro klienta)

  1. Přihlaste se na sklik.cz.
  2. Vpravo nahoře uživatelské jméno → Nastavení.
  3. Sekce Přístup k API DrakZobrazit token.
  4. Token → hlavička X-Sklik-Token.

Nový token zneplatní ten předchozí. Token je vázaný na účet, pod kterým jste přihlášeni. Pro správu cizích účtů (agentura/MCC) použijte X-Sklik-User-Id.

Missing token → 401 missing_credentials. Sklik business errors (invalid token, access denied, bad arguments) are surfaced as upstream_error with the Sklik status and full body.

Endpoints

Method Path Purpose
POST /sklik/login Verify the token. Returns {valid, status, statusMessage} (no session).
GET /sklik/limits api.limits — quotas and the statsDataLimit.
GET /sklik/write-limits Guard rails applied to writes. No credentials needed.
GET /sklik/campaigns campaigns.list, all pages collected.
GET /sklik/groups groups.list, filterable by campaign_ids.
GET /sklik/ads ads.list, filterable by campaign_ids / group_ids.
POST /sklik/campaigns /sklik/groups /sklik/ads Create — always paused.
PUT /sklik/campaigns /sklik/groups /sklik/ads Update by id (partial).
DELETE /sklik/campaigns /sklik/groups /sklik/ads Remove (?ids=1,2,3) — reversible.
POST /sklik/{entity}/restore Restore removed entities (?ids=1,2,3).
POST /sklik/report/{entity} createReport + paged readReport for an entity.
POST /sklik/rpc/{method} Generic authenticated call to any method.

The proxy never returns the Sklik session — it is a credential, and AGENTS.md forbids returning secrets from ordinary endpoints. The session is managed internally and callers have no use for it.

Listing entities

GET /sklik/campaigns, /sklik/groups, /sklik/ads wrap {entity}.list and page through the whole result set (offset/limit, SKLIK_LIST_PAGE_LIMIT rows per page, capped by SKLIK_LIST_MAX_PAGES):

{ "totalCount": 42, "returnedCount": 42, "truncated": false, "campaigns": [ ... ] }

Query parameters:

Parameter Endpoints Meaning
ids all Comma-separated ids of the entity itself.
campaign_ids groups, ads Restrict to these campaigns.
group_ids ads Restrict to these groups.
is_deleted all true/false. Omit to get both.
display_columns all Comma-separated columns; defaults to a useful subset.

Sklik's campaigns.list filter supports only ids and isDeleted — there is no status filter upstream, so filter on status in the returned rows. groups.list and ads.list do support parent filters (campaign.ids, group.ids), which is what campaign_ids / group_ids map to.

Writes — full CRUD over campaigns / groups / ads

Operation Endpoint Upstream Body / params
Create POST /sklik/{entity} {entity}.create JSON array of structs
Update PUT /sklik/{entity} {entity}.update JSON array of structs, id required
Remove DELETE /sklik/{entity}?ids=1,2 {entity}.remove ids in the query
Restore POST /sklik/{entity}/restore?ids=1,2 {entity}.restore ids in the query

{entity}campaigns, groups, ads. The structs are exactly the ones Sklik documents (campaigns.create, campaigns.update, and the groups.* / ads.* equivalents). Sklik batches are all-or-nothing: if one item fails, nothing is applied.

Create — everything is paused, not configurable

Sklik's status field defaults to active, so an omitted status would create a live, spending campaign. The proxy therefore forces status: "suspend" on every created entity and ignores any other value you send (the override is logged).

Update — ordinary CRUD, status is not forced

PUT changes only the fields you send; id is required per item. Unlike create, status is passed through as given — setting suspend is how you pause a running campaign and active is how you resume one, so forcing a value here would break half the use cases.

That does mean update can start spending. If you want activation to stay a manual action in the Sklik UI, set SKLIK_BLOCK_ACTIVATION=true: status: "active" is then refused with 403 while pausing still works. Default is false (both directions allowed). Every activation is logged at WARNING either way.

Ads: changing the creative replaces the ad. Sklik cannot edit an existing ad's headlines, description or URLs — it deletes the old ad and creates a new one, so the ad gets a new id. Re-read the group's ads after such an update. Changing only status keeps the id.

type cannot be changed on a campaign.

Remove and restore — reversible

Sklik's removal is a soft delete: "the campaign is not really removed; it is only marked as removed". Every DELETE therefore has a matching restore endpoint, and removed entities still show up in listings unless you filter with is_deleted=false.

curl -X DELETE ".../sklik/campaigns?ids=123456" -H "X-Sklik-Token: <TOKEN>"
curl -X POST   ".../sklik/campaigns/restore?ids=123456" -H "X-Sklik-Token: <TOKEN>"

Optional guard rails

Both are off by default — the proxy does not second-guess your numbers unless you ask it to:

Guard How to enable Effect
Budget ceilings Set SKLIK_MAX_DAY_BUDGET_HALERS, SKLIK_MAX_TOTAL_BUDGET_HALERS, SKLIK_MAX_CPC_HALERS to a non-zero value A create or update above the ceiling is rejected with 400 before Sklik is called.
Idempotency Send an X-Idempotency-Key header A retry with the same key returns the original result instead of repeating the write. Works on every write endpoint.
No activation via API Set SKLIK_BLOCK_ACTIVATION=true PUT refuses status: "active" with 403; pausing still works.

Amounts are in halers (100 halers = 1 Kč), matching the Sklik API — a ceiling mainly protects against a misplaced decimal point. GET /sklik/write-limits reports what is currently enforced (null = no limit).

Basic shape validation always applies (required fields present, money fields integer and non-negative), so you get a clear message instead of a generic upstream rejection.

Idempotency

Recommended for writes: if a create times out on the network you cannot tell whether the campaign was created, and a blind retry creates a second one.

  • Send X-Idempotency-Key: <unique string per logical operation> (e.g. a UUID).
  • A retry with the same key returns the stored result plus "idempotentReplay": true — Sklik is not called again.
  • A failed create releases the key, so you can retry it.
  • A concurrent duplicate (same key still in flight) gets 409.

Limitations, stated plainly: the store is in-memory and per-container. It does not survive a restart and is not shared between replicas, so with more than one container a retry can land somewhere that has never seen the key. It removes the common failure (an immediate retry after a timeout); it is not a distributed guarantee. Moving it to Redis should be a conscious decision, not a surprise.

Example — create a paused campaign

curl -X POST "https://services.csbot.cz/apps/analytics/sklik/campaigns" \
  -H "X-Sklik-Token: <TOKEN>" \
  -H "X-Idempotency-Key: 8f3a1c02-0f1e-4c3a-9d6b-2b7e5f0a1c44" \
  -H "Content-Type: application/json" \
  -d '[{"name":"Léto 2026","type":"fulltext","dayBudget":20000,"totalBudget":200000}]'

dayBudget: 20000 = 200 Kč/day. Response:

{
  "status": 200,
  "statusMessage": "OK",
  "campaignIds": [123456],
  "createdCount": 1,
  "createdStatus": "suspend",
  "idempotentReplay": false,
  "note": "Created paused. Activate manually in the Sklik UI ..."
}

Then a group and an ad in it:

curl -X POST ".../sklik/groups" -H "X-Sklik-Token: <TOKEN>" \
  -H "Content-Type: application/json" \
  -d '[{"campaignId":123456,"name":"Sestava A","cpc":300}]'

curl -X POST ".../sklik/ads" -H "X-Sklik-Token: <TOKEN>" \
  -H "Content-Type: application/json" \
  -d '[{"groupId":654321,"adType":"eta","headline1":"Nadpis jedna","headline2":"Nadpis dva","description":"Popis inzerátu.","finalUrl":"https://example.com/"}]'

Report helper

entitycampaigns, groups, ads, keywords, queries, sitelinks, productSets, banners. Body = the arguments for {entity}.createReport (restriction filter + optional display options). The proxy creates the report then pages through {entity}.readReport (100 rows/page) and returns:

{ "reportId": "...", "totalCount": 1234, "returnedCount": 1234, "truncated": false, "report": [ ... ] }

Example body for POST /sklik/report/campaigns:

[
  { "dateFrom": "2026-06-01", "dateTo": "2026-06-18", "statGranularity": "daily" },
  { "statGranularity": "daily" }
]

Generic RPC

POST /sklik/rpc/{method} with a JSON-array body of the arguments after the session struct (which the proxy injects). Reaches any method, including mutating ones — that is the long-standing behaviour and it is unchanged.

Calls made this way bypass the typed write endpoints' guard rails: nothing forces status: "suspend", no budget ceiling applies and there is no idempotency. For creating campaigns prefer POST /sklik/campaigns. An operator who wants to enforce that can set SKLIK_RPC_ALLOW_MUTATIONS=false, which makes this endpoint refuse .create / .update / .remove / .delete / .restore / .setStatus with 403. Default is true (everything allowed).

Examples:

# List campaigns
curl -X POST ".../apps/analytics/sklik/rpc/campaigns.list" \
  -H "X-Sklik-Token: <TOKEN>" -H "Content-Type: application/json" \
  -d '[{"statuses":["active"]}, {"displayColumns":["id","name","status"]}]'

# Account info
curl -X POST ".../apps/analytics/sklik/rpc/client.get" \
  -H "X-Sklik-Token: <TOKEN>" -H "Content-Type: application/json" -d '[]'

client.loginByToken cannot be called via /sklik/rpc — the proxy manages the session (returns 400).

Method reference

Full method list: https://api.sklik.cz/drak/. Common ones: client.get, api.limits, campaigns.list, groups.list, ads.list, keywords.list, *.createReport / *.readReport.