{"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":"Imports and exports","x-displayName":"Imports and exports","description":"CSV import of contacts (upload, preview, run) and CSV exports."}],"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}/export-fields":{"get":{"operationId":"export_fields","summary":"Fields an export can contain","tags":["Imports and exports"],"parameters":[{"$ref":"#/components/parameters/tenant"}],"x-postarna-permission":{"module":"contacts","level":"read","keyScope":"read or full"},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/ExportField"}}},"required":["items"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/tenants/{tenant}/exports":{"get":{"operationId":"exports_index","summary":"List exports","tags":["Imports and exports"],"parameters":[{"$ref":"#/components/parameters/tenant"},{"name":"idFrom","in":"query","required":false,"schema":{"type":"string"}},{"name":"idTo","in":"query","required":false,"schema":{"type":"string"}},{"name":"name","in":"query","required":false,"schema":{"type":"string"}},{"$ref":"#/components/parameters/page"},{"$ref":"#/components/parameters/pageSize"},{"$ref":"#/components/parameters/q"},{"$ref":"#/components/parameters/sort"},{"name":"status","in":"query","required":false,"schema":{"type":"string"},"description":"Status filter."}],"x-postarna-permission":{"module":"contacts","level":"read","keyScope":"read or full"},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/ExportSummary"}},"total":{"type":"number"},"page":{"type":"number"},"pageSize":{"type":"number"}},"required":["items","total","page","pageSize"]}}}},"400":{"description":"`validation` (message: q, sort, status)","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":"exports_create","summary":"Start a CSV export","tags":["Imports and exports"],"parameters":[{"$ref":"#/components/parameters/tenant"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExportInput"}}},"description":"Required by the server: name, selection."},"x-postarna-permission":{"module":"contacts","level":"full","keyScope":"full"},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExportSummary"}}}},"400":{"description":"`validation`","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}/exports/{export_id}":{"get":{"operationId":"exports_show","summary":"Get an export","tags":["Imports and exports"],"parameters":[{"$ref":"#/components/parameters/tenant"},{"name":"export_id","in":"path","required":true,"description":"Id of the export (string of digits).","schema":{"type":"string"}}],"x-postarna-permission":{"module":"contacts","level":"read","keyScope":"read or full"},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExportSummary"}}}},"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}/exports/{export_id}/download":{"get":{"operationId":"exports_download","summary":"Download an export (CSV)","tags":["Imports and exports"],"parameters":[{"$ref":"#/components/parameters/tenant"},{"name":"export_id","in":"path","required":true,"description":"Id of the export (string of digits).","schema":{"type":"string"}}],"x-postarna-permission":{"module":"contacts","level":"full","keyScope":"full"},"responses":{"200":{"description":"OK","content":{"text/csv":{"schema":{"type":"string"}}}},"400":{"description":"`validation`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`conflict` (message: status)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"410":{"description":"`expired` (message: export)","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}/imports":{"get":{"operationId":"imports_index","summary":"List imports","tags":["Imports and exports"],"description":"Imports with status and counts.\n\nContract notes (cs):\n\nSeznam (nejnovější první, `page`, `pageSize`) položek `ImportSummary`: `{ \"id\", \"name\", \"status\", \"kind\", \"fileName\", \"shopId\", \"mode\", \"listIds\", \"createdAt\", \"startedAt\", \"finishedAt\", \"progress\": { \"processed\", \"total\" }, \"counts\" }`. Grid SE „Všechny importy“ (audit IM-01): `q` (slova názvu), Filtry `idFrom`, `idTo`, `name`, `status` (`draft`, `queued`, `running`, `finished`, `failed`; jiný stav nebo nečíselné ID 400), řazení navíc `lists` (počet vybraných seznamů); položka seznamu nese `listNames` (názvy vybraných seznamů v jejich pořadí). Stejné Filtry (`idFrom`, `idTo`, `name`, `status` exportu) bere `GET …/exports` (EX-01). Detail `ImportDetail` navíc `source`, `purposeId`, `addedToLists` (nová členství), `errors` (prvních 50 chybných řádků `{ \"row\", \"error\", \"field\" }`) a `report` (`ImportReport`, null do dokončení).","parameters":[{"$ref":"#/components/parameters/tenant"},{"name":"idFrom","in":"query","required":false,"schema":{"type":"string"}},{"name":"idTo","in":"query","required":false,"schema":{"type":"string"}},{"name":"name","in":"query","required":false,"schema":{"type":"string"}},{"$ref":"#/components/parameters/page"},{"$ref":"#/components/parameters/pageSize"},{"$ref":"#/components/parameters/q"},{"$ref":"#/components/parameters/sort"},{"name":"status","in":"query","required":false,"schema":{"type":"string"},"description":"Status filter."}],"x-postarna-permission":{"module":"contacts","level":"read","keyScope":"read or full"},"x-mcp-tool":"list_imports","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/ImportSummary"}},"total":{"type":"number"},"page":{"type":"number"},"pageSize":{"type":"number"}},"required":["items","total","page","pageSize"]}}}},"400":{"description":"`validation` (message: q, sort, status)","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":"imports_run","summary":"Start an import","tags":["Imports and exports"],"description":"Runs an import. upsert and update_only need purposeId and consentConfirmed true; unsubscribe, remove and blacklist leave both out.\n\nContract notes (cs):\n\nTělo `ImportRequest` = `ImportPlan` a navíc `name` (1 až 100 znaků), `source` (odkud kontakty jsou, 1 až 200), `purposeId` (účel tenanta), `consentConfirmed` (musí být true: uživatel potvrzuje doložený a platný účel u všech kontaktů), nepovinně `runAutomations` (boolean, výchozí `false`). Chyby 400 `name`, `source`, `purpose`, `consent`, `runAutomations` (není boolean) a chyby náhledu.\n• **Import nespouští automatizace** (migrace 0077): kontakty, členství v seznamech, změny stavu v seznamu, změny polí a potlačení, které import zapíše, nespustí žádný spouštěč (přidání do seznamu, změna stavu v seznamu, přihlášení, nový kontakt, změna kontaktu, blacklist), jinak by tisíce importovaných lidí dostaly e-mail najednou. Jen když uživatel výslovně zaškrtne `runAutomations: true`, chová se import jako běžný zápis a automatizace spustí. Stejně tak přechod ze SmartEmailingu a jeho hodinová synchronizace nespouští automatizace nikdy. `ImportDetail` vrací `runAutomations`.\n• **Opt-out režimy (`unsubscribe`, `remove`, `blacklist`) nezapisují žádný profil ani souhlas**, takže `purposeId` a `consentConfirmed` jsou u nich nepovinné: klíč musí v těle úplně chybět (prázdný řetězec je pořád neplatné id a vrátí 400 `purpose`), server si pro záznam dosadí první účel tenanta; `consentConfirmed` se u nich vůbec nekontroluje. U ostatních režimů platí povinnost jako dřív.\n• Výsledek se rozhoduje stejně jako v náhledu. Nový kontakt: stav `subscribed` (bez souhlasu `customer`), `source` = `import` (u druhů Shoptetu `shoptet`), `consent_basis` = `imported` (bez souhlasu `none`), jazyk podle e-shopu, když ho soubor nemá. Jeden e-mail = jeden kontakt bez ohledu na zdroj: aktualizace přepíše jen neprázdné hodnoty, které se liší, nikdy e-mail a **nikdy pole, které upravil člověk v Poštárně** (`editedFields` kontaktu). Přidání do seznamů jako `confirmed` (u `pending` `unconfirmed`), jen se souhlasem; kdo se ze seznamu odhlásil, zůstane odhlášený.\n• Server ke každému kontaktu se souhlasem zapíše souhlas (`consents`: účel, `valid_from` = datum souhlasu ze souboru, jinak teď, `valid_until` podle platnosti účelu) a záznam do `consent_log` se zdrojem importu (`import` nebo `shoptet`); opt-out režimy žádný souhlas nezapisují, jen `consent_log` (`list_unsubscribed`, `suppressed`). Každý kontakt, kterého se řádek týká, dostane záznam zdroje (`sources` v detailu kontaktu: zdroj, co říká o souhlasu, kdy byl vidět poprvé a naposledy; u Shoptetu jeden záznam na e-shop).\n• Odpověď `ImportDetail` (viz níže). Do 1 000 řádků server import provede hned (`finished`), větší zařadí jako úlohu (`queued`, `running`): worker ho zpracuje po dávkách 2 000 řádků, po každé dávce uloží průběh (`progress`) a po restartu pokračuje od posledního uloženého řádku. UI se ptá `GET /imports/{id}`, dokud není `finished` nebo `failed`. Po dokončení přijde notifikace `import_finished`. Mock import provede hned.","parameters":[{"$ref":"#/components/parameters/tenant"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportRequest"},"examples":{"import-contacts-run":{"summary":"docs/api-examples/import-contacts.json run","value":{"name":"Kontakty z veletrhu","source":"csv","fileId":"71","mapping":["email","firstName","lastName","city"],"mode":"upsert","shopId":"9","listIds":[],"purposeId":"5","consentConfirmed":true}}}}}},"x-postarna-permission":{"module":"contacts","level":"full","keyScope":"full"},"x-mcp-confirm":"import","x-mcp-tool":"start_import","responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportDetail"},"examples":{"import-contacts-run":{"summary":"docs/api-examples/import-contacts.json run","value":{"id":"61","name":"Kontakty z veletrhu","status":"finished","kind":"csv","fileName":"vlozeny-text.csv","shopId":"9","mode":"upsert","listIds":[],"createdAt":"2026-10-01T08:00:00.000Z","startedAt":"2026-10-01T08:00:00.000Z","finishedAt":"2026-10-01T08:00:00.000Z","progress":{"processed":3,"total":3},"counts":{"rows":3,"create":2,"remove":0,"update":0,"invalid":1,"blacklist":0,"duplicate":0,"skipMissing":0,"unsubscribe":0,"skipSuppressed":0},"source":"csv","purposeId":"5","addedToLists":0,"errors":[{"row":4,"error":"email_invalid","field":"email"}],"runAutomations":false,"options":{},"report":{"consent":{"none":0,"optIn":2,"keptUnsubscribed":0},"merge":{"existing":0,"fromSources":{},"fieldsUpdated":0,"fieldsKeptEdited":0},"dropped":{}}}}}}}},"400":{"description":"`validation` (message: consent, draftId, purpose, runAutomations)","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}/imports/drafts":{"post":{"operationId":"imports_draft_create","summary":"Start an import draft (wizard)","tags":["Imports and exports"],"description":"SE \"Rozpracováno\": the wizard saves itself after its first step. Body {name, source, step, state, fileId?, shopId?}; 201 ImportDraft.","parameters":[{"$ref":"#/components/parameters/tenant"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportDraftInput"}}}},"x-postarna-permission":{"module":"contacts","level":"full","keyScope":"full"},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportDraft"}}}},"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}/imports/drafts/{import_id}":{"get":{"operationId":"imports_draft_show","summary":"Get an import draft","tags":["Imports and exports"],"parameters":[{"$ref":"#/components/parameters/tenant"},{"name":"import_id","in":"path","required":true,"description":"Id of the import (string of digits).","schema":{"type":"string"}}],"x-postarna-permission":{"module":"contacts","level":"read","keyScope":"read or full"},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportDraft"}}}},"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"}}},"put":{"operationId":"imports_draft_update","summary":"Save an import draft","tags":["Imports and exports"],"parameters":[{"$ref":"#/components/parameters/tenant"},{"name":"import_id","in":"path","required":true,"description":"Id of the import (string of digits).","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportDraftInput"}}}},"x-postarna-permission":{"module":"contacts","level":"full","keyScope":"full"},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportDraft"}}}},"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}/imports/files":{"post":{"operationId":"imports_upload","summary":"Upload a CSV file for import","tags":["Imports and exports"],"description":"Uploads CSV text for an import (header row, ; , or tab, up to 5 MB of text; larger files through the administration, up to 64 MB and 300 000 rows). Answers fileId, columns, sample rows, suggestedKind and suggestedMapping.\n\nmultipart/form-data with the field file (up to imports.MAX_BYTES; werkzeug spools it to a temporary file, it is read from there as a stream) or JSON {text} (pasted, up to imports.TEXT_MAX_BYTES). 201 with the stored file, its encoding and the suggested kind and mapping.\n\nContract notes (cs):\n\nSoubor jako `multipart/form-data` v poli `file` (nejvýš 64 MB), nebo vložený text jako JSON `{ \"text\": \"…\" }` (nejvýš 5 MB). Odpověď `ImportFile`: `{ \"fileId\", \"fileName\", \"size\", \"delimiter\", \"encoding\", \"columns\", \"rowCount\", \"sample\", \"suggestedKind\", \"suggestedMapping\" }`.\n• Kódování pozná server sám: UTF-8 (i s BOM), UTF-16 s BOM (Excel „Text v kódování Unicode“), jinak windows-1250 (Shoptet, český Excel); `encoding` = `utf-8`, `utf-16` nebo `windows-1250`. Oddělovač `;`, `,` nebo tabulátor podle hlavičky, uvozovky podle RFC 4180 (zdvojené uvozovky, nové řádky v poli). První řádek je hlavička, prázdné řádky se přeskočí. `sample` = prvních 5 datových řádků. Soubor se čte proudem a ukládá po dávkách, celý v paměti nikdy není.\n• `suggestedKind` a `suggestedMapping`: návrh druhu souboru a mapování podle názvů sloupců (cs, sk, en a názvy Shoptetu, `docs/imports.md`); UI ho použije, uživatel ho může změnit.\n• Chyby 400: `file` (prázdný), `size` (nad 64 MB, u vloženého textu nad 5 MB; `params.maxBytes`), `encoding` (nejde přečíst ani jako windows-1250), `header`, `rows` (žádný datový řádek nebo nad 300 000; `params.maxRows`), `format` (buňka nad 128 000 znaků, typicky chybějící uzavírací uvozovky). Tělo nad limit odmítne už server před čtením: 413 `validation` `size`.","parameters":[{"$ref":"#/components/parameters/tenant"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"anyOf":[{"type":"object","properties":{"delimiter":{"type":"string"},"quote":{"type":"string"},"hasHeader":{"type":"boolean"},"text":{"type":"string"}},"required":["delimiter","quote","hasHeader","text"]},{"type":"object","properties":{"delimiter":{},"quote":{},"hasHeader":{},"text":{"type":"string"}},"required":["text"]}]},"examples":{"import-contacts-upload":{"summary":"docs/api-examples/import-contacts.json upload","value":{"text":"email;jmeno;prijmeni;mesto\nmarie.dvorakova@example.cz;Marie;Dvořáková;Praha\ntomas.cerny@example.cz;Tomáš;Černý;Ostrava\nchybna-adresa;Jan;Bez;Plzeň\n","delimiter":";","quote":"\"","hasHeader":true}}}}}},"x-postarna-permission":{"module":"contacts","level":"full","keyScope":"full"},"x-mcp-tool":"upload_import_csv","responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportFile"},"examples":{"import-contacts-upload":{"summary":"docs/api-examples/import-contacts.json upload","value":{"fileId":"71","fileName":"vlozeny-text.csv","size":155,"delimiter":";","quote":"\"","hasHeader":true,"encoding":"utf-8","columns":["email","jmeno","prijmeni","mesto"],"rowCount":3,"sample":[["marie.dvorakova@example.cz","Marie","Dvořáková","Praha"],["tomas.cerny@example.cz","Tomáš","Černý","Ostrava"],["chybna-adresa","Jan","Bez","Plzeň"]],"suggestedKind":"csv","suggestedMapping":["email","firstName","lastName","city"]}}}}}},"400":{"description":"`validation` (message: file)","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}/imports/preview":{"post":{"operationId":"imports_preview","summary":"Preview an import: mapping, counts, problems","tags":["Imports and exports"],"description":"What an import would do with the file: counts per action and the first rows.\n\nContract notes (cs):\n\nTělo `ImportPlan`: `{ \"fileId\", \"mapping\", \"mode\", \"kind\"?, \"shopId\", \"listIds\" }`.\n• `mapping`: pro každý sloupec pole kontaktu nebo null (nenačítat). Pole: `email`, `firstName`, `lastName`, `fullName` (jméno a příjmení v jednom sloupci, doplní prázdné jméno a příjmení), `titleBefore`, `titleAfter`, `phone`, `mobile`, `company`, `companyId` (IČO), `street`, `city`, `postalCode`, `country`, `language`, `gender`, `birthday`, `note`, `newsletterConsent` (souhlas s newsletterem v řádku), `newsletterConsentAt` (jeho datum), `registeredAt` (datum registrace, uloží se ke zdroji kontaktu). Právě jeden sloupec `email`, žádné pole dvakrát (jinak 400 `mapping`).\n• `kind` (výchozí `csv`): odkud je souhlas řádku. `csv` = souhlas ke všem řádkům potvrzuje uživatel (`consentConfirmed`), ledaže soubor má namapovaný `newsletterConsent`; `shoptet_customers` = export zákazníků ze Shoptetu, souhlas rozhoduje sloupec `newsletterConsent` (povinný, jinak 400 `consentColumn`); `shoptet_newsletter` = export odběratelů newsletteru, každý řádek je odběratel; bez namapovaného `newsletterConsent` musí plán nést `subscribersConfirmed: true` (uživatel v kroku Sloupce potvrdil, že každý řádek je aktivní odběratel; sloupce tohoto exportu nejsou ověřené proti Shoptetu), jinak 400 `subscribers`. **Řádek bez souhlasu se uloží jen jako zákazník** (stav `customer`, `consentBasis` `none`, nikdy se mu neposílá, nepřidá se do `listIds`, žádný souhlas). U druhů Shoptetu se hodnota, která nejde přečíst (neznámá země, chybné datum), vynechá a započítá v `report.dropped`, řádek kvůli ní není chybný; neznámé slovo v souhlasu = bez souhlasu. Neznámý druh = 400 `kind`.\n• `mode`: `upsert` (přidat nové a aktualizovat stávající), `update_only` (jen stávající, nové přeskočit), nebo tři režimy SE, co kontakty jen vyřazují a nic nezapisují do profilu ani do souhlasu (`OPT_OUT_MODES`): `unsubscribe` (odhlásit z `listIds`), `remove` (odebrat z `listIds`) a `blacklist` (adresa na blacklist, i když ještě není kontakt). `unsubscribe` a `remove` potřebují aspoň jeden list (jinak 400 `listIds`, `LIST_MODES`); `blacklist` list nepotřebuje.\n• `shopId` = hlavní e-shop nových kontaktů; vypnutý e-shop (Nastavení › E-shopy) nový import nepřijme, 400 `shop`. `listIds` = seznamy, kam kontakty přidat (nebo u opt-out režimů odhlásit/odebrat): jen ruční a formulářové seznamy tohoto e-shopu nebo celé firmy (jinak 400 `listIds`); do seznamů napojení na e-shop se neimportuje.\n• Neznámý `fileId` (i soubor jiného tenanta) = 404.\n\nOdpověď `{ \"counts\": { \"rows\", \"create\", \"update\", \"skipSuppressed\", \"skipMissing\", \"invalid\", \"duplicate\", \"unsubscribe\", \"remove\", \"blacklist\" }, \"rows\": [ … prvních 20 … ], \"report\": ImportReport }`, řádek `{ \"row\", \"email\", \"action\", \"error\", \"field\", \"values\", \"consent\" }` (`consent` true/false, null u chybného řádku a opt-out režimů). Náhled projde celý soubor po dávkách.\n• Hodnoty se ořežou a znormalizují: e-mail malými písmeny, bez neviditelných znaků, mezinárodní doména v ASCII podobě (IDNA), tečky v adrese zůstávají; `gender` f/m (také žena, muž), `language` cs/sk (cz = cs), `country` dvě písmena nebo běžný název země (Česká republika, Slovensko, Německo …), `birthday` `YYYY-MM-DD` nebo `D. M. YYYY`, telefon jen číslice, mezery a `+()-./`, `companyId` písmena a číslice bez mezer, `newsletterConsent` `yes`/`no` (ano, áno, 1, x, souhlas, přihlášen / ne, nie, 0, odhlášen), data `newsletterConsentAt` a `registeredAt` (`D. M. YYYY` nebo `YYYY-MM-DD`, případně s časem, místní čas firmy) jako ISO UTC, ostatní text do 200 znaků. Prázdná buňka nic nemění.\n• `ImportReport` (souhrn sloučení): `consent` `{ optIn, none, keptUnsubscribed }` (řádky se souhlasem, bez souhlasu, odhlášené kontakty, které soubor uvádí se souhlasem a zůstaly odhlášené), `merge` `{ existing, fromSources, fieldsUpdated, fieldsKeptEdited }` (řádky k existujícímu kontaktu, z nich kolik už přišlo z jiného zdroje podle zdroje `csv`, `shoptet`, `smartemailing`, `api`, `form`, `manual`, `erp`; počet přepsaných hodnot a hodnot ponechaných, protože pole upravil člověk v Poštárně; obojí jen u spuštění), `dropped` `{ pole: počet }`.\n• `action`: `invalid` (`error` = `email_missing`, `email_invalid`, nebo `value_invalid` s `field`), `duplicate` (adresa už byla výš v souboru), `skip_suppressed` (kontakt je odhlášený, vrácený, se stížností nebo na seznamu potlačení: **import ho nikdy znovu nepřihlásí a nic na něm nezmění**; server navíc kontroluje tabulku `suppressions` i pro adresy bez kontaktu), `update`, `skip_missing` (`update_only` a adresa není kontakt), `create`, `unsubscribe`, `remove`, `blacklist` (poslední tři jen u odpovídajícího opt-out režimu).","parameters":[{"$ref":"#/components/parameters/tenant"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportPlan"},"examples":{"import-contacts-preview":{"summary":"docs/api-examples/import-contacts.json preview","value":{"fileId":"71","mapping":["email","firstName","lastName","city"],"mode":"upsert","shopId":"9","listIds":[]}}}}}},"x-postarna-permission":{"module":"contacts","level":"full","keyScope":"full"},"x-mcp-tool":"preview_import","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportPreview"},"examples":{"import-contacts-preview":{"summary":"docs/api-examples/import-contacts.json preview","value":{"counts":{"rows":3,"create":2,"update":0,"skipSuppressed":0,"skipMissing":0,"invalid":1,"duplicate":0,"unsubscribe":0,"remove":0,"blacklist":0},"rows":[{"row":2,"email":"marie.dvorakova@example.cz","action":"create","error":null,"field":null,"values":{"email":"marie.dvorakova@example.cz","firstName":"Marie","lastName":"Dvořáková","city":"Praha"},"consent":true},{"row":3,"email":"tomas.cerny@example.cz","action":"create","error":null,"field":null,"values":{"email":"tomas.cerny@example.cz","firstName":"Tomáš","lastName":"Černý","city":"Ostrava"},"consent":true},{"row":4,"email":"chybna-adresa","action":"invalid","error":"email_invalid","field":"email","values":{"email":"chybna-adresa","firstName":"Jan","lastName":"Bez","city":"Plzeň"},"consent":null}],"report":{"consent":{"optIn":2,"none":0,"keptUnsubscribed":0},"merge":{"existing":0,"fromSources":{},"fieldsUpdated":0,"fieldsKeptEdited":0},"dropped":{}}}}}}}},"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}/imports/{import_id}":{"delete":{"operationId":"imports_delete","summary":"Delete an import record","tags":["Imports and exports"],"description":"SE \"Odstranit import\": only a draft (409 status for an import that started).","parameters":[{"$ref":"#/components/parameters/tenant"},{"name":"import_id","in":"path","required":true,"description":"Id of the import (string of digits).","schema":{"type":"string"}}],"x-postarna-permission":{"module":"contacts","level":"full","keyScope":"full"},"x-mcp-confirm":"delete","responses":{"204":{"description":"No content"},"400":{"description":"`validation`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`conflict` (message: status)","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":"imports_show","summary":"Get an import with its progress and result","tags":["Imports and exports"],"description":"Contract notes (cs):\n\nSeznam (nejnovější první, `page`, `pageSize`) položek `ImportSummary`: `{ \"id\", \"name\", \"status\", \"kind\", \"fileName\", \"shopId\", \"mode\", \"listIds\", \"createdAt\", \"startedAt\", \"finishedAt\", \"progress\": { \"processed\", \"total\" }, \"counts\" }`. Grid SE „Všechny importy“ (audit IM-01): `q` (slova názvu), Filtry `idFrom`, `idTo`, `name`, `status` (`draft`, `queued`, `running`, `finished`, `failed`; jiný stav nebo nečíselné ID 400), řazení navíc `lists` (počet vybraných seznamů); položka seznamu nese `listNames` (názvy vybraných seznamů v jejich pořadí). Stejné Filtry (`idFrom`, `idTo`, `name`, `status` exportu) bere `GET …/exports` (EX-01). Detail `ImportDetail` navíc `source`, `purposeId`, `addedToLists` (nová členství), `errors` (prvních 50 chybných řádků `{ \"row\", \"error\", \"field\" }`) a `report` (`ImportReport`, null do dokončení).","parameters":[{"$ref":"#/components/parameters/tenant"},{"name":"import_id","in":"path","required":true,"description":"Id of the import (string of digits).","schema":{"type":"string"}}],"x-postarna-permission":{"module":"contacts","level":"read","keyScope":"read or full"},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportDetail"},"examples":{"import-contacts-show":{"summary":"docs/api-examples/import-contacts.json show","value":{"id":"61","name":"Kontakty z veletrhu","status":"finished","kind":"csv","fileName":"vlozeny-text.csv","shopId":"9","mode":"upsert","listIds":[],"createdAt":"2026-10-01T08:00:00.000Z","startedAt":"2026-10-01T08:00:00.000Z","finishedAt":"2026-10-01T08:00:00.000Z","progress":{"processed":3,"total":3},"counts":{"rows":3,"create":2,"remove":0,"update":0,"invalid":1,"blacklist":0,"duplicate":0,"skipMissing":0,"unsubscribe":0,"skipSuppressed":0},"source":"csv","purposeId":"5","addedToLists":0,"errors":[{"row":4,"error":"email_invalid","field":"email"}],"runAutomations":false,"options":{},"report":{"consent":{"none":0,"optIn":2,"keptUnsubscribed":0},"merge":{"existing":0,"fromSources":{},"fieldsUpdated":0,"fieldsKeptEdited":0},"dropped":{}}}}}}}},"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"}}}}},"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."},"q":{"name":"q","in":"query","required":false,"schema":{"type":"string","maxLength":200},"description":"Search text (case and diacritics insensitive)."},"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":{"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}}}]},"ExportField":{"type":"object","properties":{"id":{"type":"string","description":"df_* system field or cf_<id> custom field."},"kind":{"type":"string","enum":["custom","contact"]},"name":{"type":"string","description":"Custom fields: their name (tenant data); system fields are named by the UI."},"type":{"type":"string"}},"required":["id","kind","name","type"]},"ExportInput":{"type":"object","properties":{"name":{"type":"string"},"selection":{"type":"string","enum":["bounced","blacklisted","all","lists"]},"listIds":{"type":"array","items":{"type":"string"}},"fields":{"type":"array","items":{"type":"string"}},"purposeIds":{"type":"array","items":{"type":"string"}},"encoding":{"type":"string","enum":["utf-8","windows-1250"]}},"required":["name","selection","fields","encoding"]},"ExportStatus":{"type":"string","enum":["queued","running","finished","failed"]},"ExportSummary":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"status":{"$ref":"#/components/schemas/ExportStatus"},"selection":{"type":"string","enum":["bounced","blacklisted","all","lists"]},"listIds":{"type":"array","items":{"type":"string"}},"fields":{"type":"array","items":{"type":"string"}},"purposeIds":{"type":"array","items":{"type":"string"}},"encoding":{"type":"string","enum":["utf-8","windows-1250"]},"rowCount":{"type":["number","null"]},"fileName":{"type":["string","null"]},"downloadable":{"type":"boolean","description":"The file exists and its link has not expired."},"downloads":{"type":"number"},"createdAt":{"type":"string"},"finishedAt":{"type":["string","null"]},"expiresAt":{"type":["string","null"]},"listNames":{"type":"array","items":{"type":"string"},"description":"Names of the chosen lists in their order (SE's column Seznamy); older servers leave it out."}},"required":["id","name","status","selection","listIds","fields","purposeIds","encoding","rowCount","fileName","downloadable","downloads","createdAt","finishedAt","expiresAt"]},"ImportCounts":{"type":"object","properties":{"rows":{"type":"number"},"create":{"type":"number"},"update":{"type":"number"},"skipSuppressed":{"type":"number","description":"Unsubscribed, bounced, complained or suppressed: never re-subscribed and not changed."},"skipMissing":{"type":"number","description":"update_only and the address is not a contact."},"invalid":{"type":"number"},"duplicate":{"type":"number","description":"Same address earlier in the file."},"unsubscribe":{"type":"number","description":"Opt-out modes: unsubscribed from the lists, removed from them, put on the blacklist."},"remove":{"type":"number"},"blacklist":{"type":"number"}},"required":["rows","create","update","skipSuppressed","skipMissing","invalid","duplicate","unsubscribe","remove","blacklist"]},"ImportDetail":{"type":"object","properties":{"source":{"type":"string"},"purposeId":{"type":"string"},"addedToLists":{"type":"number","description":"New list memberships."},"errors":{"type":"array","items":{"$ref":"#/components/schemas/ImportRowProblem"},"description":"First 50 invalid rows."},"report":{"anyOf":[{"$ref":"#/components/schemas/ImportReport"},{"type":"null"}],"description":"null until finished."},"runAutomations":{"type":"boolean","description":"The import starts automations (ImportRequest.runAutomations); false by default."},"options":{"$ref":"#/components/schemas/ImportOptions"},"id":{"type":"string"},"name":{"type":"string"},"status":{"type":"string","enum":["queued","running","draft","finished","failed"]},"kind":{"type":"string","enum":["csv","shoptet_customers","shoptet_newsletter"]},"fileName":{"type":"string"},"shopId":{"type":"string"},"mode":{"type":"string","enum":["upsert","update_only","unsubscribe","remove","blacklist"]},"listIds":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string"},"startedAt":{"type":["string","null"],"description":"When the run started (null while queued)."},"finishedAt":{"type":["string","null"]},"progress":{"type":"object","properties":{"processed":{"type":"number"},"total":{"type":"number"}},"required":["processed","total"],"description":"Rows the run has processed so far (it commits batch by batch)."},"counts":{"anyOf":[{"$ref":"#/components/schemas/ImportCounts"},{"type":"null"}],"description":"null until finished."},"listNames":{"type":"array","items":{"type":"string"},"description":"Names of the chosen lists in their order (SE's column Seznamy; the grid only)."}},"required":["source","purposeId","addedToLists","errors","report","id","name","status","kind","fileName","shopId","mode","listIds","createdAt","startedAt","finishedAt","progress","counts"]},"ImportDraft":{"type":"object","properties":{"source":{"type":"string"},"state":{"$ref":"#/components/schemas/ImportDraftState"},"file":{"anyOf":[{"$ref":"#/components/schemas/ImportFile"},{"type":"null"}]},"updatedAt":{"type":["string","null"]},"id":{"type":"string"},"name":{"type":"string"},"status":{"type":"string","enum":["queued","running","draft","finished","failed"]},"kind":{"type":"string","enum":["csv","shoptet_customers","shoptet_newsletter"]},"fileName":{"type":"string"},"shopId":{"type":"string"},"mode":{"type":"string","enum":["upsert","update_only","unsubscribe","remove","blacklist"]},"listIds":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string"},"startedAt":{"type":["string","null"],"description":"When the run started (null while queued)."},"finishedAt":{"type":["string","null"]},"progress":{"type":"object","properties":{"processed":{"type":"number"},"total":{"type":"number"}},"required":["processed","total"],"description":"Rows the run has processed so far (it commits batch by batch)."},"counts":{"anyOf":[{"$ref":"#/components/schemas/ImportCounts"},{"type":"null"}],"description":"null until finished."},"listNames":{"type":"array","items":{"type":"string"},"description":"Names of the chosen lists in their order (SE's column Seznamy; the grid only)."}},"required":["source","state","file","updatedAt","id","name","status","kind","fileName","shopId","mode","listIds","createdAt","startedAt","finishedAt","progress","counts"],"description":"SE \"Rozpracováno\": the summary, the wizard's state and the stored file while it lasts (24 hours)."},"ImportDraftInput":{"type":"object","properties":{"name":{"type":"string"},"source":{"type":"string"},"step":{"type":"number"},"state":{"$ref":"#/components/schemas/ImportDraftState"},"fileId":{"type":["string","null"]},"shopId":{"type":["string","null"]}}},"ImportDraftState":{"type":"object","properties":{"step":{"type":"number"}},"additionalProperties":{},"description":"The wizard's own state of a draft import (opaque to the server, with the step)."},"ImportEncoding":{"type":"string","enum":["utf-8","windows-1250","utf-16"]},"ImportFile":{"type":"object","properties":{"fileId":{"type":"string"},"fileName":{"type":"string"},"size":{"type":"number","description":"Bytes of the file as uploaded."},"delimiter":{"type":"string","description":"; , tab or the one character the person chose."},"quote":{"type":"string","description":"Field enclosure (default \")."},"hasHeader":{"type":"boolean","description":"false = the first line is data and the columns have no names."},"encoding":{"allOf":[{"$ref":"#/components/schemas/ImportEncoding"}],"description":"What the server read the file as (detected)."},"columns":{"type":"array","items":{"type":"string"},"description":"Header row."},"rowCount":{"type":"number","description":"Data rows without the header."},"sample":{"type":"array","items":{"type":"array","items":{"type":"string"}},"description":"First data rows as they are in the file."},"suggestedKind":{"type":"string","enum":["csv","shoptet_customers","shoptet_newsletter"],"description":"The server's suggestion from the header names (Czech, Slovak, English, Shoptet)."},"suggestedMapping":{"$ref":"#/components/schemas/ImportMapping"}},"required":["fileId","fileName","size","delimiter","encoding","columns","rowCount","sample","suggestedKind","suggestedMapping"]},"ImportMapping":{"type":"array","items":{"type":["string","null"]}},"ImportOptions":{"type":"object","properties":{"autofill":{"type":"object","properties":{"salutation":{"type":"boolean"},"gender":{"type":"boolean"},"nameday":{"type":"boolean"},"language":{"type":"boolean"},"calendar":{"type":"string","enum":["sk","cz"]}},"required":["salutation","gender","nameday","language","calendar"],"description":"Fill empty fields from the first name and the address (SE \"Automatické doplnění údajů ke kontaktu\")."},"consentFrom":{"anyOf":[{"type":"object","properties":{"kind":{"type":"string","const":"now"}},"required":["kind"]},{"type":"object","properties":{"kind":{"type":"string","const":"date"},"date":{"type":"string"}},"required":["kind","date"]}],"description":"Start of the purpose consent: now, or one date for every row (a date column is the mapped newsletterConsentAt)."},"doi":{"type":"object","properties":{"target":{"type":"string","enum":["new","all"]},"senderId":{"type":"string"},"fromName":{"type":"string"},"replyTo":{"type":"string"},"thanksUrl":{"type":"string"},"templateId":{"type":["string","null"],"description":"SE's chosen double opt-in e-mail: a template with {{confirmlink}}; null = Poštárna's own e-mail (IM-06)."},"landingPageId":{"type":["string","null"],"description":"SE \"Vybrat z vašich landing pages\" as the thank-you page; only without thanksUrl."}},"required":["target","senderId","fromName","replyTo","thanksUrl"],"description":"Double opt-in e-mail instead of an immediate subscription (upsert and update_only)."}}},"ImportPlan":{"type":"object","properties":{"fileId":{"type":"string"},"mapping":{"$ref":"#/components/schemas/ImportMapping"},"mode":{"type":"string","enum":["upsert","update_only","unsubscribe","remove","blacklist"]},"kind":{"type":"string","enum":["csv","shoptet_customers","shoptet_newsletter"],"description":"Default csv."},"subscribersConfirmed":{"type":"boolean","description":"A Shoptet newsletter export without a consent column: the person confirmed that every row is an active\nsubscriber (required, 400 subscribers otherwise)."},"shopId":{"type":"string","description":"Main shop of new contacts; lists must belong to it or to the whole company."},"listIds":{"type":"array","items":{"type":"string"}}},"required":["fileId","mapping","mode","shopId","listIds"]},"ImportPreview":{"type":"object","properties":{"counts":{"$ref":"#/components/schemas/ImportCounts"},"rows":{"type":"array","items":{"$ref":"#/components/schemas/ImportPreviewRow"},"description":"First 20 rows."},"report":{"$ref":"#/components/schemas/ImportReport"}},"required":["counts","rows","report"]},"ImportPreviewRow":{"type":"object","properties":{"row":{"type":"number","description":"Line in the file, the header is line 1."},"email":{"type":"string"},"action":{"type":"string","enum":["invalid","unsubscribe","remove","blacklist","create","update","skip_suppressed","skip_missing","duplicate"]},"error":{"type":["string","null"],"enum":["email_missing","email_invalid","value_invalid",null]},"field":{"type":["string","null"],"enum":["companyId","firstName","lastName","phone","country","street","city","language","note","email","company","mobile","postalCode","gender","fullName","titleBefore","titleAfter","birthday","newsletterConsent","newsletterConsentAt","registeredAt",null],"description":"The field with an invalid value."},"values":{"type":"object","properties":{"companyId":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"phone":{"type":"string"},"country":{"type":"string"},"street":{"type":"string"},"city":{"type":"string"},"language":{"type":"string"},"note":{"type":"string"},"email":{"type":"string"},"company":{"type":"string"},"mobile":{"type":"string"},"postalCode":{"type":"string"},"gender":{"type":"string"},"fullName":{"type":"string"},"titleBefore":{"type":"string"},"titleAfter":{"type":"string"},"birthday":{"type":"string"},"newsletterConsent":{"type":"string"},"newsletterConsentAt":{"type":"string"},"registeredAt":{"type":"string"}},"description":"Normalized values that would be written."},"consent":{"type":["boolean","null"],"description":"Whether the row brings a newsletter consent (false = a customer only); null for invalid rows and opt-out modes."}},"required":["row","email","action","error","field","values","consent"]},"ImportReport":{"type":"object","properties":{"consent":{"type":"object","properties":{"optIn":{"type":"number"},"none":{"type":"number","description":"Rows without consent: customers only."},"keptUnsubscribed":{"type":"number","description":"The file says opt-in, the contact is unsubscribed or suppressed: it stays so (only a double opt-in lifts it)."}},"required":["optIn","none","keptUnsubscribed"]},"merge":{"type":"object","properties":{"existing":{"type":"number","description":"Rows that met an existing contact (one contact per address)."},"fromSources":{"type":"object","additionalProperties":{"type":"number"},"description":"Existing contacts per other source they came from (smartemailing, shoptet, csv, ...)."},"fieldsUpdated":{"type":"number"},"fieldsKeptEdited":{"type":"number","description":"Values not written because a person edited the field in Poštárna."}},"required":["existing","fromSources","fieldsUpdated","fieldsKeptEdited"]},"dropped":{"type":"object","properties":{"companyId":{"type":"number"},"firstName":{"type":"number"},"lastName":{"type":"number"},"phone":{"type":"number"},"country":{"type":"number"},"street":{"type":"number"},"city":{"type":"number"},"language":{"type":"number"},"note":{"type":"number"},"email":{"type":"number"},"company":{"type":"number"},"mobile":{"type":"number"},"postalCode":{"type":"number"},"gender":{"type":"number"},"fullName":{"type":"number"},"titleBefore":{"type":"number"},"titleAfter":{"type":"number"},"birthday":{"type":"number"},"newsletterConsent":{"type":"number"},"newsletterConsentAt":{"type":"number"},"registeredAt":{"type":"number"}},"description":"Values of Shoptet files that did not fit and were left out, per field."}},"required":["consent","merge","dropped"],"description":"Merge summary of an import: the consent of the rows, existing contacts and their other sources, values left out."},"ImportRequest":{"type":"object","properties":{"name":{"type":"string"},"source":{"type":"string","description":"Where the contacts come from (se-spec 01 §7.1), e.g. a trade fair."},"purposeId":{"type":"string","description":"Purpose of processing given to the imported contacts. Optional for OPT_OUT_MODES (the server stores the\ntenant's first purpose when omitted; those modes write no consent)."},"consentConfirmed":{"type":"boolean","description":"The user confirms a documented, valid purpose for every contact; must be true. Not required for OPT_OUT_MODES."},"runAutomations":{"type":"boolean","description":"Explicit opt-in: what the import writes starts automations (added to a list, subscribed, contact created ...).\nDefault false: an import is a bulk write that starts none."},"options":{"allOf":[{"$ref":"#/components/schemas/ImportOptions"}],"description":"Autofill, start of the purpose consent and the double opt-in e-mail (SE wizard)."},"draftId":{"type":"string","description":"The wizard's draft this import comes from (deleted when the import starts)."},"fileId":{"type":"string"},"mapping":{"$ref":"#/components/schemas/ImportMapping"},"mode":{"type":"string","enum":["upsert","update_only","unsubscribe","remove","blacklist"]},"kind":{"type":"string","enum":["csv","shoptet_customers","shoptet_newsletter"],"description":"Default csv."},"subscribersConfirmed":{"type":"boolean","description":"A Shoptet newsletter export without a consent column: the person confirmed that every row is an active\nsubscriber (required, 400 subscribers otherwise)."},"shopId":{"type":"string","description":"Main shop of new contacts; lists must belong to it or to the whole company."},"listIds":{"type":"array","items":{"type":"string"}}},"required":["name","source","fileId","mapping","mode","shopId","listIds"]},"ImportRowProblem":{"type":"object","properties":{"row":{"type":"number"},"error":{"type":"string","enum":["email_missing","email_invalid","value_invalid"]},"field":{"type":["string","null"],"enum":["companyId","firstName","lastName","phone","country","street","city","language","note","email","company","mobile","postalCode","gender","fullName","titleBefore","titleAfter","birthday","newsletterConsent","newsletterConsentAt","registeredAt",null]}},"required":["row","error","field"]},"ImportSummary":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"status":{"type":"string","enum":["queued","running","draft","finished","failed"]},"kind":{"type":"string","enum":["csv","shoptet_customers","shoptet_newsletter"]},"fileName":{"type":"string"},"shopId":{"type":"string"},"mode":{"type":"string","enum":["upsert","update_only","unsubscribe","remove","blacklist"]},"listIds":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string"},"startedAt":{"type":["string","null"],"description":"When the run started (null while queued)."},"finishedAt":{"type":["string","null"]},"progress":{"type":"object","properties":{"processed":{"type":"number"},"total":{"type":"number"}},"required":["processed","total"],"description":"Rows the run has processed so far (it commits batch by batch)."},"counts":{"anyOf":[{"$ref":"#/components/schemas/ImportCounts"},{"type":"null"}],"description":"null until finished."},"listNames":{"type":"array","items":{"type":"string"},"description":"Names of the chosen lists in their order (SE's column Seznamy; the grid only)."}},"required":["id","name","status","kind","fileName","shopId","mode","listIds","createdAt","startedAt","finishedAt","progress","counts"]}}}}