{"openapi":"3.1.0","jsonSchemaDialect":"https://spec.openapis.org/oas/3.1/dialect/base","info":{"title":"Poštárna API","version":"1.0.0","description":"Poštárna is a multi-tenant e-mail marketing platform with statistics for companies and e-shops (Czech and Slovak). This document describes every\nroute an integration can call. Guides in Czech and Slovak: https://www.postarna.cz/docs/api (index for AI agents:\nhttps://www.postarna.cz/llms.txt, everything in one Markdown file: https://www.postarna.cz/llms-full.txt).\n\n## Three APIs\n* **API v1** (`/api/v1/tenants/{tenant}/...`): the full API, the same one the Poštárna web app uses. JSON in UTF-8.\n* **SmartEmailing compatible API v3** (`/api/v3/...`) and its CSV statistics (`/api/csv/...`): the same paths,\n  envelopes and errors as SmartEmailing's REST API v3, so an existing SE integration only changes the address, the\n  user name and the key. Read operations plus account webhooks.\n* **MCP server** (`POST /mcp`): Model Context Protocol for AI assistants (ChatGPT, Claude, Cursor ...), OAuth 2.1 or an\n  API key.\n\n## Authentication\nCreate an API key in the app: Nastavení › Datové zdroje › API klíče (Settings › Data sources › API keys). The key\n(`pst_` + 43 characters) is shown once. Send it as `Authorization: Bearer pst_...`, or as HTTP Basic with the e-mail\nof an active member of the company as the user name and the key as the password (both work on v1 and v3). A key\nworks only in its own company (tenant), with the roles it was given (never more than its creator has), and with\nscope `full` (read and write) or `read` (only routes whose permission level is read). Keys never reach the members,\ninvitations, API keys and connected apps (those need a signed-in person: 403 `forbidden` / `api_key`).\n\n`{tenant}` in v1 paths is the company's public id (a UUID). Find it with `GET /api/v3/account-info` (`data.guid`) or\nin the app on the API keys page.\n\n## Conventions (API v1)\n* IDs are strings. Times are ISO 8601 in UTC with milliseconds (`2026-09-30T08:00:00.000Z`); local calendar days of\n  the company are `YYYY-MM-DD`; automation settings use local `YYYY-MM-DDTHH:MM` and `HH:MM`.\n* Money is a decimal string next to its `currency` (`\"1290.00\"`, `\"CZK\"`). CZK and EUR are never added up or\n  converted: every sum is per currency.\n* Lists are paged: `page` from 1, `pageSize` 1 to 100 (default 25); the answer is `{items, total, page, pageSize}`.\n  Some grids take `sort` (`name,-createdAt`, at most 3 fields, `-` = descending).\n* Request bodies of POST, PUT and PATCH are `application/json` (415 `validation` / `contentType` otherwise); CSV\n  import and file upload also take `multipart/form-data`. Unknown body fields are ignored.\n* Errors: `{\"error\": {\"code\": \"...\", \"message\": \"<field or detail>\", \"params\": {...}}}`. `code` is stable and\n  machine readable, `message` names the field or the reason, `params` carries details (for example the issues of an\n  automation graph). The API never sends texts for people.\n* No idempotency keys: retry only requests that are safe to repeat (GET, PUT of a whole object, DELETE). For POST,\n  read the state first (for example list the contacts by e-mail) before you retry after a timeout. Concurrent\n  edits of content and automation graphs are guarded by versions (`baseVersion`, 409 `conflict` / `version`).\n\n## Limits\nPer company (all keys together): a bucket of 2 500 requests refilled with 4 requests a second, and 100 000 requests a\nday (refilled continuously). Every answer to a key carries `X-RateLimit-Limit`, `X-RateLimit-Remaining` and\n`X-RateLimit-Reset` (unix time when the bucket is full again) of the bucket nearest to running out. Over the limit:\n429 `rate_limited` with `Retry-After` (seconds). Some actions have their own limits (test e-mails 10 per day per\ncampaign or template, contact events 60 per contact in 10 minutes, MCP 300 messages a minute).\n\n## Consent and sending\nPoštárna never e-mails a contact who unsubscribed, bounced, complained or is suppressed, and campaigns only reach\ncontacts with a valid marketing consent. Sending to real recipients (approving a campaign, activating an automation)\nis irreversible: check the audience first (`POST .../campaigns/audience`, `POST .../automations/{id}/test`). Test\ne-mails go only to the company's test addresses (`GET .../test-recipients`).\n","contact":{"name":"deriverge s.r.o.","url":"https://www.postarna.cz/docs/api"},"x-guides":{"cs":"https://www.postarna.cz/docs/api","sk":"https://www.postarna.sk/docs/api","llms":"https://www.postarna.cz/llms.txt","llmsFull":"https://www.postarna.cz/llms-full.txt"}},"servers":[{"url":"https://www.postarna.cz","description":"Production (the same API answers on www.postarna.sk)"}],"security":[{"bearerAuth":[]},{"basicAuth":[]}],"tags":[{"name":"Lists","x-displayName":"Lists","description":"Contact lists with explicit membership (manual and form lists) and the lists bound to an e-shop (read only membership)."}],"x-error-codes":{"conflict":{"meaning":"The change would break the state of the object. `message`: `name` (name taken), `locked` (a list bound to an e-shop), `status` (wrong state, e.g. editing a sent campaign), `recipients` (the audience changed more than 5 % since you counted it, `params.expected` and `params.actual`), `version` (stale `baseVersion`, `params.current`), `confirm` (saving a running automation needs `confirmRunning: true`), `waiting` (contacts wait in a node you remove, `params.nodes`), `runs` (deleting an automation that ran), `trigger` (no manual trigger), `limit` (too many objects).","fix":"Read the object again, then repeat with current data; for `recipients` count the audience again and send the new `expectedRecipients`."},"count_mismatch":{"meaning":"`expectedRecipients` does not match the audience.","fix":"Count again with `POST /campaigns/audience` and send the new number."},"csrf":{"meaning":"Browser session request without the CSRF header (never with an API key).","fix":"Send the API key in `Authorization` instead of cookies."},"empty_segment":{"meaning":"The campaign audience has no recipient.","fix":"Change the audience."},"expired":{"meaning":"The object is no longer available (an export file older than its retention).","fix":"Create it again (POST /exports)."},"forbidden":{"meaning":"The credentials are valid but not allowed here. `message`: `tenant` (another company in the path), `scope` (a read-only key on a write route), `api_key` (members, invitations and keys need a signed-in person), a module name (`contacts`, `campaigns`, `content`, `settings`, `billing`: the key's roles lack the level), `read_only` (support view).","fix":"Use the tenant id of the key (`GET /api/v3/account-info`), a key with scope `full`, or a key whose roles include the module (for example marketing_manager)."},"method_not_allowed":{"meaning":"The path exists but not with this HTTP method.","fix":"Check the method in this document."},"not_found":{"meaning":"The object does not exist in this company (objects of other companies answer the same).","fix":"Check the id; list the objects first."},"not_implemented":{"meaning":"The feature is not available yet.","fix":"Do not rely on it."},"rate_limited":{"meaning":"Too many requests: the company's API limit, or the limit of one action (test e-mails, contact events, sign-in attempts). `Retry-After` says after how many seconds to retry.","fix":"Wait `Retry-After` seconds; spread requests (4 a second sustained)."},"recently_sent":{"meaning":"HTTP 423: the contact cannot be deleted yet, a campaign e-mail to it is queued or went out in the last 24 hours (SmartEmailing answers the same).","fix":"Delete it after 24 hours. A request to be forgotten (GDPR) uses POST /contacts/{id}/erase, which is never refused."},"server":{"meaning":"Unexpected server error.","fix":"Retry later; the request may be repeated if it is safe to repeat."},"suppression_locked":{"meaning":"The address is suppressed for another reason (bounce, complaint, unsubscribe), so the blacklist cannot be added or removed; `params.reason` names it.","fix":"Leave it: such suppressions protect the sender and are removed only one by one (DELETE /suppressions/{email}) when the reason is gone."},"unauthorized":{"meaning":"No valid credentials: the API key is unknown, revoked, expired, idle for 180 days, its creator left the company, or the HTTP Basic user name is not an active member.","fix":"Check the key and the header (`Authorization: Bearer pst_...`). Create a new key in Nastavení › Datové zdroje › API klíče."},"unavailable":{"meaning":"A service the action needs is not available (`mail`: the mail backend refused a test e-mail, `data_key`: encryption key for webhook passwords missing).","fix":"Retry later; tell support when it persists."},"upstream":{"meaning":"An outside service failed (downloading a file by URL, the stock photo service); `params.reason` says why.","fix":"Check the URL or retry later."},"validation":{"meaning":"A parameter or body field is missing, has the wrong type or an invalid value. `message` names it (`email`, `definition.1.0.listId`, `graph` ...), `params` may carry details such as `params.issues` of an automation graph or `params.reason`.","fix":"Fix the named field. For 415 `contentType` send `Content-Type: application/json`; for `body` send a JSON object."}},"paths":{"/api/v1/tenants/{tenant}/lists":{"get":{"operationId":"lists_index","summary":"List contact lists","tags":["Lists"],"description":"Contact lists with member counts by bucket. shop = lists of the shop and of the whole company.\n\nFiltry of the grid (routes_grid.py): ?tag= (repeatable, every label), ?idFrom=&idTo=, ?createdFrom=&createdTo= (calendar days), ?membersFrom=&membersTo= (members of any state except removed), ?activeFrom=&activeTo= (SE's Počet aktivních kontaktů), ?publicName= (part of the public name).\n\nContract notes (cs):\n\nParametry: `q` (část názvu nebo veřejného názvu, bez ohledu na velká písmena a diakritiku), `shop` (seznamy e-shopu **a** seznamy bez e-shopu; e-shop jiného tenanta = 400), `tag`, `sort` (obecná pravidla), `page`, `pageSize`. Výchozí řazení: nejnovější první.","parameters":[{"$ref":"#/components/parameters/tenant"},{"$ref":"#/components/parameters/page"},{"$ref":"#/components/parameters/pageSize"},{"name":"publicName","in":"query","required":false,"schema":{"type":"string"}},{"name":"q","in":"query","required":false,"schema":{"type":"string","maxLength":200},"description":"Search text."},{"name":"shop","in":"query","required":false,"schema":{"type":"string"},"description":"Shop id filter."},{"$ref":"#/components/parameters/sort"},{"name":"tag","in":"query","required":false,"schema":{"type":"string"},"description":"Tag filter."}],"x-postarna-permission":{"module":"contacts","level":"read","keyScope":"read or full"},"x-mcp-tool":"list_lists","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/ContactListSummary"}},"total":{"type":"number"},"page":{"type":"number"},"pageSize":{"type":"number"}},"required":["items","total","page","pageSize"]}}}},"400":{"description":"`validation` (message: publicName, q, sort, tag)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"operationId":"lists_create","summary":"Create a list","tags":["Lists"],"description":"Creates a manual list.\n\nContract notes (cs):\n\nTělo `ListInput`: `{ \"name\", \"publicName\", \"shopId\" (null = celá firma), \"notifyOnUnsubscribe\", \"notifyOnAdd\", \"note\", \"tags\", \"signatureHtml\" }`, povinné je jen `name`. Vrací 201 nebo 200 s `ContactListDetail` (vždy ruční seznam). Texty se ořežou. Chyby: 400 `name` (prázdný nebo nad 100 znaků), `publicName` (nad 100), `note` (nad 1 000), `signatureHtml` (nad 5 000 znaků; nebezpečné značky a atributy se odstraní), `shop`; 409 `conflict` / `name`, když název v tenantu už existuje (přesná shoda jako `UNIQUE (tenant_id, name)`).","parameters":[{"$ref":"#/components/parameters/tenant"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListInput"},"examples":{"contacts-list-create":{"summary":"docs/api-examples/contacts.json list-create","value":{"name":"VIP zákazníci","publicName":"Novinky pro VIP","shopId":null,"notifyOnUnsubscribe":false,"notifyOnAdd":false,"note":""}}}}}},"x-postarna-permission":{"module":"contacts","level":"full","keyScope":"full"},"x-mcp-tool":"create_list","responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactListDetail"},"examples":{"contacts-list-create":{"summary":"docs/api-examples/contacts.json list-create","value":{"id":"41","name":"VIP zákazníci","publicName":"Novinky pro VIP","shopId":null,"systemKind":null,"locked":false,"manualMembers":true,"tags":[],"counts":{"total":0,"byBucket":{"active":0,"unconfirmed":0,"unsubscribed":0,"bounced":0,"complained":0,"blacklisted":0}},"createdAt":"2026-10-01T08:00:00.000Z","notifyOnUnsubscribe":false,"notifyOnAdd":false,"note":""}}}}}},"400":{"description":"`validation` (message: replyTo, senderEmail, senderName, signatureHtml, tags)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`conflict` (message: locked, name)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/tenants/{tenant}/lists/merge":{"post":{"operationId":"lists_merge","summary":"Merge lists into a new list (background)","tags":["Lists"],"description":"Bulk action Sloučit seznamy kontaktů (SZ-07): body {listIds (2 to 100), name, deleteSources}. Creates the new list named name now (409 conflict name_taken) and merges the members in the background (202 {list, jobId}; the notification list_merged tells when it is done). A contact in more lists keeps its most cautious state, nobody is subscribed again. deleteSources deletes the original lists afterwards (only the lists; 409 conflict locked when one of them is a system list).","parameters":[{"$ref":"#/components/parameters/tenant"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListMergeInput"}}},"description":"Required by the server: listIds, name."},"x-postarna-permission":{"module":"contacts","level":"full","keyScope":"full"},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListMergeResult"}}}},"400":{"description":"`validation` (message: listIds, replyTo, senderEmail, senderName, signatureHtml, tags)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`conflict` (message: locked, name)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/tenants/{tenant}/lists/tags":{"post":{"operationId":"lists_tags","summary":"Change labels of lists","tags":["Lists"],"parameters":[{"$ref":"#/components/parameters/tenant"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GridTagsInput"}}},"description":"Required by the server: ids."},"x-postarna-permission":{"module":"contacts","level":"full","keyScope":"full"},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"updated":{"type":"number"}},"required":["updated"]}}}},"400":{"description":"`validation` (message: ids)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/tenants/{tenant}/lists/{list_id}":{"delete":{"operationId":"lists_delete","summary":"Delete a list (only the list, with its contacts, or keeping contacts in other lists)","tags":["Lists"],"description":"Deletes a manual list (memberships go, contacts stay).\n\nSE's Smazat seznam with its three ways (?mode=, SZ-08): list (default) = only the list, its contacts stay (204); with_contacts = the list and its contacts, keep_used = the list and those of its contacts that are in no other list or segment: both run in the background (202 {jobId}; the notification list_deleted tells when it is done). Unsubscribed contacts keep their suppression, contacts a campaign was just sent to stay. 409 conflict locked for a system list.\n\nContract notes (cs):\n\nSmaže ruční nebo formulářový seznam (kontakty zůstávají, členství zmizí). Seznam napojení na e-shop (`locked`) = 409 `conflict` / `locked`, neznámý 404. Odpověď 204. UI: ⋮ řádku a hromadná akce přehledu seznamů.\n\nPoložka `ContactListSummary`:\n```json\n{ \"id\": \"106\", \"name\": \"VIP zákazníci CZ\", \"publicName\": \"Věrní zákazníci\", \"shopId\": \"11\",\n  \"systemKind\": null, \"locked\": false, \"manualMembers\": true, \"createdAt\": \"…\",\n  \"counts\": { \"total\": 14, \"byBucket\": { \"active\": 12, \"unconfirmed\": 0, \"unsubscribed\": 1, \"bounced\": 1, \"complained\": 0, \"blacklisted\": 0 } } }\n```\n• `systemKind` = `lists.system_kind` (`shop_contacts`, `shop_newsletter`, `shop_unsubscribed`, `form`, nebo null u ručního seznamu). `locked` = systémový seznam nejde smazat.\n• `manualMembers` = do seznamu jde ručně přidávat a odebírat. Seznamy plněné napojením na e-shop (`shop_contacts`, `shop_newsletter`, `shop_unsubscribed`) mají false, seznamy formuláře a ruční true.\n• Počty: každý člen (bez stavu `removed`) spadne právě do jedné skupiny v tomto pořadí: `blacklisted` (potlačení s důvodem `manual`), `bounced` (stav kontaktu `bounced` nebo potlačení `hard_bounce`, `soft_bounce_limit`, `invalid`), `complained` (stav `complained` nebo potlačení `complaint`), `unsubscribed` (odhlášený ze seznamu, globálně odhlášený nebo potlačení `unsubscribed`), `unconfirmed` (nepotvrzený v seznamu nebo stav `pending`), jinak `active`. `total` = součet skupin.","parameters":[{"$ref":"#/components/parameters/tenant"},{"name":"list_id","in":"path","required":true,"description":"Id of the list (string of digits).","schema":{"type":"string"}},{"name":"mode","in":"query","required":false,"schema":{"type":"string"}}],"x-postarna-permission":{"module":"contacts","level":"full","keyScope":"full"},"x-mcp-confirm":"delete","x-mcp-tool":"delete_list","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListJobQueued"}}}},"400":{"description":"`validation` (message: mode)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`conflict` (message: locked)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}},"get":{"operationId":"lists_show","summary":"Get a list","tags":["Lists"],"description":"One list with counts and settings.\n\nContract notes (cs):\n\n`ContactListDetail` = `ContactListSummary` a navíc `notifyOnUnsubscribe`, `notifyOnAdd`, `note`, `signatureHtml` (podpis seznamu v HTML, značka `{{cl_signature}}` e-mailů pro seznam; SE Podpis v e-mailu pro tento seznam kontaktů).","parameters":[{"$ref":"#/components/parameters/tenant"},{"name":"list_id","in":"path","required":true,"description":"Id of the list (string of digits).","schema":{"type":"string"}}],"x-postarna-permission":{"module":"contacts","level":"read","keyScope":"read or full"},"x-mcp-tool":"get_list","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactListDetail"}}}},"400":{"description":"`validation`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}},"patch":{"operationId":"lists_update","summary":"Update a list","tags":["Lists"],"description":"Changes a list (name, public name, notifications, note).\n\nContract notes (cs):\n\nTělo: libovolná podmnožina `ListInput`. Systémovému seznamu nejde změnit e-shop (400 `shop`), přejmenovat ho jde. Vrací `ContactListDetail`.","parameters":[{"$ref":"#/components/parameters/tenant"},{"name":"list_id","in":"path","required":true,"description":"Id of the list (string of digits).","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"publicName":{"type":"string"},"shopId":{"type":["string","null"]},"notifyOnUnsubscribe":{"type":"boolean"},"notifyOnAdd":{"type":"boolean"},"note":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"signatureHtml":{"type":"string"},"senderEmail":{"type":"string"},"senderName":{"type":"string","description":"SE's sendername and replyto of a list (API only in SE, SZ-04)."},"replyTo":{"type":"string"}}}}}},"x-postarna-permission":{"module":"contacts","level":"full","keyScope":"full"},"x-mcp-tool":"update_list","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactListDetail"}}}},"400":{"description":"`validation` (message: replyTo, senderEmail, senderName, signatureHtml, tags)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`conflict` (message: locked, name)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/tenants/{tenant}/lists/{list_id}/autofill":{"post":{"operationId":"lists_autofill","summary":"Fill empty name day, gender and salutations of the members","tags":["Lists"],"description":"Row menu \"Doplnit svátek, pohlaví a pozdrav\" (SZ-06, core/contact_autofill.py): fills only empty fields of the members; {members, contacts, fields: {column: contacts}}.","parameters":[{"$ref":"#/components/parameters/tenant"},{"name":"list_id","in":"path","required":true,"description":"Id of the list (string of digits).","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}},"x-postarna-permission":{"module":"contacts","level":"full","keyScope":"full"},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListAutofillResult"}}}},"400":{"description":"`validation`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/tenants/{tenant}/lists/{list_id}/copy":{"post":{"operationId":"lists_copy","summary":"Copy a list with its members","tags":["Lists"],"description":"Row menu \"Vytvořit kopii seznamu\" (SZ-06): body {name}; 201 with the new list (members in their states, no automation starts), 404 unknown list, 409 conflict name_taken.","parameters":[{"$ref":"#/components/parameters/tenant"},{"name":"list_id","in":"path","required":true,"description":"Id of the list (string of digits).","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"}},"required":["name"]}}},"description":"Required by the server: name."},"x-postarna-permission":{"module":"contacts","level":"full","keyScope":"full"},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactListDetail"}}}},"400":{"description":"`validation` (message: replyTo, senderEmail, senderName, signatureHtml, tags)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`conflict` (message: locked, name)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/tenants/{tenant}/lists/{list_id}/members":{"get":{"operationId":"lists_members","summary":"List members of a list","tags":["Lists"],"description":"Members of a list. bucket: active, unconfirmed, unsubscribed, bounced, complained, blacklisted.\n\nContract notes (cs):\n\nParametry: `q` (jako u kontaktů), `bucket` (`active`, `unconfirmed`, `unsubscribed`, `bounced`, `complained`, `blacklisted`; bez parametru všichni), `page`, `pageSize`. Řazení: nejnovější kontakt první. Položka `ListMember` = `ContactListItem` a navíc `listState` (`confirmed`, `unconfirmed`, `unsubscribed`; odebraní se nevrací), `bucket`, `addedAt`, `changedAt`.","parameters":[{"$ref":"#/components/parameters/tenant"},{"name":"list_id","in":"path","required":true,"description":"Id of the list (string of digits).","schema":{"type":"string"}},{"name":"bucket","in":"query","required":false,"schema":{"type":"string","enum":["active","unconfirmed","unsubscribed","bounced","complained","blacklisted"]},"description":"Bucket filter."},{"name":"createdFrom","in":"query","required":false,"schema":{"type":"string"}},{"name":"createdTo","in":"query","required":false,"schema":{"type":"string"}},{"name":"engagement","in":"query","required":false,"schema":{"type":"string"}},{"name":"import","in":"query","required":false,"schema":{"type":"string"}},{"name":"list","in":"query","required":false,"schema":{"type":"string"}},{"name":"listState","in":"query","required":false,"schema":{"type":"string"}},{"$ref":"#/components/parameters/page"},{"$ref":"#/components/parameters/pageSize"},{"name":"q","in":"query","required":false,"schema":{"type":"string","maxLength":200},"description":"Search text."},{"$ref":"#/components/parameters/shop"},{"$ref":"#/components/parameters/sort"},{"name":"source","in":"query","required":false,"schema":{"type":"string"}},{"name":"state","in":"query","required":false,"schema":{"type":"string"}},{"name":"status","in":"query","required":false,"schema":{"type":"string"},"description":"Status filter."},{"name":"tab","in":"query","required":false,"schema":{"type":"string"}}],"x-postarna-permission":{"module":"contacts","level":"read","keyScope":"read or full"},"x-mcp-tool":"list_list_members","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/ListMember"}},"total":{"type":"number"},"page":{"type":"number"},"pageSize":{"type":"number"}},"required":["items","total","page","pageSize"]},"examples":{"contacts-list-members":{"summary":"docs/api-examples/contacts.json list-members","value":{"items":[{"id":"1202","email":"jana.novakova@example.cz","firstName":"Jana","lastName":"Nováková","status":"subscribed","engagement":"N","excluded":false,"shopId":"3","suppressionReason":null,"blacklisted":false,"blacklistedAt":null,"state":"active","createdAt":"2026-10-01T08:00:00.000Z","listState":"confirmed","bucket":"active","addedAt":"2026-10-01T08:00:00.000Z","changedAt":"2026-10-01T08:00:00.000Z"}],"total":1,"page":1,"pageSize":25}}}}}},"400":{"description":"`validation` (message: q, sort)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`conflict` (message: locked, name)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"operationId":"lists_add_members","summary":"Add contacts to a list","tags":["Lists"],"description":"Adds contacts to a manual list (1 to 1000 ids). Never re-subscribes anyone who unsubscribed.\n\nContract notes (cs):\n\nTělo `{ \"contactIds\": [\"1001\", \"1002\"] }`, 1 až 1 000 id kontaktů tenanta (jinak 400 `contactIds` a nic se nezmění; duplicity se počítají jednou). Systémový seznam = 409 `locked`. Odpověď `{ \"added\", \"alreadyMember\", \"skipped\" }`:\n• `alreadyMember`: kontakt už je v seznamu potvrzený nebo nepotvrzený.\n• `skipped`: kontakt se ze seznamu odhlásil, nebo je odhlášený, vrácený, se stížností či na seznamu potlačení. **Ruční přidání nikoho znovu nepřihlásí.**\n• `added`: nový člen, nebo dříve odebraný (`removed`). Stav `confirmed`, u kontaktu ve stavu `pending` `unconfirmed`.","parameters":[{"$ref":"#/components/parameters/tenant"},{"name":"list_id","in":"path","required":true,"description":"Id of the list (string of digits).","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"contactIds":{"type":"array","items":{"type":"string"}}},"required":["contactIds"]},"examples":{"contacts-list-add":{"summary":"docs/api-examples/contacts.json list-add","value":{"contactIds":["1202"]}}}}},"description":"Required by the server: contactIds."},"x-postarna-permission":{"module":"contacts","level":"full","keyScope":"full"},"x-mcp-confirm":"trigger","x-mcp-tool":"add_list_members","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddMembersResult"},"examples":{"contacts-list-add":{"summary":"docs/api-examples/contacts.json list-add","value":{"added":1,"alreadyMember":0,"skipped":0}}}}}},"400":{"description":"`validation` (message: contactIds)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`conflict` (message: locked)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/tenants/{tenant}/lists/{list_id}/members/remove":{"post":{"operationId":"lists_remove_members","summary":"Remove contacts from a list","tags":["Lists"],"description":"Removes contacts from a manual list.\n\nContract notes (cs):\n\nTělo `{ \"contactIds\": [...] }` se stejnou kontrolou. Potvrzený a nepotvrzený člen dostane stav `removed` (řádek a historie zůstávají). Člen, který se ze seznamu odhlásil, zůstane `unsubscribed`, aby ho pozdější přidání znovu nepřihlásilo. Odpověď `{ \"removed\" }`.","parameters":[{"$ref":"#/components/parameters/tenant"},{"name":"list_id","in":"path","required":true,"description":"Id of the list (string of digits).","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"contactIds":{"type":"array","items":{"type":"string"}}},"required":["contactIds"]}}},"description":"Required by the server: contactIds."},"x-postarna-permission":{"module":"contacts","level":"full","keyScope":"full"},"x-mcp-confirm":"remove","x-mcp-tool":"remove_list_members","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RemoveMembersResult"}}}},"400":{"description":"`validation` (message: contactIds)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`conflict` (message: locked)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/tenants/{tenant}/lists/{list_id}/members/unsubscribe":{"post":{"operationId":"lists_unsubscribe_members","summary":"Unsubscribe contacts from a list","tags":["Lists"],"description":"Unsubscribes the given members from this list only (body {contactIds}); 409 locked for lists filled by an e-shop connection. Answers {unsubscribed: n}.\n\nContract notes (cs):\n\nTělo `{ \"contactIds\": [...] }` se stejnou kontrolou. Potvrzený a nepotvrzený člen dostane stav `unsubscribed` jen v tomto seznamu (karta kontaktu, tab Seznamy: „Odhlásit kontakt ze seznamu“); stav kontaktu a ostatní seznamy se nemění, nikdo se tím znovu nepřihlásí. Seznam plněný napojením e-shopu = 409 `locked`. Odpověď `{ \"unsubscribed\" }`.","parameters":[{"$ref":"#/components/parameters/tenant"},{"name":"list_id","in":"path","required":true,"description":"Id of the list (string of digits).","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"contactIds":{"type":"array","items":{"type":"string"}}},"required":["contactIds"]}}},"description":"Required by the server: contactIds."},"x-postarna-permission":{"module":"contacts","level":"full","keyScope":"full"},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"unsubscribed":{"type":"number"}},"required":["unsubscribed"]}}}},"400":{"description":"`validation` (message: contactIds)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`conflict` (message: locked)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"pst_ API key","description":"An API key of the company: Authorization: Bearer pst_... (Nastavení › Datové zdroje › API klíče). Scope read or full, roles of the key."},"basicAuth":{"type":"http","scheme":"basic","description":"HTTP Basic as SmartEmailing: user name = e-mail of an active member of the company, password = the API key."},"oauth2":{"type":"oauth2","description":"OAuth 2.1 for MCP clients (AI assistants) only: authorization code with PKCE S256, dynamic registration.","flows":{"authorizationCode":{"authorizationUrl":"https://www.postarna.cz/oauth/authorize","tokenUrl":"https://www.postarna.cz/oauth/token","refreshUrl":"https://www.postarna.cz/oauth/token","scopes":{"read":"Read only (routes whose permission level is read).","full":"Everything the member's roles allow."}}}}},"parameters":{"tenant":{"name":"tenant","in":"path","required":true,"description":"Public id of the company (tenant), a UUID: GET /api/v3/account-info answers it as data.guid; the app shows it on the API keys page.","schema":{"type":"string","format":"uuid"}},"page":{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1},"description":"Page number, from 1."},"pageSize":{"name":"pageSize","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":25},"description":"Items per page, 1 to 100."},"shop":{"name":"shop","in":"query","required":false,"schema":{"type":"string"},"description":"Only objects of this shop (shop id)."},"sort":{"name":"sort","in":"query","required":false,"schema":{"type":"string"},"description":"Sort fields separated by commas, \"-\" = descending, at most 3 (e.g. name,-id)."}},"headers":{"X-RateLimit-Limit":{"description":"Size of the bucket nearest to running out (2 500 per second bucket or 100 000 daily).","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in that bucket.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time when that bucket is full again.","schema":{"type":"integer"}},"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"responses":{"Unauthorized":{"description":"401 unauthorized: missing, unknown, revoked, expired or idle API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"unauthorized","message":"api_key"}}}}},"Forbidden":{"description":"403 forbidden: message tenant (another company), scope (read-only key), api_key (person only), or the module the key's roles lack.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"forbidden","message":"scope"}}}}},"RateLimited":{"description":"429 rate_limited: wait Retry-After seconds.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"per-second"}}}}}},"schemas":{"AddMembersResult":{"type":"object","properties":{"added":{"type":"number"},"alreadyMember":{"type":"number","description":"Already confirmed or unconfirmed in the list."},"skipped":{"type":"number","description":"Unsubscribed from the list, unsubscribed, bounced, complained or suppressed: never re-subscribed by hand."}},"required":["added","alreadyMember","skipped"]},"ContactListDetail":{"type":"object","properties":{"notifyOnUnsubscribe":{"type":"boolean"},"notifyOnAdd":{"type":"boolean"},"note":{"type":"string","description":"Private note for the team."},"signatureHtml":{"type":"string","description":"SE's Podpis v e-mailu pro tento seznam kontaktů (HTML), sanitized: {{cl_signature}} of e-mails to the list."},"senderEmail":{"type":"string","description":"SE's e-mail odesílatele of the list (lower case, '' = none): the signature goes into every e-mail sent from this\naddress, and the list's e-mail notices go to the account's user with this address."},"id":{"type":"string"},"name":{"type":"string"},"publicName":{"type":"string","description":"Name shown on the unsubscribe page; empty = the name is used."},"shopId":{"type":["string","null"],"description":"null = list of the whole tenant, not bound to one e-shop."},"systemKind":{"type":["string","null"],"enum":["form","shop_contacts","shop_newsletter","shop_unsubscribed",null]},"locked":{"type":"boolean","description":"System lists cannot be deleted."},"manualMembers":{"type":"boolean","description":"False for lists filled by an e-shop connection: members are not added or removed by hand (409)."},"tags":{"type":"array","items":{"type":"string"},"description":"Labels (štítky), at most 20."},"counts":{"$ref":"#/components/schemas/ListCounts"},"createdAt":{"type":"string"}},"required":["notifyOnUnsubscribe","notifyOnAdd","note","signatureHtml","senderEmail","id","name","publicName","shopId","systemKind","locked","manualMembers","tags","counts","createdAt"]},"ContactListSummary":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"publicName":{"type":"string","description":"Name shown on the unsubscribe page; empty = the name is used."},"shopId":{"type":["string","null"],"description":"null = list of the whole tenant, not bound to one e-shop."},"systemKind":{"type":["string","null"],"enum":["form","shop_contacts","shop_newsletter","shop_unsubscribed",null]},"locked":{"type":"boolean","description":"System lists cannot be deleted."},"manualMembers":{"type":"boolean","description":"False for lists filled by an e-shop connection: members are not added or removed by hand (409)."},"tags":{"type":"array","items":{"type":"string"},"description":"Labels (štítky), at most 20."},"counts":{"$ref":"#/components/schemas/ListCounts"},"createdAt":{"type":"string"}},"required":["id","name","publicName","shopId","systemKind","locked","manualMembers","tags","counts","createdAt"]},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["conflict","count_mismatch","csrf","empty_segment","expired","forbidden","method_not_allowed","not_found","not_implemented","rate_limited","recently_sent","server","suppression_locked","unauthorized","unavailable","upstream","validation"],"description":"Stable machine readable code (see the error catalog in the guides)."},"message":{"type":"string","description":"The field or the reason (technical, not for people), e.g. \"email\", \"definition.1.0.listId\", \"name\"."},"params":{"type":"object","additionalProperties":true,"description":"Details: issues of a graph, expected and actual counts, retryAfter ..."}}}},"examples":[{"error":{"code":"validation","message":"email"}},{"error":{"code":"conflict","message":"version","params":{"current":4}}}]},"GridTagsInput":{"type":"object","properties":{"ids":{"type":"array","items":{"type":"string"}},"add":{"type":"array","items":{"type":"string"}},"remove":{"type":"array","items":{"type":"string"}},"tags":{"type":"array","items":{"type":"string"}}},"required":["ids"],"description":"Bulk label change: add and remove, or tags = the new labels of every row."},"ListAutofillResult":{"type":"object","properties":{"members":{"type":"number"},"contacts":{"type":"number"},"fields":{"type":"object","additionalProperties":{"type":"number"}}},"required":["members","contacts","fields"]},"ListCounts":{"type":"object","properties":{"total":{"type":"number","description":"Members without the removed state; the sum of byBucket."},"byBucket":{"type":"object","properties":{"active":{"type":"number"},"unsubscribed":{"type":"number"},"bounced":{"type":"number"},"complained":{"type":"number"},"blacklisted":{"type":"number"},"unconfirmed":{"type":"number"}},"required":["active","unsubscribed","bounced","complained","blacklisted","unconfirmed"]}},"required":["total","byBucket"]},"ListInput":{"type":"object","properties":{"name":{"type":"string"},"publicName":{"type":"string"},"shopId":{"type":["string","null"]},"notifyOnUnsubscribe":{"type":"boolean"},"notifyOnAdd":{"type":"boolean"},"note":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"signatureHtml":{"type":"string"},"senderEmail":{"type":"string"},"senderName":{"type":"string","description":"SE's sendername and replyto of a list (API only in SE, SZ-04)."},"replyTo":{"type":"string"}},"required":["name","publicName","shopId","notifyOnUnsubscribe","notifyOnAdd","note"]},"ListJobQueued":{"type":"object","properties":{"jobId":{"type":["string","null"]},"queued":{"type":"boolean"}},"required":["jobId"],"description":"202 of a deletion with contacts or of a merge: the work runs in the background, a notification tells when done."},"ListMember":{"type":"object","properties":{"listState":{"type":"string","enum":["unsubscribed","confirmed","unconfirmed"],"description":"State in this list; removed members are never returned."},"bucket":{"type":"string","enum":["active","unsubscribed","bounced","complained","blacklisted","unconfirmed"]},"addedAt":{"type":"string"},"changedAt":{"type":"string"},"id":{"type":"string"},"email":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"status":{"type":"string","enum":["pending","subscribed","unsubscribed","bounced","complained","customer"]},"engagement":{"type":"string","enum":["A","B","C","D","E","N"]},"excluded":{"type":"boolean","description":"SE \"Vyloučené\": long inactive yet mailed often (recomputed weekly with the engagement)."},"shopId":{"type":["string","null"],"description":"Main e-shop (contacts.shop_id), null when unknown."},"suppressionReason":{"type":["string","null"],"enum":["invalid","manual","unsubscribed","hard_bounce","complaint","soft_bounce_limit",null],"description":"Set when the address is on the tenant suppression list."},"blacklisted":{"type":"boolean","description":"On the tenant's blacklist (\"Na blacklistu\"): a manual entry, or an entry of SmartEmailing's blacklist that keeps\nits own reason (migration 0180). Older servers leave it out: then suppressionReason 'manual' decides."},"blacklistedAt":{"type":["string","null"],"description":"When the address went on the blacklist (the suppression's time), null when it is not on it (\"Přidán na blacklist\")."},"state":{"type":"string","enum":["active","bounced","blacklisted"],"description":"SmartEmailing's 3-value contact state; older servers leave it out (contactState derives it)."},"createdAt":{"type":"string"},"extra":{"type":"object","additionalProperties":{"anyOf":[{"type":"boolean"},{"type":"string"},{"type":"number"},{"type":"null"}]},"description":"Values of the extra columns asked for (ContactQuery.fields): dates as YYYY-MM-DD, times as ISO."}},"required":["listState","bucket","addedAt","changedAt","id","email","firstName","lastName","status","engagement","excluded","shopId","suppressionReason","createdAt"]},"ListMergeInput":{"type":"object","properties":{"listIds":{"type":"array","items":{"type":"string"}},"name":{"type":"string"},"deleteSources":{"type":"boolean"}},"required":["listIds","name","deleteSources"],"description":"Bulk action Sloučit seznamy kontaktů (SZ-07)."},"ListMergeResult":{"type":"object","properties":{"list":{"$ref":"#/components/schemas/ContactListDetail"},"jobId":{"type":["string","null"]}},"required":["list","jobId"]},"RemoveMembersResult":{"type":"object","properties":{"removed":{"type":"number"}},"required":["removed"]}}}}