{
    "openapi": "3.1.0",
    "info": {
        "title": "PostPal API",
        "version": "1.0.0",
        "description": "The PostPal API lets you create and manage direct-mail campaigns\nprogrammatically from your own systems. Our API is organised around REST\nwith predictable, resource-oriented URLs and uses standard HTTP response\ncodes to indicate success or failure.\n\n## What you can do\n\n- Trigger personalised direct-mail campaigns from your CRM, ERP, or\n  marketing automation platform\n- Pass custom metadata to correlate PostPal campaigns with records in\n  your own systems\n- Control campaign segment, design, delivery window, and billing\n  details via a single API call\n\n## API design\n\n- All requests are over **HTTPS**\n- Request and response bodies use **JSON**\n- Dates use **ISO 8601** format (`YYYY-MM-DD` / `YYYY-MM-DDTHH:mm:ssZ`)\n- Money values are expressed in **cents**\n\n## Current scope\n\nThe public API surface currently covers **campaign creation** and **API Flow postcard submission**.\nAdditional endpoints will be added as the platform evolves.\n\nSupported campaign segment sources are `klaviyo` and `csv`.\nShopify segments are not supported by the public API.\n\n---\n\n# Authentication\n\nThe PostPal API authenticates via **Bearer tokens**. Include your token\nin the `Authorization` header on every request:\n\n```\nAuthorization: Bearer pp_live_your_token_here\n```\n\n## Getting a token\n\n1. [Sign in to PostPal](https://app.getpostpal.com/login)\n2. Go to [Settings \u2192 API Access](https://app.getpostpal.com/settings/api)\n3. Create a new API token\n   _(token issuance requires API-token access on your account)_\n\n## Test vs. live tokens\n\n| Mode | Prefix | Behaviour |\n|------|--------|-----------|\n| Test | `pp_test_\u2026` | Test tokens are intended for postcard flow testing. They record API Flow entries without triggering real mailings. |\n| Live | `pp_live_\u2026` | Live tokens create campaigns and process live API Flow entries. |\n\nUse test tokens while integrating postcard submissions. Use a live\ntoken to create campaigns or to send real mail.\n\nMissing or invalid tokens return `401 Unauthenticated`.\n\n---\n\n# Quick Start\n\n## 1. Get access\n\n- [Sign in to PostPal](https://app.getpostpal.com/login)\n- Open [Settings \u2192 API Access](https://app.getpostpal.com/settings/api)\n- Create an API token \u2014 use a **live** token for campaign creation\n\n## 2. Make your first request\n\n```bash\ncurl -X POST \"https://getpostpal.com/api/v1/campaigns\" \\\n  -H \"Authorization: Bearer pp_live_your_token\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Idempotency-Key: campaign-create-001\" \\\n  -d '{\n    \"name\": \"My First Campaign\",\n    \"processingType\": \"one_off\",\n    \"segment\": { \"source\": \"klaviyo\", \"id\": \"your-segment-uuid\" },\n    \"design\": { \"postcardDesignId\": \"your-design-uuid\" },\n    \"delivery\": { \"deliveryPeriodEndAt\": \"2026-06-01\" },\n    \"addressVerification\": true,\n    \"billing\": {\n      \"paymentMethod\": \"rechnung\",\n      \"invoiceEmail\": \"billing@example.com\"\n    }\n  }'\n```\n\nSupported segment sources:\n\n```json\n{ \"segment\": { \"source\": \"klaviyo\", \"id\": \"your-segment-uuid\" } }\n```\n\n```json\n{ \"segment\": { \"source\": \"csv\", \"id\": \"your-csv-file-uuid\" } }\n```\n\n## 3. Read the response\n\nA `201 Created` response contains the campaign object \u2014 note the `id`,\n`slug`, and `status` fields. See the **Campaigns** section for the full\nendpoint reference and response schema.\n\n## 4. Retry safely\n\nAlways include an `Idempotency-Key` header. If a request fails in\ntransit and you retry with the same key and payload, PostPal returns\nthe original campaign instead of creating a duplicate. Reusing a key\nwith a *different* payload returns `409 Conflict`.\n\n## 5. Go live\n\nUse a test token (`pp_test_\u2026`) with `POST /api/v1/postcards` while\ntesting API Flow submissions. Keep using live tokens for campaign\ncreation."
    },
    "servers": [
        {
            "url": "https://app.getpostpal.com",
            "description": "PostPal App"
        }
    ],
    "paths": {
        "/api/v1/postcards": {
            "post": {
                "operationId": "v1.postcards.store",
                "description": "Submit a recipient address to an existing API Flow for batch direct-mail delivery.\n\nUse a test token (`pp_test_\u2026`) to record entries without triggering real mailings. Use a live token (`pp_live_\u2026`) for production.\n\nSign in to PostPal and create a token if your account grants API-token access.",
                "summary": "Submit postcard recipient",
                "tags": [
                    "Postcards"
                ],
                "requestBody": {
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "flow_id": {
                                        "type": "string",
                                        "format": "uuid"
                                    },
                                    "qr_codes": {
                                        "type": "object",
                                        "description": "Design-bound QR values for API Flow postcards.\n\nUse `qr_codes` only when the linked API Flow's Postcard Studio design contains QR codes with API field names. In the Studio, the user selects a QR code and assigns a QR API field name (`API-Feldname` in the Studio UI), for example `voucher_url_1` or `voucher_url_2`. Those field names become the required request keys under `qr_codes`: `voucher_url_1` in the design means the request must include `qr_codes.voucher_url_1`.\n\nContract:\n- If the design contains QR codes with API field names, `qr_codes` is required and must include every configured QR API field name.\n- If the design contains no QR codes with API field names, omit `qr_codes`; sending it is rejected.\n- Unknown keys are rejected.\n- Values must be non-empty strings.\n- Each value may contain at most 2,048 characters.\n- Each value must be the complete HTTP(S) target URL that should be encoded into the matching QR code.\n- PostPal checks before accepting the entry that each QR URL is valid and fits into a QR code.\n\nHow the value is used:\n- API-bound QR codes in the design use the exact placeholder `{{voucher_url_1}}` as their QR target.\n- The request value replaces the full placeholder, so `qr_codes.voucher_url_1` must already be a complete scan-ready URL.\n- Do not send only a voucher code, path segment or query fragment unless that fragment itself is a complete HTTP(S) URL.\n\nComplete request fragment:\n```json\n{\n  \"qr_codes\": {\n    \"voucher_url_1\": \"https://example.com/voucher/1\",\n    \"voucher_url_2\": \"https://example.com/voucher/2\"\n  }\n}\n```\n\nAI agent instructions:\n- Do not invent `qr_codes` keys. Use exactly the QR API field names configured in the linked design.\n- Use `qr_codes`, not `qr`, for QR values.\n- On `422`, inspect `meta.expected_qr_keys` and `meta.qr_fit_errors`.\n- For missing or unknown keys, rewrite the payload keys exactly to `meta.expected_qr_keys`.\n- For invalid URL errors, provide a complete `http` or `https` URL as the QR value.\n- For fit errors, shorten the full QR URL value.",
                                        "propertyNames": {
                                            "type": "string",
                                            "pattern": "^[a-z][a-z0-9_]{0,63}$"
                                        },
                                        "additionalProperties": {
                                            "type": "string",
                                            "description": "Full HTTP(S) target URL for one design-defined QR API field. This exact URL is encoded into the QR code.",
                                            "minLength": 1,
                                            "maxLength": 2048,
                                            "example": "https://example.com/voucher/1"
                                        },
                                        "example": {
                                            "voucher_url_1": "https://example.com/voucher/1",
                                            "voucher_url_2": "https://example.com/voucher/2"
                                        },
                                        "x-postpal-qr-contract": {
                                            "where_fields_are_defined": "The user adds QR codes in Postcard Studio and assigns each API Flow QR code an API field name (`API-Feldname` in the Studio UI).",
                                            "request_mapping": "Each QR API field name becomes a required key under qr_codes, for example API field name voucher_url_1 => qr_codes.voucher_url_1.",
                                            "required_when": "Required when the linked API Flow design contains one or more QR codes with API field names.",
                                            "prohibited_when": "Rejected when the linked API Flow design contains no QR codes with API field names.",
                                            "all_expected_keys_required": true,
                                            "unknown_keys_rejected": true,
                                            "value_is_full_target_url": true
                                        },
                                        "x-postpal-agent-guidance": [
                                            "Do not invent `qr_codes` keys. Use exactly the QR API field names configured in the linked design.",
                                            "Use `qr_codes`, not `qr`, for QR values.",
                                            "On 422, inspect meta.expected_qr_keys and meta.qr_fit_errors.",
                                            "For missing or unknown keys, rewrite the payload keys to match meta.expected_qr_keys exactly.",
                                            "For invalid URL errors, provide a complete http or https URL as the qr_codes value.",
                                            "For QR fit errors, shorten the full QR URL value."
                                        ]
                                    },
                                    "from": {
                                        "type": "object",
                                        "properties": {
                                            "company": {
                                                "type": [
                                                    "string",
                                                    "null"
                                                ]
                                            },
                                            "first_name": {
                                                "type": [
                                                    "string",
                                                    "null"
                                                ]
                                            },
                                            "last_name": {
                                                "type": [
                                                    "string",
                                                    "null"
                                                ]
                                            },
                                            "address_line1": {
                                                "type": "string"
                                            },
                                            "address_line2": {
                                                "type": [
                                                    "string",
                                                    "null"
                                                ]
                                            },
                                            "zip": {
                                                "type": "string"
                                            },
                                            "city": {
                                                "type": "string"
                                            },
                                            "country": {
                                                "type": "string"
                                            }
                                        },
                                        "required": [
                                            "address_line1",
                                            "zip",
                                            "city",
                                            "country"
                                        ]
                                    },
                                    "to": {
                                        "type": "object",
                                        "properties": {
                                            "company": {
                                                "type": [
                                                    "string",
                                                    "null"
                                                ]
                                            },
                                            "first_name": {
                                                "type": [
                                                    "string",
                                                    "null"
                                                ]
                                            },
                                            "last_name": {
                                                "type": [
                                                    "string",
                                                    "null"
                                                ]
                                            },
                                            "address_line1": {
                                                "type": "string"
                                            },
                                            "address_line2": {
                                                "type": [
                                                    "string",
                                                    "null"
                                                ]
                                            },
                                            "zip": {
                                                "type": "string"
                                            },
                                            "city": {
                                                "type": "string"
                                            },
                                            "country": {
                                                "type": "string"
                                            }
                                        },
                                        "required": [
                                            "address_line1",
                                            "zip",
                                            "city",
                                            "country"
                                        ]
                                    },
                                    "content": {
                                        "type": "object",
                                        "description": "Design-bound Markdown content for API Flow postcards.\n\nUse `content` only when the linked API Flow's Postcard Studio design contains Markdown areas. In the Studio, the user drags a Markdown area into the design and assigns an API field name (`API-Feldname` in the Studio UI), for example `personal_message` or `next_steps`. Those field names become the required request keys under `content`: `personal_message` in the design means the request must include `content.personal_message`.\n\nContract:\n- If the design contains Markdown areas, `content` is required and must include every configured API field name.\n- If the design contains no Markdown areas, omit `content`; sending it is rejected.\n- Unknown keys are rejected.\n- Values must be non-empty strings.\n- Each value may contain at most 8,000 characters; all values together may contain at most 20,000 characters.\n- PostPal parses and fit-checks the text against the exact designed area, font, font size, line height and width before accepting the entry.\n\nSupported PostPal Markdown v1:\n- paragraphs and blank lines\n- JSON line breaks as `\\n`\n- bold text: `**important**`\n- italic text: `*note*`\n- bullet lists: `- item`\n- numbered lists: `1. item`\n\nNot supported:\n- HTML, links, images, code, tables, quotes, headings or nested lists\n- Studio variables such as `{{first_name}}`, `{{voucher_code_1}}` or similar placeholders\n\nEmoji:\n- Common Unicode emoji are printed as colour Google Noto graphics, never as `?`. This applies to `content` values and to recipient data such as `to.first_name`.\n- The emoji catalogue ships with PostPal and cannot contain sequences newer than the pinned Noto release. Such a sequence is **not** rejected with `422`; it only surfaces when the postcard is rendered. The entry preview reports it, and in print only the affected recipient is skipped and reported.\n\nAI agent instructions:\n- Do not invent `content` keys. Use exactly the API field names configured in the linked design.\n- On `422`, inspect `meta.expected_content_keys`, `meta.allowed_markdown` and `meta.markdown_fit_errors`.\n- For missing or unknown keys, rewrite the payload keys exactly to `meta.expected_content_keys`.\n- For fit errors, shorten the text or ask the user to enlarge the Markdown area in the design. Do not use unsupported Markdown or HTML as a workaround.",
                                        "maxProperties": 10,
                                        "propertyNames": {
                                            "type": "string",
                                            "pattern": "^[a-z][a-z0-9_]{0,63}$"
                                        },
                                        "additionalProperties": {
                                            "type": "string",
                                            "description": "Markdown text for one design-defined Markdown area. Use JSON line breaks as \\n.",
                                            "minLength": 1,
                                            "maxLength": 8000,
                                            "example": "Hallo **Max**,\n\n- Pers\u00f6nlicher Vorteil\n- N\u00e4chster Schritt"
                                        },
                                        "example": {
                                            "personal_message": "Hallo **Max**,\n\nvielen Dank f\u00fcr deinen Einkauf.\n\n- Pers\u00f6nlicher Vorteil\n- N\u00e4chster Schritt",
                                            "next_steps": "1. Paket pr\u00fcfen\n2. QR-Code scannen\n3. Vorteil sichern"
                                        },
                                        "x-postpal-content-contract": {
                                            "where_fields_are_defined": "The user adds Markdown areas in Postcard Studio and assigns each area an API field name (`API-Feldname` in the Studio UI).",
                                            "request_mapping": "Each API field name becomes a required key under content, for example API field name personal_message => content.personal_message.",
                                            "required_when": "Required when the linked API Flow design contains one or more Markdown areas.",
                                            "prohibited_when": "Rejected when the linked API Flow design contains no Markdown areas.",
                                            "all_expected_keys_required": true,
                                            "unknown_keys_rejected": true
                                        },
                                        "x-postpal-markdown": {
                                            "supported": [
                                                "paragraphs",
                                                "line_breaks",
                                                "bold",
                                                "italic",
                                                "bullet_lists",
                                                "numbered_lists"
                                            ],
                                            "unsupported": [
                                                "html",
                                                "links",
                                                "images",
                                                "code",
                                                "tables",
                                                "quotes",
                                                "headings",
                                                "nested_lists",
                                                "studio_variables"
                                            ],
                                            "limits": {
                                                "max_markdown_areas_per_design": 10,
                                                "max_characters_per_content_field": 8000,
                                                "max_characters_across_content_object": 20000
                                            }
                                        },
                                        "x-postpal-agent-guidance": [
                                            "Do not invent content keys. Use exactly the API field names configured in the linked design.",
                                            "If a request fails with 422, inspect meta.expected_content_keys, meta.allowed_markdown, and meta.markdown_fit_errors.",
                                            "For missing or unknown keys, rewrite the payload keys to match meta.expected_content_keys exactly.",
                                            "For fit errors, shorten the Markdown text or ask the user to enlarge the Markdown area in the design.",
                                            "Do not use HTML, links, images, code blocks, tables, headings, quotes, nested lists, or Studio variables as workarounds."
                                        ]
                                    }
                                },
                                "required": [
                                    "flow_id",
                                    "from",
                                    "to"
                                ],
                                "example": {
                                    "flow_id": "flow-uuid",
                                    "from": {
                                        "company": "Muster GmbH",
                                        "first_name": "Markus",
                                        "last_name": "M\u00fcller",
                                        "address_line1": "Unter den Linden 1",
                                        "zip": "10117",
                                        "city": "Berlin",
                                        "country": "DE"
                                    },
                                    "to": {
                                        "company": "Beispiel AG",
                                        "first_name": "Max",
                                        "last_name": "Mustermann",
                                        "address_line1": "Musterstra\u00dfe 42",
                                        "address_line2": "c/o Familie Sommer",
                                        "zip": "10115",
                                        "city": "Berlin",
                                        "country": "DE"
                                    },
                                    "content": {
                                        "personal_message": "Hallo **Max**,\n\nvielen Dank f\u00fcr deinen Einkauf.\n\n- Pers\u00f6nlicher Vorteil\n- N\u00e4chster Schritt",
                                        "next_steps": "1. Paket pr\u00fcfen\n2. QR-Code scannen\n3. Vorteil sichern"
                                    },
                                    "qr_codes": {
                                        "voucher_url_1": "https://example.com/voucher/1",
                                        "voucher_url_2": "https://example.com/voucher/2"
                                    }
                                }
                            },
                            "examples": {
                                "markdown_and_qr_content": {
                                    "summary": "API Flow request with Markdown content and QR values",
                                    "description": "Use this shape when the linked Postcard Studio design contains both Markdown areas and QR codes with API field names.",
                                    "value": {
                                        "flow_id": "flow-uuid",
                                        "from": {
                                            "company": "Muster GmbH",
                                            "first_name": "Markus",
                                            "last_name": "M\u00fcller",
                                            "address_line1": "Unter den Linden 1",
                                            "zip": "10117",
                                            "city": "Berlin",
                                            "country": "DE"
                                        },
                                        "to": {
                                            "company": "Beispiel AG",
                                            "first_name": "Max",
                                            "last_name": "Mustermann",
                                            "address_line1": "Musterstra\u00dfe 42",
                                            "address_line2": "c/o Familie Sommer",
                                            "zip": "10115",
                                            "city": "Berlin",
                                            "country": "DE"
                                        },
                                        "content": {
                                            "personal_message": "Hallo **Max**,\n\nvielen Dank f\u00fcr deinen Einkauf.\n\n- Pers\u00f6nlicher Vorteil\n- N\u00e4chster Schritt",
                                            "next_steps": "1. Paket pr\u00fcfen\n2. QR-Code scannen\n3. Vorteil sichern"
                                        },
                                        "qr_codes": {
                                            "voucher_url_1": "https://example.com/voucher/1",
                                            "voucher_url_2": "https://example.com/voucher/2"
                                        }
                                    }
                                },
                                "markdown_content": {
                                    "summary": "API Flow request with design-bound Markdown content",
                                    "description": "Use this shape when the linked Postcard Studio design contains Markdown areas. The keys under content must match the API field names configured in the design.",
                                    "value": {
                                        "flow_id": "flow-uuid",
                                        "from": {
                                            "company": "Muster GmbH",
                                            "first_name": "Markus",
                                            "last_name": "M\u00fcller",
                                            "address_line1": "Unter den Linden 1",
                                            "zip": "10117",
                                            "city": "Berlin",
                                            "country": "DE"
                                        },
                                        "to": {
                                            "company": "Beispiel AG",
                                            "first_name": "Max",
                                            "last_name": "Mustermann",
                                            "address_line1": "Musterstra\u00dfe 42",
                                            "address_line2": "c/o Familie Sommer",
                                            "zip": "10115",
                                            "city": "Berlin",
                                            "country": "DE"
                                        },
                                        "content": {
                                            "personal_message": "Hallo **Max**,\n\nvielen Dank f\u00fcr deinen Einkauf.\n\n- Pers\u00f6nlicher Vorteil\n- N\u00e4chster Schritt",
                                            "next_steps": "1. Paket pr\u00fcfen\n2. QR-Code scannen\n3. Vorteil sichern"
                                        }
                                    }
                                },
                                "qr_content": {
                                    "summary": "API Flow request with design-bound QR values",
                                    "description": "Use this shape when the linked Postcard Studio design contains QR codes with API field names. The keys under qr_codes must match the QR API field names configured in the design.",
                                    "value": {
                                        "flow_id": "flow-uuid",
                                        "from": {
                                            "company": "Muster GmbH",
                                            "first_name": "Markus",
                                            "last_name": "M\u00fcller",
                                            "address_line1": "Unter den Linden 1",
                                            "zip": "10117",
                                            "city": "Berlin",
                                            "country": "DE"
                                        },
                                        "to": {
                                            "company": "Beispiel AG",
                                            "first_name": "Max",
                                            "last_name": "Mustermann",
                                            "address_line1": "Musterstra\u00dfe 42",
                                            "address_line2": "c/o Familie Sommer",
                                            "zip": "10115",
                                            "city": "Berlin",
                                            "country": "DE"
                                        },
                                        "qr_codes": {
                                            "voucher_url_1": "https://example.com/voucher/1",
                                            "voucher_url_2": "https://example.com/voucher/2"
                                        }
                                    }
                                },
                                "minimal": {
                                    "summary": "Minimal request without Markdown areas",
                                    "description": "Use this shape only when the linked API Flow design contains no Markdown areas and no QR codes with API field names.",
                                    "value": {
                                        "flow_id": "flow-uuid",
                                        "from": {
                                            "company": "Muster GmbH",
                                            "first_name": "Markus",
                                            "last_name": "M\u00fcller",
                                            "address_line1": "Unter den Linden 1",
                                            "zip": "10117",
                                            "city": "Berlin",
                                            "country": "DE"
                                        },
                                        "to": {
                                            "company": "Beispiel AG",
                                            "first_name": "Max",
                                            "last_name": "Mustermann",
                                            "address_line1": "Musterstra\u00dfe 42",
                                            "address_line2": "c/o Familie Sommer",
                                            "zip": "10115",
                                            "city": "Berlin",
                                            "country": "DE"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "required": true
                },
                "responses": {
                    "404": {
                        "$ref": "#/components/responses/ModelNotFoundException",
                        "description": "The referenced API Flow was not found or does not belong to this account.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "message": {
                                            "type": "string",
                                            "example": "Referenced flow not found."
                                        }
                                    },
                                    "required": [
                                        "message"
                                    ]
                                }
                            }
                        }
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationException",
                        "description": "The request payload failed validation.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "message": {
                                            "type": "string",
                                            "example": "The given data was invalid."
                                        },
                                        "errors": {
                                            "type": "object",
                                            "properties": {
                                                "segment.source": {
                                                    "type": "array",
                                                    "items": {
                                                        "type": "string",
                                                        "example": "Shopify segments are not supported by this API. Use klaviyo or csv."
                                                    }
                                                }
                                            },
                                            "additionalProperties": {
                                                "type": "array",
                                                "items": {
                                                    "type": "string"
                                                }
                                            }
                                        },
                                        "meta": {
                                            "type": "object",
                                            "properties": {
                                                "expected_content_keys": {
                                                    "type": "array",
                                                    "description": "The exact content keys required by the linked design. AI agents should use this list to repair missing or unknown keys.",
                                                    "items": {
                                                        "type": "string"
                                                    },
                                                    "example": [
                                                        "personal_message",
                                                        "next_steps"
                                                    ]
                                                },
                                                "allowed_markdown": {
                                                    "type": "array",
                                                    "description": "The only supported Markdown feature set for content values.",
                                                    "items": {
                                                        "type": "string"
                                                    },
                                                    "example": [
                                                        "paragraphs",
                                                        "line_breaks",
                                                        "bold",
                                                        "italic",
                                                        "bullet_lists",
                                                        "numbered_lists"
                                                    ]
                                                },
                                                "markdown_fit_errors": {
                                                    "type": "array",
                                                    "description": "Physical layout failures. The request was syntactically valid, but at least one Markdown value did not fit into its designed area.",
                                                    "items": {
                                                        "type": "object",
                                                        "properties": {
                                                            "key": {
                                                                "type": "string"
                                                            },
                                                            "label": {
                                                                "type": "string"
                                                            },
                                                            "available_width_mm": {
                                                                "type": "number"
                                                            },
                                                            "available_height_mm": {
                                                                "type": "number"
                                                            },
                                                            "measured_height_mm": {
                                                                "type": "number"
                                                            },
                                                            "overflow_mm": {
                                                                "type": "number"
                                                            },
                                                            "font_size_pt": {
                                                                "type": "number"
                                                            },
                                                            "line_height": {
                                                                "type": "number"
                                                            },
                                                            "block_count": {
                                                                "type": "integer"
                                                            },
                                                            "line_count": {
                                                                "type": "integer"
                                                            },
                                                            "suggested_action": {
                                                                "type": "string"
                                                            }
                                                        }
                                                    }
                                                },
                                                "expected_qr_keys": {
                                                    "type": "array",
                                                    "description": "The exact QR API field names required by the linked design. AI agents should use this list to repair missing or unknown qr_codes keys.",
                                                    "items": {
                                                        "type": "string"
                                                    },
                                                    "example": [
                                                        "voucher_url_1",
                                                        "voucher_url_2"
                                                    ]
                                                },
                                                "qr_fit_errors": {
                                                    "type": "array",
                                                    "description": "QR encoder capacity failures. The resolved URL was valid, but too large to fit into a QR code.",
                                                    "items": {
                                                        "type": "object",
                                                        "properties": {
                                                            "key": {
                                                                "type": "string"
                                                            },
                                                            "label": {
                                                                "type": "string"
                                                            },
                                                            "resolved_url_length": {
                                                                "type": "integer"
                                                            },
                                                            "suggested_action": {
                                                                "type": "string"
                                                            }
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    },
                                    "required": [
                                        "message",
                                        "errors"
                                    ]
                                },
                                "example": {
                                    "message": "The given data was invalid.",
                                    "errors": {
                                        "content.personal_message": [
                                            "Der Markdown-Inhalt passt nicht in die Fl\u00e4che \"personal_message\"."
                                        ],
                                        "content.next_steps": [
                                            "Markdown-Inhalt ist f\u00fcr diese Fl\u00e4che erforderlich."
                                        ],
                                        "content.unused_key": [
                                            "Der Markdown-Schl\u00fcssel \"unused_key\" ist in diesem API-Flow-Design nicht vorgesehen."
                                        ]
                                    },
                                    "meta": {
                                        "expected_content_keys": [
                                            "personal_message",
                                            "next_steps"
                                        ],
                                        "allowed_markdown": [
                                            "paragraphs",
                                            "line_breaks",
                                            "bold",
                                            "italic",
                                            "bullet_lists",
                                            "numbered_lists"
                                        ],
                                        "expected_qr_keys": [
                                            "voucher_url_1",
                                            "voucher_url_2"
                                        ],
                                        "markdown_fit_errors": [
                                            {
                                                "key": "personal_message",
                                                "label": "personal_message",
                                                "available_width_mm": 76.2,
                                                "available_height_mm": 82.4,
                                                "measured_height_mm": 96.8,
                                                "overflow_mm": 14.4,
                                                "font_size_pt": 10,
                                                "line_height": 1.25,
                                                "block_count": 5,
                                                "line_count": 18,
                                                "suggested_action": "K\u00fcrze den Text oder vergr\u00f6\u00dfere die Markdown-Fl\u00e4che."
                                            }
                                        ],
                                        "qr_fit_errors": [
                                            {
                                                "key": "voucher_url_1",
                                                "label": "voucher_url_1",
                                                "resolved_url_length": 2300,
                                                "suggested_action": "K\u00fcrze den QR-Wert oder die Ziel-URL."
                                            }
                                        ]
                                    }
                                }
                            }
                        }
                    },
                    "403": {
                        "$ref": "#/components/responses/AuthorizationException"
                    },
                    "201": {
                        "description": "Recipient queued. The flow is active and the entry will be processed in the next batch.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "request_id": {
                                            "type": "string"
                                        },
                                        "flow_id": {
                                            "type": "string",
                                            "format": "uuid"
                                        },
                                        "status": {
                                            "type": "string",
                                            "enum": [
                                                "queued",
                                                "accepted"
                                            ]
                                        },
                                        "queued_for": {
                                            "type": [
                                                "string",
                                                "null"
                                            ],
                                            "format": "date-time"
                                        }
                                    },
                                    "required": [
                                        "request_id",
                                        "flow_id",
                                        "status",
                                        "queued_for"
                                    ]
                                },
                                "example": {
                                    "request_id": "order-12345-attempt-1",
                                    "flow_id": "flow-uuid",
                                    "status": "queued",
                                    "queued_for": "2026-05-01T08:00:00+00:00"
                                }
                            }
                        }
                    },
                    "202": {
                        "description": "Recipient accepted. The flow is currently paused and the entry will be processed once resumed.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "request_id": {
                                            "type": "string"
                                        },
                                        "flow_id": {
                                            "type": "string",
                                            "format": "uuid"
                                        },
                                        "status": {
                                            "type": "string",
                                            "enum": [
                                                "queued",
                                                "accepted"
                                            ]
                                        },
                                        "queued_for": {
                                            "type": [
                                                "string",
                                                "null"
                                            ],
                                            "format": "date-time"
                                        }
                                    },
                                    "required": [
                                        "request_id",
                                        "flow_id",
                                        "status",
                                        "queued_for"
                                    ]
                                },
                                "example": {
                                    "request_id": "order-12345-attempt-1",
                                    "flow_id": "flow-uuid",
                                    "status": "accepted",
                                    "queued_for": null
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Unauthenticated.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "message": {
                                            "type": "string",
                                            "example": "Unauthenticated."
                                        }
                                    },
                                    "required": [
                                        "message"
                                    ]
                                }
                            }
                        }
                    },
                    "409": {
                        "description": "Duplicate X-Request-Id: replayed cached response for this request ID. The replay is unconditional \u2014 it also applies when the postcard has since been removed from its mailing in the API logs, so a resend will not re-queue it.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "request_id": {
                                            "type": "string"
                                        },
                                        "flow_id": {
                                            "type": "string",
                                            "format": "uuid"
                                        },
                                        "status": {
                                            "type": "string",
                                            "enum": [
                                                "queued",
                                                "accepted"
                                            ]
                                        },
                                        "queued_for": {
                                            "type": [
                                                "string",
                                                "null"
                                            ],
                                            "format": "date-time"
                                        }
                                    },
                                    "required": [
                                        "request_id",
                                        "flow_id",
                                        "status",
                                        "queued_for"
                                    ]
                                },
                                "example": {
                                    "request_id": "order-12345-attempt-1",
                                    "flow_id": "flow-uuid",
                                    "status": "queued",
                                    "queued_for": "2026-05-01T08:00:00+00:00"
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "X-Request-Id",
                        "in": "header",
                        "required": false,
                        "description": "Optional idempotency key. If the same value is sent twice, the second request replays the cached response (409) instead of creating a duplicate entry. Any non-empty string is valid; client-generated IDs such as order IDs work well. Note that the replay also applies after a request has been removed from its mailing in the API logs: resending the same X-Request-Id returns 409 and does not re-queue the postcard. Use the \"Wieder aufnehmen\" action in the API logs to undo a removal, or send a new X-Request-Id to queue a fresh postcard.",
                        "schema": {
                            "type": "string",
                            "example": "order-12345-attempt-1"
                        },
                        "example": "order-12345-attempt-1"
                    }
                ]
            }
        },
        "/api/v1/campaigns": {
            "post": {
                "operationId": "v1.campaigns.store",
                "description": "Create a direct-mail campaign in PostPal.\n\nCampaigns are real objects and require a live token (`pp_live_\u2026`). Test tokens are rejected for this endpoint.\n\nSign in to PostPal and create a token if your account grants API-token access.",
                "summary": "Create campaign",
                "tags": [
                    "Campaigns"
                ],
                "requestBody": {
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string"
                                    },
                                    "description": {
                                        "type": [
                                            "string",
                                            "null"
                                        ]
                                    },
                                    "metadata": {
                                        "type": [
                                            "array",
                                            "null"
                                        ],
                                        "items": {
                                            "type": "string"
                                        }
                                    },
                                    "processingType": {
                                        "type": "string",
                                        "enum": [
                                            "automatic",
                                            "one_off"
                                        ]
                                    },
                                    "addressVerification": {
                                        "type": "boolean"
                                    },
                                    "segment": {
                                        "type": "object",
                                        "properties": {
                                            "source": {
                                                "type": "string",
                                                "enum": [
                                                    "klaviyo",
                                                    "csv"
                                                ]
                                            },
                                            "id": {
                                                "type": "string"
                                            }
                                        },
                                        "required": [
                                            "source",
                                            "id"
                                        ]
                                    },
                                    "design": {
                                        "type": "object",
                                        "properties": {
                                            "postcardDesignId": {
                                                "type": "string"
                                            },
                                            "material": {
                                                "type": [
                                                    "string",
                                                    "null"
                                                ],
                                                "enum": [
                                                    "bilderdruck",
                                                    "chromo",
                                                    "recycling"
                                                ]
                                            },
                                            "refinement": {
                                                "type": [
                                                    "string",
                                                    "null"
                                                ],
                                                "enum": [
                                                    "none",
                                                    "folienkaschierung"
                                                ]
                                            }
                                        },
                                        "required": [
                                            "postcardDesignId"
                                        ]
                                    },
                                    "delivery": {
                                        "type": "object",
                                        "properties": {
                                            "deliveryPeriodEndAt": {
                                                "type": "string"
                                            },
                                            "nextMailingCreationDate": {
                                                "type": [
                                                    "string",
                                                    "null"
                                                ]
                                            }
                                        },
                                        "required": [
                                            "deliveryPeriodEndAt"
                                        ]
                                    },
                                    "billing": {
                                        "type": "object",
                                        "properties": {
                                            "paymentMethod": {
                                                "type": [
                                                    "string",
                                                    "null"
                                                ],
                                                "enum": [
                                                    "vorkasse",
                                                    "rechnung",
                                                    "guthaben"
                                                ]
                                            },
                                            "invoiceEmail": {
                                                "type": "string",
                                                "format": "email"
                                            },
                                            "invoiceCompanyName": {
                                                "type": [
                                                    "string",
                                                    "null"
                                                ]
                                            },
                                            "invoiceStreet": {
                                                "type": [
                                                    "string",
                                                    "null"
                                                ]
                                            },
                                            "invoiceStreetNumber": {
                                                "type": [
                                                    "string",
                                                    "null"
                                                ]
                                            },
                                            "invoiceCity": {
                                                "type": [
                                                    "string",
                                                    "null"
                                                ]
                                            },
                                            "invoiceZipCode": {
                                                "type": [
                                                    "string",
                                                    "null"
                                                ]
                                            },
                                            "invoiceVatNumber": {
                                                "type": [
                                                    "string",
                                                    "null"
                                                ]
                                            }
                                        }
                                    },
                                    "schedule": {
                                        "type": "object",
                                        "properties": {
                                            "sendoutInterval": {
                                                "type": [
                                                    "string",
                                                    "null"
                                                ]
                                            },
                                            "minSegmentCustomersPerOrder": {
                                                "type": [
                                                    "integer",
                                                    "null"
                                                ],
                                                "minimum": 500
                                            },
                                            "yearlyAnchorMonth": {
                                                "type": [
                                                    "integer",
                                                    "null"
                                                ],
                                                "minimum": 1,
                                                "maximum": 12
                                            },
                                            "yearlyAnchorDay": {
                                                "type": [
                                                    "integer",
                                                    "null"
                                                ],
                                                "minimum": 1,
                                                "maximum": 31
                                            }
                                        }
                                    },
                                    "vouchers": {
                                        "type": [
                                            "array",
                                            "null"
                                        ],
                                        "items": {
                                            "type": "object",
                                            "properties": {
                                                "slotNumber": {
                                                    "type": "integer",
                                                    "minimum": 1
                                                },
                                                "entries": {
                                                    "type": "array",
                                                    "items": {
                                                        "type": "object",
                                                        "properties": {
                                                            "voucherId": {
                                                                "type": "string"
                                                            },
                                                            "percentage": {
                                                                "type": "integer",
                                                                "minimum": 1,
                                                                "maximum": 100
                                                            }
                                                        },
                                                        "required": [
                                                            "voucherId",
                                                            "percentage"
                                                        ]
                                                    },
                                                    "minItems": 1
                                                }
                                            }
                                        }
                                    }
                                },
                                "required": [
                                    "name",
                                    "processingType",
                                    "addressVerification",
                                    "segment",
                                    "design",
                                    "delivery",
                                    "billing"
                                ],
                                "example": {
                                    "name": "Spring Reactivation Campaign",
                                    "description": "Direct-mail campaign created via API",
                                    "metadata": {
                                        "externalId": "erp-123"
                                    },
                                    "processingType": "one_off",
                                    "segment": {
                                        "source": "klaviyo",
                                        "id": "segment-uuid"
                                    },
                                    "design": {
                                        "postcardDesignId": "design-uuid",
                                        "material": "bilderdruck",
                                        "refinement": "folienkaschierung"
                                    },
                                    "delivery": {
                                        "deliveryPeriodEndAt": "2026-05-01"
                                    },
                                    "addressVerification": true,
                                    "billing": {
                                        "paymentMethod": "rechnung",
                                        "invoiceEmail": "billing@example.com",
                                        "invoiceCompanyName": "Example GmbH",
                                        "invoiceStreet": "Main Street",
                                        "invoiceStreetNumber": "1",
                                        "invoiceCity": "Berlin",
                                        "invoiceZipCode": "10115",
                                        "invoiceVatNumber": "DE123456789"
                                    },
                                    "vouchers": []
                                }
                            },
                            "examples": {
                                "klaviyo": {
                                    "summary": "Create a Klaviyo campaign",
                                    "value": {
                                        "name": "Spring Reactivation Campaign",
                                        "description": "Direct-mail campaign created via API",
                                        "metadata": {
                                            "externalId": "erp-123"
                                        },
                                        "processingType": "one_off",
                                        "segment": {
                                            "source": "klaviyo",
                                            "id": "segment-uuid"
                                        },
                                        "design": {
                                            "postcardDesignId": "design-uuid",
                                            "material": "bilderdruck",
                                            "refinement": "folienkaschierung"
                                        },
                                        "delivery": {
                                            "deliveryPeriodEndAt": "2026-05-01"
                                        },
                                        "addressVerification": true,
                                        "billing": {
                                            "paymentMethod": "rechnung",
                                            "invoiceEmail": "billing@example.com",
                                            "invoiceCompanyName": "Example GmbH",
                                            "invoiceStreet": "Main Street",
                                            "invoiceStreetNumber": "1",
                                            "invoiceCity": "Berlin",
                                            "invoiceZipCode": "10115",
                                            "invoiceVatNumber": "DE123456789"
                                        },
                                        "vouchers": []
                                    }
                                },
                                "csv": {
                                    "summary": "Create a CSV campaign",
                                    "value": {
                                        "name": "Spring Reactivation Campaign",
                                        "description": "Direct-mail campaign created via API",
                                        "metadata": {
                                            "externalId": "erp-123"
                                        },
                                        "processingType": "one_off",
                                        "segment": {
                                            "source": "csv",
                                            "id": "csv-file-uuid"
                                        },
                                        "design": {
                                            "postcardDesignId": "design-uuid",
                                            "material": "bilderdruck",
                                            "refinement": "folienkaschierung"
                                        },
                                        "delivery": {
                                            "deliveryPeriodEndAt": "2026-05-01"
                                        },
                                        "addressVerification": true,
                                        "billing": {
                                            "paymentMethod": "rechnung",
                                            "invoiceEmail": "billing@example.com",
                                            "invoiceCompanyName": "Example GmbH",
                                            "invoiceStreet": "Main Street",
                                            "invoiceStreetNumber": "1",
                                            "invoiceCity": "Berlin",
                                            "invoiceZipCode": "10115",
                                            "invoiceVatNumber": "DE123456789"
                                        },
                                        "vouchers": []
                                    }
                                }
                            }
                        }
                    },
                    "required": true
                },
                "responses": {
                    "201": {
                        "description": "",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "id": {
                                            "type": "string"
                                        },
                                        "slug": {
                                            "type": "string"
                                        },
                                        "object": {
                                            "type": "string",
                                            "example": "campaign"
                                        },
                                        "name": {
                                            "type": "string"
                                        },
                                        "description": {
                                            "type": "string"
                                        },
                                        "status": {
                                            "type": "string"
                                        },
                                        "metadata": {
                                            "type": "string"
                                        },
                                        "dateCreated": {
                                            "type": "string"
                                        },
                                        "dateModified": {
                                            "type": "string"
                                        },
                                        "appUrl": {
                                            "type": "string"
                                        }
                                    },
                                    "required": [
                                        "id",
                                        "slug",
                                        "object",
                                        "name",
                                        "description",
                                        "status",
                                        "metadata",
                                        "dateCreated",
                                        "dateModified",
                                        "appUrl"
                                    ]
                                }
                            }
                        }
                    },
                    "200": {
                        "description": "",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "string"
                                }
                            }
                        }
                    },
                    "403": {
                        "$ref": "#/components/responses/AuthorizationException",
                        "description": "Test API tokens cannot create campaigns.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "message": {
                                            "type": "string",
                                            "example": "Test API tokens cannot create campaigns. Use a live token to create campaigns."
                                        }
                                    },
                                    "required": [
                                        "message"
                                    ]
                                }
                            }
                        }
                    },
                    "404": {
                        "$ref": "#/components/responses/ModelNotFoundException",
                        "description": "A referenced account resource was not found.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "message": {
                                            "type": "string",
                                            "example": "Referenced design not found."
                                        }
                                    },
                                    "required": [
                                        "message"
                                    ]
                                }
                            }
                        }
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationException",
                        "description": "The request payload failed validation.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "message": {
                                            "type": "string",
                                            "example": "The given data was invalid."
                                        },
                                        "errors": {
                                            "type": "object",
                                            "properties": {
                                                "segment.source": {
                                                    "type": "array",
                                                    "items": {
                                                        "type": "string",
                                                        "example": "Shopify segments are not supported by this API. Use klaviyo or csv."
                                                    }
                                                }
                                            },
                                            "additionalProperties": {
                                                "type": "array",
                                                "items": {
                                                    "type": "string"
                                                }
                                            }
                                        }
                                    },
                                    "required": [
                                        "message",
                                        "errors"
                                    ]
                                },
                                "example": {
                                    "message": "The given data was invalid.",
                                    "errors": {
                                        "segment.source": [
                                            "Shopify segments are not supported by this API. Use klaviyo or csv."
                                        ]
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Unauthenticated.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "message": {
                                            "type": "string",
                                            "example": "Unauthenticated."
                                        }
                                    },
                                    "required": [
                                        "message"
                                    ]
                                }
                            }
                        }
                    },
                    "409": {
                        "description": "The supplied idempotency key was reused with a different payload.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "message": {
                                            "type": "string",
                                            "example": "The provided Idempotency-Key was already used for a different request."
                                        }
                                    },
                                    "required": [
                                        "message"
                                    ]
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "Idempotency-Key",
                        "in": "header",
                        "required": false,
                        "description": "Optional key for safely retrying the same create request without creating duplicate campaigns.",
                        "schema": {
                            "type": "string",
                            "example": "campaign-create-001"
                        },
                        "example": "campaign-create-001"
                    }
                ]
            }
        }
    },
    "components": {
        "responses": {
            "ModelNotFoundException": {
                "description": "Not found",
                "content": {
                    "application/json": {
                        "schema": {
                            "type": "object",
                            "properties": {
                                "message": {
                                    "type": "string",
                                    "description": "Error overview."
                                }
                            },
                            "required": [
                                "message"
                            ]
                        }
                    }
                }
            },
            "ValidationException": {
                "description": "Validation error",
                "content": {
                    "application/json": {
                        "schema": {
                            "type": "object",
                            "properties": {
                                "message": {
                                    "type": "string",
                                    "description": "Errors overview."
                                },
                                "errors": {
                                    "type": "object",
                                    "description": "A detailed description of each field that failed validation.",
                                    "additionalProperties": {
                                        "type": "array",
                                        "items": {
                                            "type": "string"
                                        }
                                    }
                                }
                            },
                            "required": [
                                "message",
                                "errors"
                            ]
                        }
                    }
                }
            },
            "AuthorizationException": {
                "description": "Authorization error",
                "content": {
                    "application/json": {
                        "schema": {
                            "type": "object",
                            "properties": {
                                "message": {
                                    "type": "string",
                                    "description": "Error overview."
                                }
                            },
                            "required": [
                                "message"
                            ]
                        }
                    }
                }
            }
        },
        "securitySchemes": {
            "bearerAuth": {
                "type": "http",
                "scheme": "bearer",
                "bearerFormat": "API token",
                "description": "Use a PostPal API token created in the authenticated app settings."
            }
        }
    },
    "security": [
        {
            "bearerAuth": []
        }
    ],
    "tags": [
        {
            "name": "Campaigns",
            "description": "Create and manage direct-mail campaigns."
        },
        {
            "name": "Postcards",
            "description": "Submit recipient addresses to API Flows for batch direct-mail delivery."
        }
    ]
}