# Parasend API: the whole reference Base URL: https://api.parasend.com/v1 Rules for agents: - Start with a test key (`ps_test_…`). Test mode sends no e-mail or SMS, the one-time code is always `000000`, and nothing is billed. Switch to a live key (`ps_live_…`) only when the integration works. - Send an `Idempotency-Key` header with every POST. A retry with the same key returns the first answer and never sends twice. - Never put a kennitala, an e-mail address or other personal data in a URL, a log or `metadata`. - Keep the delivery ID. The Delivery Certificate (PDF) and the evidence record (XML) are the proof; fetch and keep them once the delivery is confirmed. - Check the `Parasend-Signature` header on every webhook before trusting it. --- # Introduction Source: https://parasend.com/docs The Parasend API sends documents that prove they arrived. You send a **delivery** to one recipient: one or more files, a title and how the recipient must identify themselves. Parasend tells the recipient by e-mail, SMS or both. The recipient opens the link, verifies at the level you chose (eID, an ID document check, an SMS code, or the link alone) and confirms receipt. Every step is written into a sealed **evidence record**, and every delivery has a **Delivery Certificate** you can keep, print or hand to a court. It is a REST API that speaks JSON over HTTPS, with signed webhooks for everything that happens. ```bash curl -X POST "https://api.parasend.com/v1/deliveries" \ -H "Authorization: Bearer $PARASEND_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "recipient": { "email": "anna.bjork@example.com", "verification_level": "high" }, "files": ["fil_01M6P3Q7Z8XK2VQ4N9R5T1W3YB"], "title": "Final notice: invoice 2026-114", "language": "en" }' ``` ## Where to start - **[Quickstart](/docs/quickstart)**: your first delivery in five minutes, with a test key. - **[Authentication](/docs/authentication)**: test keys and live keys, and what each permission allows. - **[Test and live mode](/docs/test-mode)**: test mode sends nothing and costs nothing, so try everything there first. - **[Receiving webhooks](/docs/webhooks)**: be told when the recipient opens, verifies and confirms. - **[API reference](/docs/api/deliveries)**: every endpoint, with examples in cURL, Node.js, Python, C#, Java and PHP. ## How it fits together 1. **Upload** the files with [Upload a file](/docs/api/files/upload). Each gets a `fil_` ID and its SHA-256. 2. **Send** a delivery with [Create a delivery](/docs/api/deliveries/create), or many at once with a [batch](/docs/api/batches). 3. **Follow** it: the events `delivery.notified`, `delivery.opened`, `delivery.confirmed` and the rest reach your [webhook endpoint](/docs/api/webhooks/create), or you [list events](/docs/api/events/list). 4. **Prove** it: download the [Delivery Certificate](/docs/api/evidence/certificate) (PDF) and the [evidence record](/docs/api/evidence/record) (XML). Anyone holding the certificate can [check the record](/docs/api/verify/check) at parasend.com/verify. Reminders follow automatically if the recipient does not confirm, by the [follow-up policy](/docs/api/follow-up-policies) you choose. Your notices can carry your own look, sending address and link domain through [brand profiles](/docs/api/brand-profiles). ## Base URL ```text https://api.parasend.com/v1 ``` The same address serves test keys and live keys; the key decides the mode. Parasend runs in the EU, in AWS Ireland. ## For AI agents Every page here is also Markdown: add `.md` to the address, or use **Copy for LLM** at the top of a page. [llms.txt](/docs/llms.txt) indexes the docs, [llms-full.txt](/docs/llms-full.txt) holds the whole API in one file, and [openapi.json](/docs/openapi.json) is the OpenAPI 3.1 description. --- # Quickstart Source: https://parasend.com/docs/quickstart Send your first registered delivery in five minutes. Everything here runs in **test mode**: nothing is sent to anyone and nothing is billed. ## 1. Get a test key [Create a free account](https://app.parasend.com/register), or [sign in](https://app.parasend.com/sign-in). In the portal, open **API keys** and make a key in test mode. Test keys start with `ps_test_`. Keep the key out of your code: ```bash export PARASEND_KEY=ps_test_... ``` ## 2. Upload a file Upload the document to send. The answer is the file's ID and its SHA-256. ```bash curl -X POST "https://api.parasend.com/v1/files" \ -H "Authorization: Bearer $PARASEND_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -F "file=@invoice-2026-114.pdf" ``` ```json { "id": "fil_01M6P3Q7Z8XK2VQ4N9R5T1W3YB", "object": "file", "name": "invoice-2026-114.pdf", "bytes": 48213, "sha256": "9f2c…", "created": "2026-11-06T10:12:03Z" } ``` ## 3. Send the delivery Send it to a recipient, with the file's ID. Use your own e-mail address: in test mode it receives nothing, but you will open the link yourself in the next step. ```bash curl -X POST "https://api.parasend.com/v1/deliveries" \ -H "Authorization: Bearer $PARASEND_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "recipient": { "name": "Anna Björk Jónsdóttir", "email": "anna.bjork@example.com", "verification_level": "low" }, "files": ["fil_01M6P3Q7Z8XK2VQ4N9R5T1W3YB"], "title": "Final notice: invoice 2026-114", "language": "en" }' ``` The answer is the delivery. Keep its `id`. Its `delivery_url` is the recipient's link. ## 4. Open it as the recipient Open the `delivery_url` in a browser and confirm receipt. With `verification_level: "low"` the link alone is enough. To try an SMS code instead, send with `"verification_level": "medium"` and a `phone`: in test mode no SMS is sent and the code is always `000000`. ## 5. See what happened List the delivery's events: ```bash curl "https://api.parasend.com/v1/deliveries/dlv_.../events" \ -H "Authorization: Bearer $PARASEND_KEY" ``` You will see `delivery.created`, `delivery.notified`, `delivery.opened` and `delivery.confirmed`. In production you would not poll: [add a webhook endpoint](/docs/api/webhooks/create) and Parasend sends each event as it happens. ## 6. Download the proof ```bash curl "https://api.parasend.com/v1/deliveries/dlv_.../evidence/document" \ -H "Authorization: Bearer $PARASEND_KEY" \ -o delivery-certificate.pdf ``` That is the Delivery Certificate: who sent what to whom, how the recipient verified, and when each step happened. ## Next - [Receiving webhooks](/docs/webhooks), and checking their signature. - [Authentication](/docs/authentication): permissions, and moving to a live key. - [Batches](/docs/api/batches): many deliveries at once, with follow-up. - [Follow-up policies](/docs/api/follow-up-policies): the reminders a delivery sends while it waits. --- # Authentication Source: https://parasend.com/docs/authentication Every request carries an API key as a Bearer token: ```bash curl "https://api.parasend.com/v1/deliveries" \ -H "Authorization: Bearer $PARASEND_KEY" ``` ## Test keys and live keys | Key | Starts with | What it does | |---|---|---| | Test | `ps_test_` | Works in test mode: sends nothing, bills nothing. See [test and live mode](/docs/test-mode). | | Live | `ps_live_` | Sends real notices to real people, and is billed. | The key decides the mode. The two modes never see each other's data: a test key asking for a live delivery gets `404`, the same as for one that does not exist. ## Making a key Keys are made, rolled and deleted only in the portal, by a person signed in with eID: open **API keys**. An API key cannot make or change keys. A live key's secret is shown once, when the key is made or rolled; a test key's can be shown again in the portal. Keep keys in your secret store, never in code or in a URL. Each key has: - **Permissions**, per resource: for example `deliveries.create` and `deliveries.read`, `files.create`, `evidence.read`, `webhooks.create`. A request outside them answers `403 permission_denied`, naming the permission it needs in `param`. - **Who sees what**: a key sees all of the organisation's deliveries, or only its own holder's. - Optionally an **expiry date**, a **monthly cap** on deliveries, and an **IP allowlist**. ## Rolling a key Roll a key in the portal when it may have leaked, or on a schedule. The new secret works at once; the old one keeps working for 24 hours, so you can switch without downtime. ## When it fails | Status | Code | Why | |---|---|---| | 401 | `api_key_missing` | No `Authorization: Bearer` header. | | 401 | `api_key_invalid` | Not a key Parasend issued, or a rolled key's old secret after its 24 hours. | | 401 | `api_key_expired` | The key has passed its expiry date. | | 401 | `api_key_revoked` | The key was deleted, or the account is closed. | | 403 | `api_key_ip_not_allowed` | The request came from an address outside the key's IP allowlist. | | 403 | `permission_denied` | The key lacks the permission named in `param`. | Every code is listed at [error codes](/docs/errors). --- # Test and live mode Source: https://parasend.com/docs/test-mode Every account has two modes. The API key decides which one a request uses: `ps_test_` keys work in test mode, `ps_live_` keys in live mode. Both use the same address, `https://api.parasend.com/v1`. ## What test mode does - **Sends nothing.** Notices by e-mail and SMS go to a test sink, never to a real address or phone. The delivery still moves on as if they were sent: you get `delivery.notified` and the rest. - **Costs nothing.** Test deliveries are never billed and do not use the free allowance. - **Keeps the two apart.** Test mode and live mode never see each other's deliveries, files, batches or events. Asking for an object from the other mode answers `404`, the same as for one that does not exist. - **Runs the same code.** Everything else works as in live mode: validation, idempotency, webhooks, the recipient's page, the certificate. Deliveries, batches and webhook endpoints carry `livemode`. A webhook endpoint made with a test key receives only test-mode events, and one made with a live key only live ones. ## The recipient's side in test mode Open the delivery's `delivery_url` yourself to act as the recipient. Each `verification_level` works like this in test mode: | Level | In test mode | |---|---| | `low` | The link alone, as in live mode. | | `medium` | No SMS is sent. The code is always `000000`. | | `substantial` | The ID document check runs in the provider's sandbox. | | `high` | eID sign-in goes through the eID providers' test systems: sign in with a test identity, never a real one. | ## Moving to live 1. Make a live key in **API keys** in the portal, with only the permissions it needs. 2. Store it in your secret store, and switch the key your code reads. Nothing else changes: same address, same requests. 3. Choose a plan in the portal before you send more than the free allowance. A live key sends real notices to real people. Keep it out of logs, URLs and front-end code. --- # Handling errors Source: https://parasend.com/docs/handling-errors Parasend answers with the usual HTTP status codes: `2xx` worked, `4xx` means the request needs to change, `5xx` means something went wrong on our side. Every error has the same body: ```json { "error": { "type": "invalid_request", "code": "recipient_phone_invalid", "message": "recipient.phone must be in E.164 format (e.g. +3546661234).", "param": "recipient.phone", "doc_url": "https://parasend.com/docs/errors/recipient_phone_invalid", "request_id": "req_01M6P3QD4H8WZ2KX7N1R9T5V3B" } } ``` | Field | What it is | |---|---| | `type` | The group the error belongs to (below). | | `code` | A stable, specific code. Branch on this, never on `message`. Every code has its own page under [error codes](/docs/errors). | | `message` | A sentence for a person to read. It can change. | | `param` | When one parameter caused it, its name, as a dotted path such as `recipient.phone`. | | `doc_url` | The code's page in these docs: what it means and what to do. | | `request_id` | The request's ID, also in the `Request-Id` header. Give it to us when you ask for help. | A few errors carry more fields, named on their own page. ## Error types | Type | Status | What to do | |---|---|---| | `invalid_request` | 400, sometimes 404 | Fix the request. `code` and `param` say what is wrong. | | `authentication_error` | 401 | Check the API key. See [authentication](/docs/authentication). | | `permission_error` | 403 | The key lacks a permission, or the action is not allowed for this account. | | `not_found` | 404 | No such object in this mode, or no such route. | | `conflict` | 409 | The object's state does not allow this now, for example a reminder before the first notice has gone out (`remind_state_invalid`). | | `payload_too_large` | 413 | The body or file is too large. | | `rate_limited` | 429 | Wait, then try again. See [rate limits](/docs/rate-limits). | | `api_error` | 500, 503 | Try again with the same `Idempotency-Key`. If it keeps happening, contact us with the `request_id`. | ## Retrying safely - Retry `429`, `500`, `503` and network failures, with exponential backoff. When the answer has a `Retry-After` header, wait at least that many seconds. - Send the **same** `Idempotency-Key` when you retry a `POST`. If the first attempt did go through, you get its answer back instead of a second delivery. See [idempotency](/docs/idempotency). - Do not retry other `4xx` errors unchanged: they will fail the same way. ## Request IDs Every answer carries a `Request-Id` header, such as `req_01M6P3QD4H8WZ2KX7N1R9T5V3B`. Log it with your own record of the call. You can also send your own `Request-Id` starting with `req_`, and Parasend uses it. --- # Idempotency Source: https://parasend.com/docs/idempotency Networks fail. A request can time out after Parasend has already sent the delivery, and a plain retry would send it twice. To make retries safe, every `POST` carries an `Idempotency-Key` header: a value you choose, unique for each new action. ```bash curl -X POST "https://api.parasend.com/v1/deliveries" \ -H "Authorization: Bearer $PARASEND_KEY" \ -H "Idempotency-Key: 5f0c6a1e-8a3b-4d2e-9c71-2b4f6e8d0a13" \ -H "Content-Type: application/json" \ -d '{ ... }' ``` ## How it works - **First request with a key:** Parasend does the work and keeps the answer for 24 hours. - **The same key and the same body within 24 hours:** Parasend does nothing new and returns the stored answer, with the header `Idempotent-Replayed: true`. - **The same key with a different body:** `409 idempotency_key_reused`. A key belongs to one request. - **No key on a `POST`:** `400 idempotency_key_missing`. Only successful answers are kept. If the first attempt failed with an error, a retry with the same key runs again, so you can fix a problem and retry. Keys are kept per account and mode, and per method and path: your keys never meet another customer's, and a key used in test mode does not affect live mode. ## Choosing keys - 1 to 255 characters of `A–Z`, `a–z`, `0–9`, `_` and `-`. Anything else answers `400 idempotency_key_invalid`. - A UUID works well. Better still, derive the key from your own record, such as `invoice-2026-114-notice`, so a restart of your system cannot send the same notice twice. - Make a new key for each new action, and reuse it only to retry that action. Only `POST` needs a key. `GET`, `PATCH` and `DELETE` requests carry none: repeating one leaves the same result. Our code examples send a key on every `POST`. --- # Pagination Source: https://parasend.com/docs/pagination Endpoints that list objects, such as [List deliveries](/docs/api/deliveries/list), answer one page at a time: ```json { "object": "list", "data": [ { "id": "dlv_01M6P3R2…", "object": "delivery" } ], "has_more": true, "next_cursor": "eyJjIjoiMjAyNi0xMS0wNlQw…" } ``` | Parameter | What it does | |---|---| | `limit` | How many objects on a page. Default 25, at most 100. | | `cursor` | Where to continue: the `next_cursor` from the page before. Leave it out for the first page. | Pages run newest first. While `has_more` is `true`, ask again with `cursor` set to `next_cursor`: ```bash curl "https://api.parasend.com/v1/deliveries?limit=100&cursor=eyJjIjoiMjAyNi0xMS0wNlQw…" \ -H "Authorization: Bearer $PARASEND_KEY" ``` ```js let cursor; do { const url = new URL('https://api.parasend.com/v1/deliveries'); url.searchParams.set('limit', '100'); if (cursor) url.searchParams.set('cursor', cursor); const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}` } }); const page = await res.json(); for (const delivery of page.data) handle(delivery); cursor = page.has_more ? page.next_cursor : undefined; } while (cursor); ``` A cursor is opaque: pass it back as it is, and never build one yourself. One that has been changed answers `400 cursor_invalid`; a `limit` outside the range answers `400 limit_invalid`. Filters, such as `status` on deliveries, stay the same from page to page: send them with every request. --- # Rate limits Source: https://parasend.com/docs/rate-limits There is no fixed limit on requests per second for an API key today. Send at the pace your work needs. To send many deliveries at once, use a [batch](/docs/api/batches) rather than thousands of single calls: one request, and Parasend paces the notices. Some actions have limits of their own. Each answers `429` with a code that says which limit was reached: | Limit | Code | What to do | |---|---|---| | The key's monthly cap of deliveries, if you set one in **API keys** | `monthly_delivery_cap_exceeded` | Raise the cap in the portal, or wait for the next month. | | The free plan's daily number of deliveries | `free_daily_cap_reached` | Choose a plan in the portal, or wait until tomorrow. | | Your monthly spending cap, if you set one | `spending_cap_reached` | Raise the cap in the portal, or wait for the next month. | | Manual reminders: 10 per recipient a day | `remind_rate_limited` | Try again tomorrow. Automatic reminders from the [follow-up policy](/docs/api/follow-up-policies) do not count. | | The [record check](/docs/api/verify/check): 120 lookups an hour from one address, and 20 lookups that find nothing in 10 minutes | `rate_limited` | Wait the seconds in `Retry-After`. | Limits on test-mode deliveries follow the key's own cap only: test mode is never billed, so the free and spending limits do not apply to it. ## Headers An endpoint with a fixed limit, such as the record check, tells you where you stand on every answer: | Header | What it says | |---|---| | `RateLimit-Limit` | How many requests the window allows. | | `RateLimit-Remaining` | How many are left in this window. | | `RateLimit-Reset` | Seconds until the window starts again. | | `Retry-After` | On a `429`: seconds to wait before trying again. | ## Handling a 429 Wait as long as `Retry-After` says when it is there; otherwise back off exponentially, starting at one second. Retry a `POST` with the same `Idempotency-Key`, so a retry can never send twice. A `429` for a cap or a plan limit will not clear by waiting a few seconds: the code says which it is, so show it to a person instead of retrying in a loop. --- # Versioning Source: https://parasend.com/docs/versioning The version is in the address: `https://api.parasend.com/v1`. Version 1 is the current one. ## What can change within v1 We keep adding to v1 without breaking code that uses it. Within v1 we may: - add new endpoints; - add optional parameters to requests; - add fields to answers and to event `data`; - add event types, error codes and values to lists of states and reasons; - change the wording of `message` in errors. So your code should ignore fields it does not know, handle event types it does not know (or subscribe only to the ones it uses), branch on error `code` and never on `message`, and treat an unknown status as "not finished". ## What makes a new version A change that would break working code, such as removing a field, renaming one, or changing what a value means, comes in a new major version, `/v2`. v1 keeps working after v2 arrives, and the [changelog](/docs/changelog) says when each change comes. ## Old names A few fields and parameters have newer names. The old names are still accepted, but these docs show only the new ones. Use the names you see here. --- # Changelog Source: https://parasend.com/docs/changelog Changes to the Parasend API, newest first. Changes within v1 never break working code: see [versioning](/docs/versioning). ## November 2026: API v1 The first public version of the Parasend API. - Registered deliveries with four verification levels: eID, an ID document check, an SMS code, or the link alone. - Files, batches of deliveries, follow-up policies with automatic reminders, and saved wordings. - Events and signed webhooks for every step, with webhook endpoints filtered by event type and by the API key that sent the delivery. - The Delivery Certificate (PDF), the evidence record (XML), and the public record check. - Brand profiles, brand assets and link domains, so notices carry your look, sending address and domain. - Test mode on the same address as live mode, with `ps_test_` keys. - These docs, every page also as Markdown, with [llms.txt](/docs/llms.txt), [llms-full.txt](/docs/llms-full.txt) and [openapi.json](/docs/openapi.json). --- # Receiving webhooks Source: https://parasend.com/docs/webhooks Parasend tells your system about delivery events (created, notified, opened, confirmed, bounced, sealed …) by sending an HTTPS `POST` to the endpoint you register with `POST /v1/webhook_endpoints`. This guide covers what arrives, how to answer, and how to verify that a request really came from Parasend. The verification code below is tested in Parasend's own CI against the code that signs the requests, so it stays correct as the service evolves. ## Your endpoint's address - **HTTPS on port 443, to a host name.** Not plain `http`, not another port, not an IP address, and no user name or password in the URL. - **Public addresses only.** Every address the host name resolves to (A and AAAA) must be on the public internet. Names such as `localhost` or `*.local`, and names that resolve to private, loopback or link-local addresses (`10.x`, `192.168.x`, `127.x`, `169.254.x`, `fc00::/7` …), are refused. - **Checked twice.** When you register the endpoint or change its URL — the request is refused with `webhook_url_not_public`, `webhook_url_unresolvable` or `webhook_url_port_not_allowed` — and again on every connection. If the name later resolves to a non-public address, Parasend does not connect; the attempt fails with the error `destination_not_public` and is retried on the usual schedule. - The OAuth 2.0 `token_url` of an endpoint follows the same rules. ## Which events an endpoint gets An endpoint gets the events of its own mode (test or live) whose type it subscribes to (`event_types`; empty means every type). By default that is every delivery's events, whichever API key or person sent it. To limit an endpoint to some senders (*Uppruni* in the portal), set `delivery_sources`: ```json { "delivery_sources": { "api_keys": ["key_01K7C5Y8A0B1C2D3E4F5G6H7J8"], "portal": false } } ``` - **`api_keys`:** deliveries sent with these keys. A key keeps matching after it is rolled or passed to a new holder. - **`portal: true`:** also deliveries that members sent from the portal. - **`null`** (the default) means every delivery. - **Which events are filtered.** Only events tied to a delivery are filtered, by that delivery's sender. Events not tied to a delivery always go through. - **When a change applies.** A change applies to events from then on. Retries already queued still go ahead. - **Test and replay.** A test event or a replay always reaches the endpoint. ## What you receive ```http POST /your/endpoint HTTP/1.1 Content-Type: application/json User-Agent: Parasend-Webhooks/1.0 (+https://parasend.com/docs/webhooks) Parasend-Signature: t=1791737791,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd Parasend-Event-Id: evt_01K7C5Z2W8G9S1V3N4T6R8Y0QX Parasend-Event-Type: delivery.confirmed {"id":"evt_01K7C5Z2W8G9S1V3N4T6R8Y0QX","object":"event","type":"delivery.confirmed","created":"2026-10-11T16:56:31.000Z","resource":{"type":"delivery","id":"dlv_01K7C5Y8A0B1C2D3E4F5G6H7J8"},"batch_id":null,"service_level":"standard","reason":null,"data":null} ``` - **The body is thin.** It names the event and the resource — never the recipient's personal data or file names. Fetch the resource (`GET /v1/deliveries/{id}`) with your API key when you need its current state. - **`Parasend-Replay: true`** marks a request you replayed by hand; **`Parasend-Test: true`** marks a test event. - **`Authorization`** is added when you configured a static bearer token or OAuth 2.0 client credentials on the endpoint. - **`delivery.email_opened` is indicative only.** It comes from an image in the e-mail. Opens that look automatic are left out: ones Amazon SES flags as a bot, Apple Mail loading every image in advance, known automated programs, and opens within a minute of the e-mail arriving (security scanners). Some can still get through, and mail programs that block images report no open at all. `delivery.opened` (the recipient opened the link and chose a way to sign in) and `delivery.confirmed` are the events that carry weight. Loading the page alone is not an open: mail-security scanners load links in a browser before anyone reads the e-mail. ## How to answer - Answer with any **2xx** status within **10 seconds**. Anything else — another status, a timeout, a redirect (redirects are not followed) — counts as a failed attempt. - **Answer first, work later.** Store the event and process it in the background, so a slow job never causes a retry. ## Retries, duplicates and order - A failed attempt is retried after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours, 12 hours, 24 hours and 24 hours — nine attempts over about three days. After that the event stays in the endpoint's log, your organisation's admins are told, and you can send it again with `POST /v1/webhook_endpoints/{id}/replay`. `GET /v1/webhook_endpoints/{id}/attempts` shows every attempt and its outcome. - **Delivery is at least once.** The same event can arrive more than once: keep the event `id` (also in `Parasend-Event-Id`) and ignore one you have already processed. - **Order is not guaranteed.** A retry can arrive after a later event. Order by `created`, or simply fetch the resource's current state when an event arrives. - **A replay ends the retries.** When a replay gets a 2xx answer, Parasend stops any retries still due for that event on that endpoint. ## Verifying the signature Every request is signed with your endpoint's signing secret (shown once, when you create the endpoint): ``` Parasend-Signature: t=,v1=."> ``` For 24 hours after you roll the secret, the header carries two `v1` values — one made with the new secret, one with the previous — so you can switch at your own pace. Accept the request when **any** `v1` matches the secret you hold. To verify: 1. Take the **raw body bytes exactly as received.** Do not parse and re-serialise the JSON first — any change in spacing or key order breaks the signature. 2. Split the header into `t` and `v1`. 3. Reject the request if `t` is more than **5 minutes** from your clock. This stops someone from replaying a captured request later. (A replay you trigger yourself is signed afresh, with a new `t`.) 4. Compute HMAC-SHA256 of `t`, a full stop, and the raw body, with your secret as the key, and compare it to `v1` **in constant time**. Answer `400` when verification fails, and do not process the event. ### Node.js ```js // file: docs/examples/verify-webhook.mjs // Verify a Parasend webhook signature — Node.js 18+, no dependencies. import { createHmac, timingSafeEqual } from 'node:crypto'; export const TOLERANCE_SECONDS = 5 * 60; /** * True only if `header` (the Parasend-Signature value) is a valid signature of `rawBody` — the exact bytes received, * before any JSON parsing — made with `secret` within the last five minutes. Never throws on bad input. * For 24 hours after a secret roll the header carries two v1 values, one per secret: any one may match. */ export function verifyParasendSignature(secret, header, rawBody, nowSeconds = Math.floor(Date.now() / 1000), toleranceSeconds = TOLERANCE_SECONDS) { if (!header) return false; let t = NaN; const v1s = []; for (const item of header.split(',')) { const i = item.indexOf('='); if (i <= 0) return false; const key = item.slice(0, i).trim(); const value = item.slice(i + 1).trim(); if (key === 't') t = Number(value); else if (key === 'v1') v1s.push(value); } if (!Number.isInteger(t) || v1s.length === 0 || !v1s.every((v) => /^[0-9a-f]{64}$/.test(v))) return false; if (Math.abs(nowSeconds - t) > toleranceSeconds) return false; // too old, or from the future: a replayed request const body = typeof rawBody === 'string' ? Buffer.from(rawBody, 'utf8') : rawBody; const expected = createHmac('sha256', secret).update(`${t}.`).update(body).digest(); return v1s.some((v) => timingSafeEqual(Buffer.from(v, 'hex'), expected)); // each comparison in constant time } ``` With Express, read the body raw on the webhook route only: ```js import express from 'express'; import { verifyParasendSignature } from './verify-webhook.mjs'; const app = express(); app.post('/webhooks/parasend', express.raw({ type: 'application/json' }), (req, res) => { if (!verifyParasendSignature(process.env.PARASEND_WEBHOOK_SECRET, req.get('Parasend-Signature'), req.body)) { return res.sendStatus(400); } const event = JSON.parse(req.body.toString('utf8')); // store `event` (deduplicate on event.id), then process it in the background res.sendStatus(204); }); ``` ### Python ```python # file: docs/examples/verify_webhook.py """Verify a Parasend webhook signature - Python 3.8+, standard library only.""" import hashlib import hmac import time from typing import Optional TOLERANCE_SECONDS = 5 * 60 HEX = set("0123456789abcdef") def verify_parasend_signature(secret: str, header: Optional[str], raw_body: bytes, now: Optional[int] = None, tolerance: int = TOLERANCE_SECONDS) -> bool: """True only if `header` (the Parasend-Signature value) is a valid signature of `raw_body` - the exact bytes received, before any JSON parsing - made with `secret` within the last five minutes. For 24 hours after a secret roll the header carries two v1 values, one per secret: any one may match.""" if not header: return False t_value, v1s = "", [] for item in header.split(","): key, sep, value = item.partition("=") if not sep or not key.strip(): return False if key.strip() == "t": t_value = value.strip() elif key.strip() == "v1": v1s.append(value.strip()) try: t = int(t_value) except ValueError: return False if not v1s or any(len(v1) != 64 or not set(v1) <= HEX for v1 in v1s): return False now = int(time.time()) if now is None else now if abs(now - t) > tolerance: # too old, or from the future: a replayed request return False expected = hmac.new(secret.encode("utf-8"), str(t).encode("ascii") + b"." + raw_body, hashlib.sha256).hexdigest() return any(hmac.compare_digest(expected, v1) for v1 in v1s) # each comparison in constant time ``` With Flask, `request.get_data()` gives the raw bytes: ```python import json import os from flask import Flask, request from verify_webhook import verify_parasend_signature app = Flask(__name__) @app.post("/webhooks/parasend") def parasend_webhook(): raw = request.get_data() if not verify_parasend_signature(os.environ["PARASEND_WEBHOOK_SECRET"], request.headers.get("Parasend-Signature"), raw): return "", 400 event = json.loads(raw) # store `event` (deduplicate on event["id"]), then process it in the background return "", 204 ``` ## Testing your endpoint `POST /v1/webhook_endpoints/{id}/test` sends a signed test event to your endpoint — `delivery.created` by default, or any event type you name in `{"event_type": "…"}`. It carries `Parasend-Test: true`, and its outcome appears in the endpoint's attempts. ## Keep the secret secret Store the signing secret like a password — in your secret manager, never in source code or logs. Anyone who has it can make requests that pass verification. If it may have leaked, roll it (`POST /v1/webhook_endpoints/{id}/roll_secret`, or "Endurnýja" in the portal): the new secret is shown once, and the previous one keeps signing for 24 hours. --- # Single sign-on: Okta Source: https://parasend.com/docs/sso/okta Your staff sign in to Parasend with their Okta account. With provisioning (SCIM), Okta also adds and suspends them in Parasend, and Push Groups can set their role. Setting it up takes about 20 minutes. You need to be an admin in Parasend, on Plan 1–3 or Enterprise. In Okta, you need to be a super admin or an app admin. ## Before you start - **Verify your e-mail domain** in Parasend under **Team → Domains**. Single sign-on can be turned on only once a domain is verified. When staff sign in with SSO and enter their work e-mail, the domain tells Parasend where to send them. - **Open the Set up dialog** in Parasend under **Sign-in & SSO → Set up**, and choose **Okta**. It shows the addresses to paste into Okta, each with a copy button. Keep it open next to this guide. ## 1. Create the app in Okta 1. In the Okta Admin Console, go to **Applications → Applications → Create App Integration**, choose **SAML 2.0**, and name the app, for example "Parasend". 2. Under **Configure SAML**, paste these from Parasend's Set up dialog: - **Single sign-on URL** (keep "Use this for Recipient URL and Destination URL" ticked); - **Audience URI (SP Entity ID)**. 3. Set **Name ID format** to **Unspecified**, and keep **Application username** as Okta username. 4. Under **Attribute Statements**, add one: name `parasend_user_id`, value `user.id`. - Parasend links each person by this value. It is Okta's own user ID, which never changes. - The e-mail address is never used to link anyone. Without the attribute, the test fails. - Optionally, also add `firstName` = `user.firstName` and `lastName` = `user.lastName`. Parasend only shows them. 5. Choose **Preview the SAML Assertion** and check that `parasend_user_id` is in it. Then finish. 6. On the **Sign On** tab, copy the **Metadata URL**. In Parasend's dialog, paste it under step 3 and choose **Register provider**. 7. On the **Assignments** tab, assign the people or groups who should use Parasend. ## 2. Test, then turn on 1. In Parasend, choose **Test with your own account**. You sign in at Okta and come back to Parasend. You must be assigned to the app yourself. 2. Parasend shows whom Okta signed in. If it is you, choose **Yes, this is me**. That Okta account is then linked to your electronic ID. 3. Choose **Turn on** and confirm with your electronic ID. From then on, staff choose **Use Microsoft, Okta, Google or SAML?** on Parasend's sign-in page and enter their work e-mail, or open Parasend from their Okta dashboard. The first time, each person confirms who they are once with their electronic ID. After that, their Okta account is enough. ## 3. Require SSO (optional) - **What it does:** when SSO is required, members get into your Parasend account only through Okta. Someone you deactivate in Okta can no longer get in. - **If Okta fails:** the owner and up to two admins you name can still sign in with an electronic ID. - **Before you turn it on:** Parasend names the members who have never signed in through Okta. Make sure Okta has them. - **API keys** keep working. To turn it on, sign in with SSO, choose **Require SSO**, and confirm with your electronic ID. ## 4. Provisioning (SCIM) With provisioning, Okta sends each change to Parasend at once. It adds people when you assign them and suspends them when you unassign or deactivate them. 1. In Parasend, under **Provisioning (SCIM)**, choose **Create token** and confirm with your electronic ID. Copy the token: it is shown only once. 2. In Okta, on the app's **General** tab, edit **App Settings**, set **Provisioning** to **SCIM**, and save. 3. On the **Provisioning** tab, under **Integration**, choose **Edit** and fill in: - **SCIM connector base URL**: the SCIM URL from Parasend; - **Unique identifier field for users**: `userName`; - **Supported provisioning actions**: Push New Users and Push Profile Updates, and Push Groups if you want roles from groups; - **Authentication Mode**: HTTP Header, with the token as the Bearer token. 4. Choose **Test Connector Configuration**, then **Save**. 5. Under **Provisioning → To App**, choose **Edit** and enable **Create Users**, **Update User Attributes** and **Deactivate Users**. Leave **Sync Password** off, and save. 6. People assigned before provisioning was on aren't sent yet. On the **Assignments** tab, choose **Provision User** for them. What each change in Okta does in Parasend: - **Assigned:** the person is listed under **Provisioned, not signed in yet** on the Team page. They join your team at their first SSO sign-in. - **Unassigned or deactivated:** the member is suspended and signed out at once. They take no seat, and their SSO link ends, so if they come back they confirm with their electronic ID again. The owner gets an e-mail listing the live API keys the person held, to roll. - **Suspended in Okta:** nothing reaches Parasend, because Okta doesn't tell apps about a suspension. Deactivate or unassign instead. While SSO is required, a suspended Okta user can't get in anyway, because they can't sign in to Okta. - **Assigned or activated again:** the member is restored, if your plan has a free seat. - **Removing someone from the team:** Okta never removes people from apps, so do this on Parasend's Team page. - **Never deleted:** deliveries, evidence and the audit trail. - **Never suspended by provisioning:** the owner. ## 5. Roles from groups (Push Groups) 1. Check that **Push Groups** is ticked under **Provisioning → Integration**. 2. In Okta, create a group for each role you want to give, named after it: **Parasend Admins**, **Parasend Developers**, **Parasend Senders**, **Parasend Viewers** or **Parasend Billing**. The role's name alone also works. Its members must also be assigned to the app, directly or through a group. 3. On the app's **Push Groups** tab, choose **Push Groups → Find groups by name** and pick the group. 4. Under the push action, choose **Create Group**, keep the group's name, and save. - Okta then finds Parasend's group by that name, so "Parasend Admins" becomes the Admin group. - Don't use **Link Group**: it lists Parasend's groups only when Okta can import groups, which this app doesn't do. Okta pushes membership changes at once. How roles work: - **One group:** each member gets that group's role at once. - **Several groups:** they get the highest role, in the order Admin, Developer, Sender, Billing, Viewer. Parasend shows a warning. - **No group:** they get the role set under **Without a group** in Parasend. - **Changing a role:** a role that comes from a group can't be changed in Parasend. Change the group in Okta. - **The owner's role** never changes. - **Unlinking a pushed group** and deleting it in the app empties the group in Parasend. Its members fall back to the role set under **Without a group**. ## Certificates - **Validity:** Okta's SAML signing certificate for an app is valid for ten years. - **Warnings:** Parasend e-mails your owner and admins 30, 14 and 7 days before it expires. - **To rotate:** on the **Sign On** tab, under **SAML Signing Certificates**, generate a new certificate and activate it. There is nothing to do in Parasend. Parasend reads your metadata URL every day, and at once when a new certificate signs a sign-in. ## If something goes wrong - **Parasend says the test failed with the reason code `subject`:** the `parasend_user_id` attribute is missing or empty. Check it with **Preview the SAML Assertion**. - **The SCIM test in Okta fails:** check the base URL, and that the token is entered as a Bearer token. Revoked tokens stop working at once. - **Someone left but can still get in:** they were suspended in Okta, not deactivated. Deactivate or unassign them. - **Locked out while SSO is required:** the owner and the admins you named sign in with their electronic ID on the usual sign-in page, and can stop requiring SSO. --- # Single sign-on: Microsoft Entra ID Source: https://parasend.com/docs/sso/entra Your staff sign in to Parasend with their Microsoft work account. With provisioning (SCIM), Entra ID also adds, suspends and removes them in Parasend, and can set their role from a group. Setting it up takes about 20 minutes. You need to be an admin in Parasend, on Plan 1–3 or Enterprise. In Entra, you need to be a Cloud Application Administrator or Application Administrator. ## Before you start - **Verify your e-mail domain** in Parasend under **Team → Domains**. Single sign-on can be turned on only once a domain is verified. When staff sign in with SSO and enter their work e-mail, the domain tells Parasend where to send them. - **Open the Set up dialog** in Parasend under **Sign-in & SSO → Set up**, and choose **Microsoft Entra ID**. It shows the addresses to paste into Entra, each with a copy button. Keep it open next to this guide. ## 1. Create the app in Entra 1. In the Microsoft Entra admin center, go to **Enterprise applications → New application → Create your own application**. 2. Name it, for example "Parasend", and choose **Integrate any other application you don't find in the gallery**. 3. Under **Properties**, set **Assignment required?** to **Yes**. Only the people and groups you assign can then sign in. 4. Under **Users and groups**, assign the people who should use Parasend. Assigning groups needs Entra ID P1 or P2. ## 2. Set up SAML 1. Go to **Single sign-on → SAML** and edit **Basic SAML Configuration**. 2. Paste these from Parasend's Set up dialog: - **Identifier (Entity ID)**; - **Reply URL (Assertion Consumer Service URL)**; - **Sign on URL**. This is Parasend's start URL. With it, the Parasend tile in My Apps starts the sign-in at Parasend. 3. Leave **Attributes & Claims** as they are. Entra always sends `objectidentifier` and `tenantid`, and Parasend links each person by them. The e-mail address is never used to link anyone; name and e-mail are only shown. 4. Under **SAML Certificates**, copy the **App Federation Metadata Url**. 5. In Parasend's dialog, paste it under step 3 and choose **Register provider**. ## 3. Test, then turn on 1. In Parasend, choose **Test with your own account**. You sign in at Microsoft and come back to Parasend. You must be assigned to the app yourself. 2. Parasend shows whom Entra signed in. If it is you, choose **Yes, this is me**. That Microsoft account is then linked to your electronic ID. 3. Choose **Turn on** and confirm with your electronic ID. From then on, staff choose **Use Microsoft, Okta, Google or SAML?** on Parasend's sign-in page and enter their work e-mail, or open Parasend from My Apps. The first time, each person confirms who they are once with their electronic ID. After that, their Microsoft account is enough. ## 4. Require SSO (optional) - **What it does:** when SSO is required, members get into your Parasend account only through Entra. Someone you disable in Entra can no longer get in. - **If Entra fails:** the owner and up to two admins you name can still sign in with an electronic ID. - **Before you turn it on:** Parasend names the members who have never signed in through Entra. Make sure Entra has them. - **API keys** keep working. To turn it on, sign in with SSO, choose **Require SSO**, and confirm with your electronic ID. ## 5. Provisioning (SCIM) With provisioning, a change in Entra reaches Parasend without waiting for the person's next sign-in. Entra adds people when you assign them and suspends them when you unassign or disable them. 1. In Parasend, under **Provisioning (SCIM)**, choose **Create token** and confirm with your electronic ID. Copy the token: it is shown only once. 2. In Entra, open the app's **Provisioning**, set **Provisioning Mode** to **Automatic**, and under **Admin Credentials** paste: - **Tenant URL**: the SCIM URL from Parasend; - **Secret Token**: the token. 3. Choose **Test Connection**, then **Save**. 4. Under **Mappings → Provision Microsoft Entra ID Users**, edit the **externalId** mapping. Set its source attribute to **objectId** (Entra's default is mailNickname). Parasend matches a provisioned person to their sign-in by this value; with any other value, the two never meet. 5. Under **Settings**, keep **Scope** at **Sync only assigned users and groups**. Set **Provisioning Status** to **On** and save. Entra runs a provisioning cycle about every 40 minutes. For a change that can't wait, for example when someone leaves, choose **Provision on demand** for that person. What each change in Entra does in Parasend: - **Assigned:** the person is listed under **Provisioned, not signed in yet** on the Team page. They join your team at their first SSO sign-in. - **Unassigned, disabled or deleted:** the member is suspended and signed out at once. They take no seat, and their SSO link ends, so if they come back they confirm with their electronic ID again. The owner gets an e-mail listing the live API keys the person held, to roll. - **Assigned or enabled again:** the member is restored, if your plan has a free seat. - **Deleted for good** (after Entra's 30 days): the member is removed from the team, and their API keys pass to the owner. - **Never deleted:** deliveries, evidence and the audit trail. - **Never suspended or removed by provisioning:** the owner. ## 6. Roles from groups This needs Entra ID P1 or P2, because it assigns groups to the app. 1. In Entra, create a group for each role you want to give, named after it: **Parasend Admins**, **Parasend Developers**, **Parasend Senders**, **Parasend Viewers** or **Parasend Billing**. The role's name alone also works. 2. Assign these groups to the app under **Users and groups**. 3. Keep **Mappings → Provision Microsoft Entra ID Groups** enabled. How roles work: - **One group:** each member gets that group's role at once. - **Several groups:** they get the highest role, in the order Admin, Developer, Sender, Billing, Viewer. Parasend shows a warning. - **No group:** they get the role set under **Without a group** in Parasend. - **Changing a role:** a role that comes from a group can't be changed in Parasend. Change the group in Entra. - **The owner's role** never changes. Parasend has only these five groups. If you also assign other groups to the app to give people access, such as "All staff", Entra reports an error for each of them in every cycle. To avoid that, add a scoping filter to the group mapping (**Source Object Scope**: displayName starts with "Parasend"), so that only the role groups are sent. ## Certificates - **Validity:** Entra's SAML signing certificate is valid for three years by default. - **Warnings:** Entra e-mails its notification address 60, 30 and 7 days before the certificate expires. Parasend e-mails your owner and admins 30, 14 and 7 days before. - **To rotate:** under **SAML Certificates**, create a new certificate and make it active. There is nothing to do in Parasend. Parasend reads your metadata URL every day, and at once when a new certificate signs a sign-in. ## If something goes wrong - **Entra shows an error before you get back to Parasend:** - AADSTS50105: you aren't assigned to the app. - AADSTS50011: the Reply URL doesn't match. - **Parasend says the test failed:** the page explains why and gives a reason code. `tenant` means that the metadata URL comes from a different Entra tenant than the account that signed in. - **Provisioned people never join:** check that **externalId** is mapped to **objectId**. - **Locked out while SSO is required:** the owner and the admins you named sign in with their electronic ID on the usual sign-in page, and can stop requiring SSO. --- # Single sign-on: Google Workspace Source: https://parasend.com/docs/sso/google Your staff sign in to Parasend with their Google work account. Setting it up takes about 15 minutes, but Google can take up to 24 hours to give people access to a new app. Google Workspace has no provisioning (SCIM) for custom apps. So Google can't tell Parasend when someone leaves. Read "When someone leaves" below before you rely on it. You need to be an admin in Parasend, on Plan 1–3 or Enterprise. In Google, you need to be a super admin. ## Before you start - **Verify your e-mail domain** in Parasend under **Team → Domains**. Single sign-on can be turned on only once a domain is verified. When staff sign in with SSO and enter their work e-mail, the domain tells Parasend where to send them. - **Open the Set up dialog** in Parasend under **Sign-in & SSO → Set up**, and choose **Google Workspace**. It shows the addresses to paste into Google, each with a copy button. Keep it open next to this guide. ## 1. Create the app in Google 1. In the Google Admin console, go to **Apps → Web and mobile apps → Add app → Add custom SAML app**. Name it, for example "Parasend", and continue. 2. On **Google Identity Provider details**, choose **Download metadata**. You get an XML file. Continue. 3. On **Service provider details**, paste these from Parasend's Set up dialog: - **ACS URL**; - **Entity ID**; - **Start URL**. 4. On the same page, set **Name ID format** to **EMAIL** and **Name ID** to **Basic Information → Primary email**. Continue. 5. Under **Attribute mapping**, you may map First name to `firstName` and Last name to `lastName`. Parasend only shows them. Finish. 6. Open the app's **User access** and turn it on for everyone, or for the organisational units or groups who should use Parasend. 7. In Parasend's dialog, paste the whole content of the XML file under step 3 and choose **Register provider**. ## 2. Test, then turn on 1. In Parasend, choose **Test with your own account**. You sign in at Google and come back to Parasend. If Google says you don't have access to the app, wait: access can take up to 24 hours. 2. Parasend shows whom Google signed in. If it is you, choose **Yes, this is me**. That Google account is then linked to your electronic ID. 3. Choose **Turn on** and confirm with your electronic ID. From then on, staff choose **Use Microsoft, Okta, Google or SAML?** on Parasend's sign-in page and enter their work e-mail, or open Parasend from the Google apps menu. The first time, each person confirms who they are once with their electronic ID. After that, their Google account is enough. ## How Google identifies people Google tells Parasend only the person's primary e-mail address, and an address can later be given to someone else, for example to the next person in the same job. So that a newcomer never gets into Parasend as the person who had the address before: - **Links last 90 days.** After that, the person confirms with their electronic ID again at their next SSO sign-in. - **Removal ends the link.** When you remove someone from your team in Parasend, their link ends at once. Anyone who later gets the address must confirm with their own electronic ID. ## When someone leaves Because Google can't tell Parasend, do these: 1. **Require SSO** (below). Then a person you suspend or delete in Google can't get into your Parasend account, because they can't sign in to Google. 2. **Remove them from the team** in Parasend: on the **Team** page, open the person and choose **Remove from the team**. Their deliveries and evidence stay. 3. **Roll the API keys** they created, on the **API keys** page. ## Require SSO - **What it does:** when SSO is required, members get into your Parasend account only through Google. - **If Google fails:** the owner and up to two admins you name can still sign in with an electronic ID. - **Before you turn it on:** Parasend names the members who have never signed in through Google. Make sure Google gives them access. - **API keys** keep working. To turn it on, sign in with SSO, choose **Require SSO**, and confirm with your electronic ID. ## Certificates - **Validity:** Google's SAML certificate is valid for five years. - **Warnings:** Parasend e-mails your owner and admins 30, 14 and 7 days before it expires. - **To rotate:** Parasend can't read Google's metadata by itself, because it was pasted. Switch the app to the new certificate in Google and download the metadata again. Then, in Parasend, choose **Replace metadata** on the connection and paste it. Sign-ins fail between these two steps, so do them together. ## If something goes wrong - **Google says you don't have access:** check **User access** for the app, then wait. Changes can take up to 24 hours. - **Parasend says the test failed:** the page explains why and gives a reason code. Check that the Name ID is the primary e-mail. - **Locked out while SSO is required:** the owner and the admins you named sign in with their electronic ID on the usual sign-in page, and can stop requiring SSO. --- # Single sign-on: any SAML provider Source: https://parasend.com/docs/sso/saml Any identity provider that supports SAML 2.0 can sign your staff in to Parasend, for example JumpCloud, OneLogin, PingFederate, Keycloak or AD FS. If it also supports SCIM 2.0, it can add, suspend and remove people in Parasend. You need to be an admin in Parasend, on Plan 1–3 or Enterprise. ## What Parasend needs from your provider - **A signed assertion, signed with RSA-SHA256.** Assertions signed with SHA-1 are refused. Encrypted assertions aren't accepted. - **Sign-in started at Parasend**, with the HTTP-POST binding back to the ACS URL. A provider's app launcher works too: Parasend then starts the sign-in again at your provider, and the person is signed in without noticing. - **A permanent ID for each person that is never reused.** Use a persistent NameID, or an attribute you name in the Set up dialog, for example `employeeId`. The e-mail address is never used to link anyone, unless it is the ID itself. - If the NameID has the e-mail format, Parasend treats it as an e-mail address. Such links last 90 days, as with Google Workspace. After that, the person confirms with their electronic ID again. - **Clocks within two minutes** of the correct time. - **Its metadata,** by URL or as XML. A URL is better: Parasend reads it every day, and at once when a new certificate signs a sign-in. ## Before you start - **Verify your e-mail domain** in Parasend under **Team → Domains**. Single sign-on can be turned on only once a domain is verified. When staff sign in with SSO and enter their work e-mail, the domain tells Parasend where to send them. - **Open the Set up dialog** in Parasend under **Sign-in & SSO → Set up**, and choose **Other SAML provider**. It shows the addresses to paste into your provider, each with a copy button. ## 1. Create the app at your provider 1. Create a SAML 2.0 app, and paste these from Parasend's Set up dialog: - **Entity ID** (also called Audience or SP Entity ID); - **ACS URL** (also called Reply URL or Single sign-on URL); - **Start URL**, if your provider has a field for the app's sign-in or start address. 2. Choose the NameID format **persistent**, or one whose value is permanent. 3. Give the app to the people who should use Parasend. 4. In Parasend's dialog, paste the metadata URL or XML under step 3. 5. If the NameID isn't permanent, enter the name of the attribute that holds the person's ID under **Attribute with the user's ID**. 6. Choose **Register provider**. ## 2. Test, then turn on 1. In Parasend, choose **Test with your own account**. You sign in at your provider and come back to Parasend. 2. Parasend shows whom your provider signed in, with the ID it sent. If it is you, choose **Yes, this is me**. That account is then linked to your electronic ID. 3. Choose **Turn on** and confirm with your electronic ID. From then on, staff choose **Use Microsoft, Okta, Google or SAML?** on Parasend's sign-in page and enter their work e-mail. The first time, each person confirms who they are once with their electronic ID. After that, their account at your provider is enough. ## 3. Require SSO (optional) - **What it does:** when SSO is required, members get into your Parasend account only through your provider. - **If your provider fails:** the owner and up to two admins you name can still sign in with an electronic ID. - **Before you turn it on:** Parasend names the members who have never signed in through your provider. - **API keys** keep working. To turn it on, sign in with SSO, choose **Require SSO**, and confirm with your electronic ID. ## 4. Provisioning (SCIM 2.0) 1. In Parasend, under **Provisioning (SCIM)**, choose **Create token** and confirm with your electronic ID. Copy the token: it is shown only once. 2. At your provider, enter: - **Base URL:** the SCIM URL from Parasend; - **Authentication:** an HTTP header, `Authorization: Bearer` followed by the token; - **Unique user field:** `userName`. 3. Send **externalId** with the same value as the ID your provider sends in SAML. Parasend matches a provisioned person to their sign-in by it, never by e-mail. What Parasend does with each change: - **A new user:** listed under **Provisioned, not signed in yet** on the Team page, and joins your team at their first SSO sign-in. - **`active: false`:** the member is suspended and signed out at once. They take no seat, and their SSO link ends. The owner gets an e-mail listing the live API keys the person held, to roll. - **`active: true`:** the member is restored, if your plan has a free seat. - **`DELETE`:** the member is removed from the team, and their API keys pass to the owner. - **Never deleted:** deliveries, evidence and the audit trail. - **Never suspended or removed by provisioning:** the owner. - **Too many calls at once:** Parasend answers 429. Wait and try again. ### Roles from groups Parasend has five groups, one for each role a member can have: Admin, Developer, Sender, Viewer and Billing. Their IDs are the role names (`/Groups/admin`). - **Names:** create or match a group by its role's name, alone or after "Parasend", for example "Parasend Admins". Other names are refused, and a rename is ignored. - **Several groups:** a member in several groups gets the highest role, in the order Admin, Developer, Sender, Billing, Viewer. - **No group:** a member in none gets the role set under **Without a group**. - **Deleting a group** empties it. The group itself stays. ## Certificates - **Warnings:** Parasend e-mails your owner and admins 30, 14 and 7 days before your provider's certificate expires. - **With a metadata URL:** there is nothing to do in Parasend when the certificate changes. - **With pasted XML:** when the new certificate starts signing, choose **Replace metadata** on the connection and paste the new metadata. Sign-ins fail in between. ## If something goes wrong - **Parasend says the test failed:** the page explains why and gives a reason code. Common reasons: - the assertion isn't signed, or is signed with SHA-1; - the ACS URL or Entity ID doesn't match; - the clocks differ by more than two minutes; - the ID is missing. - **Locked out while SSO is required:** the owner and the admins you named sign in with their electronic ID on the usual sign-in page, and can stop requiring SSO. --- # Deliveries Source: https://parasend.com/docs/api/deliveries One evidenced transfer of a content payload (files or a secret) to exactly one recipient. ## Endpoints - [Create a delivery](https://parasend.com/docs/api/deliveries/create.md): `POST /v1/deliveries` - [List deliveries](https://parasend.com/docs/api/deliveries/list.md): `GET /v1/deliveries` - [Retrieve a delivery](https://parasend.com/docs/api/deliveries/retrieve.md): `GET /v1/deliveries/{id}` - [Check a contact before sending](https://parasend.com/docs/api/deliveries/check-contact.md): `POST /v1/deliveries/contact_check` - [Send a reminder](https://parasend.com/docs/api/deliveries/remind.md): `POST /v1/deliveries/{id}/remind` - [Revoke a delivery](https://parasend.com/docs/api/deliveries/revoke.md): `POST /v1/deliveries/{id}/revoke` - [Delete a delivery](https://parasend.com/docs/api/deliveries/delete.md): `DELETE /v1/deliveries/{id}` - [List a delivery's events](https://parasend.com/docs/api/deliveries/events.md): `GET /v1/deliveries/{id}/events` - [List a delivery's e-mails and SMS](https://parasend.com/docs/api/deliveries/notifications.md): `GET /v1/deliveries/{id}/notifications` - [List a delivery's files](https://parasend.com/docs/api/deliveries/files.md): `GET /v1/deliveries/{id}/files` - [Download a delivered file](https://parasend.com/docs/api/deliveries/download-file.md): `GET /v1/deliveries/{id}/files/{fileId}/content` - [Set the recipient's postal address](https://parasend.com/docs/api/deliveries/set-address.md): `PATCH /v1/deliveries/{id}/address` - [Change a delivery's follow-up policy](https://parasend.com/docs/api/deliveries/change-policy.md): `POST /v1/deliveries/{id}/policy_change` - [Get a delivery's cost](https://parasend.com/docs/api/deliveries/cost.md): `GET /v1/deliveries/{id}/cost` - [Get the cost of several deliveries](https://parasend.com/docs/api/deliveries/costs.md): `GET /v1/deliveries/costs` - [Get delivery counts](https://parasend.com/docs/api/deliveries/summary.md): `GET /v1/deliveries/summary` ## The Delivery object - `id` (string, optional): The delivery's ID, starting `dlv_`. - `object` (string, optional): Always `delivery`. - `content_type` (string, optional): What the delivery carries: `files`, documents. One of: `files`. - `status` (string, optional): Where a delivery is in its life. Activity such as reminders and opens is in `last_activity`, never a status. `authenticated`: the recipient has verified but not yet confirmed receipt. The end states are `delivered`, `expired`, `revoked`, `bounced` and `consignment_failed`. One of: `created`, `notified`, `opened`, `authenticated`, `auth_failed`, `confirmed`, `delivered`, `evidence_sealed`, `expired`, `revoked`, `bounced`, `consignment_failed`, `paper_pending_approval`, `paper_dispatched`. - `last_activity` (object · nullable, optional): The latest activity on the delivery, such as a reminder or an open; null until there is any. - `recipient` (object, optional): Who receives a delivery. At least one of `email` and `phone`; `phone` is required at `verification_level: medium`, where the SMS code goes. With `conditional_authentication` true, only the named person can open it: `personal_code` is required at `high` (only an eID with that kennitala passes) and `id_document_number` at `substantial` (only that identity document passes). Without it, the recipient still verifies at the level you set, and their verified identity is recorded and returned to you. A mismatch is the event `delivery.auth_failed`, with the reason `personal_code_mismatch` or `id_document_mismatch`. - `recipient.verification_level` (string, required): How the recipient must identify themselves before they see the documents: `high`, eID; `substantial`, a passport or ID-card check with a selfie; `medium`, a one-time code sent by SMS when they open the link; `low`, the link alone. A stronger way is always accepted. One of: `high`, `substantial`, `medium`, `low`. - `recipient.name` (string · nullable, optional): The recipient's name as you gave it, trimmed: up to 255 characters, no control characters. It is sealed into the evidence as given and never checked; null when none was given. `verified_name` holds the name from their eID or passport. - `recipient.verified_name` (string · nullable, optional): The recipient's name as their eID or passport states it, once they have signed in with one; null before that, or for a confirmation without an eID or a passport check. `name` stays as the sender declared it (sealed into the evidence as such). - `recipient.verified_method` (string · nullable, optional): How `verified_name` was established — the method of the sign-in it was read from (an eID, a passkey, or videoid for a passport check); null with it. One of: `audkenni`, `smart_id`, `sms_otp`, `passkey`, `videoid`, `password`, `link_only`. - `recipient.verification_photos` (array of string, optional): The photos kept, with the recipient's consent, from the passport or ID-card check their verified name came from: the document's photo page and the selfie. Each is at GET /deliveries/{id}/recipient_photos/{kind} (JPEG; needs `evidence.read`). Empty for any other method. One of: `document`, `selfie`. - `recipient.email` (string · email · nullable, optional): The recipient's e-mail address, where e-mail notices and reminders go; null when not given. At least one of `email` and `phone` is required. - `recipient.phone` (string · nullable, optional): E.164 - `recipient.address` (object · nullable, optional) - `recipient.address.line1` (string, optional): The first address line, such as the street and number. Required. - `recipient.address.line2` (string · nullable, optional): A second address line, or null. - `recipient.address.postal_code` (string, optional): The postal code. Required. - `recipient.address.city` (string, optional): The town or city. Required. - `recipient.address.country` (string, optional): ISO 3166-1 alpha-2 - `recipient.address.source` (string · nullable, optional): Where the address came from, as you state it: `thjodskra`, Iceland's national registry (Þjóðskrá); `customer_csv`, your own records, imported from a file; `manual`, entered by hand. Default `manual`. One of: `thjodskra`, `customer_csv`, `manual`. - `recipient.conditional_authentication` (boolean, optional): When true, only the person you name can open the delivery: at `high` their eID must carry `personal_code`, and at `substantial` their passport or ID card must carry `id_document_number`. Default false: the recipient still identifies themselves at the level, and you get back the identity they used. Default: `false`. - `recipient.personal_code` (string · nullable, optional): The recipient's Icelandic personal code (kennitala). Required when `conditional_authentication` is true at `high`. - `recipient.id_document_number` (string · nullable, optional): The number of the recipient's passport or ID card. Required when `conditional_authentication` is true at `substantial`. - `batch_id` (string · nullable, optional): The batch it belongs to (`bat_…`), or null when it is not part of a batch. - `delivery_url` (string · nullable, optional): Signed recipient URL (headless/embed integration). - `evidence_policy` (string · uri, optional): The evidence policy this delivery's evidence is produced under, as a URN. It is on the delivery and on every evidence record. - `certificate_language` (string, optional): The language of every Delivery Certificate of this delivery, the sender's and the recipient's: your organisation's certificate language when the delivery was created, never changed after. `?language=` on the certificate routes may name it; the other language is 409 `certificate_language_fixed`. One of: `is`, `en`. - `warnings` (array of object, optional): Notices on the create response that do not stop the delivery. Show them to whoever sent it. - `warnings[].code` (string, optional): `sms_otp_unavailable`: SMS one-time codes that came only with the default methods were dropped, because Parasend does not send SMS to that number's country on your account. `link_domain_unavailable`: sent with Parasend's own links because your link domain is not working. One of: `fail_closed_delivery`, `sms_otp_unavailable`, `link_domain_unavailable`. - `warnings[].message` (string, optional) - `sender_display` (object, optional): Resolved value (delivery → template → organisation default). - `operator_reference` (string · nullable, optional): For a company sender: an opaque reference to the person who sent it, shown under the company name instead of their name. null for a personal account. - `recipient_ref` (string, optional): An opaque reference to the recipient, repeated on the evidence. - `options` (object, optional) - `options.notify_channels` (array of string, optional): How the recipient is told — the invitation and every reminder go to each channel listed; each needs its contact (recipient.email, recipient.phone). Default — e-mail when there is an address, else SMS. Told on both, a failure on one channel bounces the delivery only once the other cannot arrive either. One of: `email`, `sms`. - `options.auth_methods` (array of string, optional): Which ways of identifying the recipient to offer within `recipient.verification_level`, in your order of preference. The recipient may always use a way at least as strong as the weakest one you list. Left out: every way the level allows (an SMS code only when the recipient has a phone). One of: `audkenni`, `smart_id`, `sms_otp`, `passkey`, `videoid`, `password`, `link_only`. - `options.reminder_policy` (string · nullable, optional): The follow-up policy's ID (`rpol_…`): the reminders this delivery sends while it waits. - `options.brand_profile_id` (string · nullable, optional): The brand profile's ID (`bp_…`). One set on the delivery wins over the batch's, and the batch's over the account's default. - `options.expires_in_days` (integer, optional): How many days the recipient has to confirm receipt before the delivery expires: a whole number from 1 to 365. Default 30. For a delivery in a batch, the days count from when the batch is sent out. - `options.sender_display` (string, optional): What the recipient and the evidence show as the sender. `legal_person` (your organisation's default) shows the organisation, with an opaque reference to the person who sent it; that person's name, personal code and e-mail are not shown. `individual` names the person who sent it as well. Your choice per delivery or template; a personal account is always `individual`. One of: `legal_person`, `individual`. - `options.allow_parasend_links` (boolean, optional): Send even though your brand profile's link domain is not working, with Parasend's own links (warning `link_domain_unavailable`). Without it such a delivery is refused with 409 `link_domain_unavailable`. Default: `false`. - `options.notice_wording` (object, optional): Which saved wording the first notice uses, per channel: a wording ID, null for Parasend's own, or left out for your organisation's default. - `options.notice_wording.email` (string · nullable, optional) - `options.notice_wording.sms` (string · nullable, optional) - `legal_hold` (boolean, optional): Whether the delivery is under legal hold. While it is, `DELETE /deliveries/{id}` is refused with `delete_blocked_legal_hold`. - `deletion_scheduled_at` (string · date-time · nullable, optional): Set when DELETE /deliveries/{id} has scheduled the delivery's removal; null otherwise. - `livemode` (boolean, optional): `true` in live mode, `false` in test mode. - `created` (string · date-time, optional): When it was created, in ISO 8601 (UTC). - `expires_at` (string · date-time · nullable, optional): When the delivery expires if the recipient has not confirmed receipt, in ISO 8601 (UTC): `options.expires_in_days` after it was created, or for a delivery in a batch, after the batch was sent out. null while its batch has not been sent out. - `metadata` (object, optional): Your own key–value pairs, kept with the object and returned as you sent them. Never put personal data here. - `kind` (string, optional): Unified-list discriminator. GET /deliveries returns all kinds. One of: `documents`, `secret`, `request`. - `direction` (string, optional): inbound = a fulfillment of a document request (sender = the verified uploader). One of: `outbound`, `inbound`. Default: `"outbound"`. - `origin_request` (string · nullable, optional): req_… linkage when this delivery fulfils a document request. - `follow_up` (object · nullable, optional): Where the delivery is in its follow-up policy; null for a delivery created before follow-up policies existed. - `reminder_waiting` (object · nullable, optional): A reminder that waits because the delivery's link domain is not working: it goes on that domain once it works again, or with Parasend's link once the domain has failed (after 72 hours). null when none waits. - `reminder_waiting.since` (string · date-time, required): When the first waiting reminder was put off. - `reminder_waiting.reason` (string, required): One of: `link_domain_degraded`. - `reminder_waiting.link_host` (string, required): The delivery's link domain. - `email_waiting` (object · nullable, optional): An e-mail that waits for your own sending address (its sender is set to hold on failure): it goes from that address once it works again, or from Parasend's at `until` at the latest. null when none waits. - `email_waiting.since` (string · date-time, required) - `email_waiting.reason` (string, required): One of: `email_sender`. - `email_waiting.from_address` (string · email, required) - `email_waiting.until` (string · date-time, required) - `private` (boolean, optional): Sent with a private key; written once at creation. - `title` (string · nullable, optional): The subject the recipient sees in the notice and on their page. - `message` (string · nullable, optional): The plain-text note shown before sign-in, or null. - `language` (string, optional): The language of the notice and of the recipient page. One of: `is`, `en`. - `short_url` (string · uri · nullable, optional): A short link to the recipient page, the form an SMS carries. - `files` (array of object, optional): The delivery's files: each one's ID, its name in the delivery, and whether it is the main document. - `files[].file_id` (string, optional) - `files[].path` (string, optional) - `files[].primary` (boolean, optional) ```json { "id": "dlv_01M6R27T05512SCV3852TPM2XY", "object": "delivery", "kind": "documents", "direction": "outbound", "content_type": "files", "status": "notified", "last_activity": { "at": "2026-11-06T09:15:05.667Z", "type": "notified" }, "title": "Final notice: invoice 2026-114", "message": null, "language": "en", "certificate_language": "is", "recipient": { "name": "Anna Björk Jónsdóttir", "verified_name": null, "verified_method": null, "verification_photos": [], "email": "anna.bjork@example.com", "phone": null, "address": null, "verification_level": "high", "conditional_authentication": false, "personal_code": null, "id_document_number": null }, "files": [ { "file_id": "fil_01M6R27SZK99V6N6WG4GK15T6R", "path": "test-notice.pdf", "primary": true } ], "batch_id": null, "delivery_url": null, "short_url": "https://parasend.com/d/RM1RyXl0", "evidence_policy": "urn:parasend:policy:evidence:standard:1", "sender_display": "legal_person", "operator_reference": "OP-7K3Q-M2", "recipient_ref": "rcp_01M6R27T05ZNSRNMAW2CA8MTF6", "private": false, "options": { "auth_methods": [ "audkenni", "smart_id", "passkey" ], "reminder_policy": null, "brand_profile_id": null, "expires_in_days": 30, "sender_display": "legal_person", "notify_channels": [ "email" ] }, "legal_hold": false, "deletion_scheduled_at": null, "livemode": false, "created": "2026-11-06T09:15:05.604Z", "expires_at": "2026-12-06T09:15:05.603Z", "metadata": { "case_ref": "IN-2026-114" }, "origin_request": null, "reminder_waiting": null, "email_waiting": null, "follow_up": { "policy_id": null, "policy_name": null, "policy_source": "none", "state": "done", "reminders_sent": 0, "last_step": null, "next_step": null, "paused": false } } ``` --- # Files Source: https://parasend.com/docs/api/files Document containers — multiple files and folder structures per delivery via the manifest. Content is never scanned or inspected (N4, 23 Sep 2026) — integrity under Art. 44(1)(c) is served by not touching the bytes; the AUP places content at the customer's own risk. ## Endpoints - [Upload a file](https://parasend.com/docs/api/files/upload.md): `POST /v1/files` - [Retrieve a file](https://parasend.com/docs/api/files/retrieve.md): `GET /v1/files/{id}` ## The File object - `id` (string, optional): The file's ID, starting `fil_`. - `object` (string, optional): Always `file`. - `name` (string, optional): The file's name as uploaded, in Unicode NFC, with any path separator replaced by `_`; at most 255 characters. It is part of the sealed evidence record. - `bytes` (integer, optional): The file's size in bytes. - `sha256` (string, optional): The SHA-256 digest of the file's content, as 64 lower-case hexadecimal characters. - `created` (string · date-time, optional): When it was created, in ISO 8601 (UTC). ```json { "id": "fil_01M6R27SZK99V6N6WG4GK15T6R", "object": "file", "name": "test-notice.pdf", "bytes": 691, "sha256": "1b0cff41d97c8dcf92b7de986d66d12fb94e68f6db19821aa4b263dcfb0b6225", "created": "2026-11-06T09:15:05.587Z" } ``` --- # Batches Source: https://parasend.com/docs/api/batches First-class container of deliveries sharing files and policies, with an aggregated monotonic funnel. Lifecycle draft → send out (/finalize) → sending → completed. ## Endpoints - [Create a batch](https://parasend.com/docs/api/batches/create.md): `POST /v1/batches` - [List batches](https://parasend.com/docs/api/batches/list.md): `GET /v1/batches` - [Retrieve a batch](https://parasend.com/docs/api/batches/retrieve.md): `GET /v1/batches/{id}` - [Update a batch](https://parasend.com/docs/api/batches/update.md): `PATCH /v1/batches/{id}` - [Add deliveries to a batch](https://parasend.com/docs/api/batches/add-deliveries.md): `POST /v1/batches/{id}/deliveries` - [List a batch's deliveries](https://parasend.com/docs/api/batches/deliveries.md): `GET /v1/batches/{id}/deliveries` - [Check a batch before sending](https://parasend.com/docs/api/batches/preflight.md): `GET /v1/batches/{id}/preflight` - [Send out a batch](https://parasend.com/docs/api/batches/send-out.md): `POST /v1/batches/{id}/finalize` - [Get a batch report](https://parasend.com/docs/api/batches/report.md): `GET /v1/batches/{id}/report` - [Pause a batch's follow-ups](https://parasend.com/docs/api/batches/pause.md): `POST /v1/batches/{id}/pause` - [Resume a batch's follow-ups](https://parasend.com/docs/api/batches/resume.md): `POST /v1/batches/{id}/resume` - [Change a batch's follow-up policy](https://parasend.com/docs/api/batches/change-policy.md): `POST /v1/batches/{id}/policy_change` - [Extend a batch's deliveries](https://parasend.com/docs/api/batches/extend-expiry.md): `POST /v1/batches/{id}/extend_expiry` - [Duplicate a batch](https://parasend.com/docs/api/batches/duplicate.md): `POST /v1/batches/{id}/duplicate` - [Cancel a batch](https://parasend.com/docs/api/batches/cancel.md): `POST /v1/batches/{id}/cancel` ## The Batch object - `id` (string, optional): The batch's ID, starting `bat_`. - `object` (string, optional): Always `batch`. - `name` (string · nullable, optional): The batch's name, for your own reference. A batch created without one is named `Batch` and the creation date (UTC), e.g. `Batch 2026-11-06`; null if it was set to null. - `status` (string, optional): `draft`, then `scheduled` (optional), `sending`, `following_up` and `completed`; or `cancelled`. Paused follow-ups are a flag, not a status. One of: `draft`, `scheduled`, `sending`, `following_up`, `completed`, `cancelled`. - `sending_mode` (string, optional): `on_finalize`: deliveries added to the draft wait and go out together when you send the batch out (`POST /batches/{id}/finalize`), or at `scheduled_at`. `rolling`: the batch is sending from the start and each delivery goes out as soon as it is added, until you close the batch with `POST /batches/{id}/finalize`. One of: `on_finalize`, `rolling`. - `funnel` (object, optional): Counts that only go up: `notified` ≥ `opened` ≥ `authenticated` ≥ `confirmed_direct`, and `delivered_total` = `confirmed_direct` + `rescued_by_followups`. - `funnel.total` (integer, optional): Deliveries counted. - `funnel.notified` (integer, optional): Deliveries whose first notice has gone out. - `funnel.opened` (integer, optional): Notified deliveries whose recipient opened the link, or got further. - `funnel.authenticated` (integer, optional): Notified deliveries whose recipient identified themselves, or got further (every delivered one included). - `funnel.confirmed_direct` (integer, optional): Deliveries whose recipient confirmed receipt after the first notice, with no follow-up. - `funnel.rescued_by_followups` (integer, optional): Deliveries whose recipient confirmed receipt only after a follow-up. - `funnel.delivered_total` (integer, optional): All delivered deliveries: `confirmed_direct` plus `rescued_by_followups`. - `funnel.expired` (integer, optional): Deliveries that expired before the recipient confirmed receipt. - `funnel.revoked` (integer, optional): Deliveries that were revoked. - `funnel.auth_failed` (integer, optional): Deliveries whose status is now `auth_failed`: the recipient's last attempt to identify themselves failed. - `funnel.bounced` (integer, optional): Deliveries that ended `bounced`: the recipient could not be reached. - `funnel.paper_pending` (integer, optional): Deliveries in a paper-mail step (`paper_pending_approval` or `paper_dispatched`). - `funnel.needs_address` (integer, optional): Deliveries waiting for a postal address. Currently always 0. - `funnel.bounce_rate` (number, optional): `bounced` as a fraction of `notified`, from 0 to 1, rounded to four decimals; 0 when none have been notified. - `funnel.median_time_to_confirm` (string · nullable, optional): ISO 8601 duration. - `created` (string · date-time, optional): When it was created, in ISO 8601 (UTC). - `follow_up_policy_id` (string · nullable, optional): The follow-up policy (`rpol_…`) the batch's deliveries follow, unless a delivery follows its own. null: no follow-up policy (never your organisation's default policy). - `allow_policy_override` (boolean, optional): Whether a delivery in the batch may follow a different follow-up policy, named in its `options.reminder_policy`. When false, such a delivery is refused with `policy_override_not_allowed`. - `follow_ups_paused` (boolean, optional): Whether the batch's follow-ups are paused (`POST /batches/{id}/pause`). Reminders that come due while paused wait, and go in the next send window after `POST /batches/{id}/resume`. - `scheduled_at` (string · date-time · nullable, optional): When the batch is to be sent out, in ISO 8601 (UTC), if you gave it a send time; null otherwise. Sending out a batch with a future `scheduled_at` makes it `scheduled` until that time. - `send_window` (object, optional) - `send_window.start` (string, required): Local time HH:MM, inclusive. - `send_window.end` (string, required): Local time HH:MM, exclusive; after start, same day. - `send_window.tz` (string, optional): The time zone of `start` and `end`: always `Atlantic/Reykjavik`, which is UTC all year. - `defaults` (object, optional) - `defaults.options` (object, optional) - `defaults.options.notify_channels` (array of string, optional): How the recipient is told — the invitation and every reminder go to each channel listed; each needs its contact (recipient.email, recipient.phone). Default — e-mail when there is an address, else SMS. Told on both, a failure on one channel bounces the delivery only once the other cannot arrive either. One of: `email`, `sms`. - `defaults.options.auth_methods` (array of string, optional): Which ways of identifying the recipient to offer within `recipient.verification_level`, in your order of preference. The recipient may always use a way at least as strong as the weakest one you list. Left out: every way the level allows (an SMS code only when the recipient has a phone). One of: `audkenni`, `smart_id`, `sms_otp`, `passkey`, `videoid`, `password`, `link_only`. - `defaults.options.reminder_policy` (string · nullable, optional): The follow-up policy's ID (`rpol_…`): the reminders this delivery sends while it waits. - `defaults.options.brand_profile_id` (string · nullable, optional): The brand profile's ID (`bp_…`). One set on the delivery wins over the batch's, and the batch's over the account's default. - `defaults.options.expires_in_days` (integer, optional): How many days the recipient has to confirm receipt before the delivery expires: a whole number from 1 to 365. Default 30. For a delivery in a batch, the days count from when the batch is sent out. - `defaults.options.sender_display` (string, optional): What the recipient and the evidence show as the sender. `legal_person` (your organisation's default) shows the organisation, with an opaque reference to the person who sent it; that person's name, personal code and e-mail are not shown. `individual` names the person who sent it as well. Your choice per delivery or template; a personal account is always `individual`. One of: `legal_person`, `individual`. - `defaults.options.allow_parasend_links` (boolean, optional): Send even though your brand profile's link domain is not working, with Parasend's own links (warning `link_domain_unavailable`). Without it such a delivery is refused with 409 `link_domain_unavailable`. Default: `false`. - `defaults.options.notice_wording` (object, optional): Which saved wording the first notice uses, per channel: a wording ID, null for Parasend's own, or left out for your organisation's default. - `defaults.options.notice_wording.email` (string · nullable, optional) - `defaults.options.notice_wording.sms` (string · nullable, optional) - `defaults.additional_security_source` (string · nullable, optional): `csv_personal_code`: a delivery added with a `recipient.personal_code`, and no `recipient.conditional_authentication` of its own, gets `conditional_authentication: true`, so only that person can open it. null or left out: no such rule. One of: `csv_personal_code`. - `defaults.language` (string, optional): The `language` (`is` or `en`) for deliveries added to the batch that do not set their own. - `defaults.message` (string, optional): The `message` for deliveries added to the batch that do not set their own: the plain-text note the recipient sees before signing in. - `defaults.title` (string, optional): The `title` for deliveries added to the batch that do not set their own: the subject the recipient sees in the notice and on their page. - `defaults.verification_level` (string, optional): The `recipient.verification_level` (`high`, `substantial`, `medium` or `low`) for deliveries added to the batch that do not set their own. - `files` (array of string, optional): IDs of the files (`fil_…`) shared by every delivery in the batch. Each delivery added gets these first, then its own files. - `accepting_deliveries` (boolean, optional): Whether you can add deliveries to the batch now: true for a `draft`, and for a `rolling` batch that is sending and has not been closed. - `counts` (object, optional): The batch's deliveries by where they stand now. Each delivery is in exactly one of `delivered`, `in_progress` and `ended`, so the three add up to `total`. - `counts.total` (integer, optional) - `counts.delivered` (integer, optional) - `counts.in_progress` (integer, optional) - `counts.ended` (integer, optional): Expired, revoked, bounced or failed — not delivered. - `created_by` (object, optional): Who created the batch: `user_id`, the member or the API key's holder (null for a key with no holder), and `via`, `portal` or `api_key`. - `created_by.user_id` (string · nullable, optional) - `created_by.via` (string · nullable, optional): One of: `portal`, `api_key`. - `livemode` (boolean, optional): `true` in live mode, `false` in test mode. - `sent_out_at` (string · date-time · nullable, optional): When the batch was sent out, in ISO 8601 (UTC); for a `rolling` batch, when it was closed to new deliveries. null until then; a batch cancelled first gets the time it was cancelled. - `following_up_at` (string · date-time · nullable, optional): When the batch moved to `following_up`, in ISO 8601 (UTC): it had been sent out and every delivery in it had had its first notice. null until then. - `completed_at` (string · date-time · nullable, optional): When the batch became `completed`, in ISO 8601 (UTC): every delivery in it had reached an end state. null until then. - `cancelled_at` (string · date-time · nullable, optional): When the batch was cancelled with `POST /batches/{id}/cancel`, in ISO 8601 (UTC); null if it has not been. - `follow_up_funnel` (object, optional): Only on GET /batches/{id}. - `follow_up_funnel.as_of` (string · date-time, optional): When the counts were taken, in ISO 8601 (UTC). - `follow_up_funnel.policy_id` (string · nullable, optional): The follow-up policy the counts are limited to, from the `policy_id` query parameter (`rpol_none` for deliveries with no policy); null when they cover the whole batch. - `follow_up_funnel.total` (integer, optional): Deliveries counted. - `follow_up_funnel.not_yet_notified` (integer, optional): Deliveries whose first notice has not gone out yet, such as those in a batch not yet sent out. - `follow_up_funnel.notice` (object, optional): The first notice: `sent` is every notified delivery and `bounced` those whose notice bounced; the outcome counts are the deliveries that had no reminder. `email_opened` counts notices whose e-mail was opened before any reminder went (a hint, not proof). - `follow_up_funnel.notice.sent` (integer, optional): Deliveries that reached this stage: for the notice, every notified delivery; for a reminder, the deliveries that got at least this many reminders. - `follow_up_funnel.notice.bounced` (integer, optional): Deliveries whose message at this stage bounced. - `follow_up_funnel.notice.delivered` (integer, optional): Of the deliveries for which this was the last stage, those delivered. - `follow_up_funnel.notice.in_progress` (integer, optional): Of the deliveries for which this was the last stage, those still under way. - `follow_up_funnel.notice.unreachable` (integer, optional): Of the deliveries for which this was the last stage, those that could not be reached (bounced). - `follow_up_funnel.notice.expired` (integer, optional): Of the deliveries for which this was the last stage, those that expired. - `follow_up_funnel.notice.revoked` (integer, optional): Of the deliveries for which this was the last stage, those revoked. - `follow_up_funnel.notice.not_matched` (integer, optional): Of the deliveries for which this was the last stage, those that ended `consignment_failed`: the recipient's identification did not meet what the delivery required. - `follow_up_funnel.notice.renotified_via_lookup` (integer, optional) - `follow_up_funnel.notice.email_opened` (integer, optional): Notices whose e-mail was opened (a hint, not proof). - `follow_up_funnel.notice.renotified_delivered` (integer, optional): Deliveries confirmed after a notice to a channel found by the lookup. - `follow_up_funnel.reminders` (array of object, optional): One entry per reminder, in order (`number` 1, 2, 3…): `sent` is the deliveries that got at least that many reminders, `bounced` those for which it bounced, and the outcome counts the deliveries whose last reminder it was. `channels` counts that reminder by channel. - `follow_up_funnel.reminders[].sent` (integer, optional): Deliveries that reached this stage: for the notice, every notified delivery; for a reminder, the deliveries that got at least this many reminders. - `follow_up_funnel.reminders[].bounced` (integer, optional): Deliveries whose message at this stage bounced. - `follow_up_funnel.reminders[].delivered` (integer, optional): Of the deliveries for which this was the last stage, those delivered. - `follow_up_funnel.reminders[].in_progress` (integer, optional): Of the deliveries for which this was the last stage, those still under way. - `follow_up_funnel.reminders[].unreachable` (integer, optional): Of the deliveries for which this was the last stage, those that could not be reached (bounced). - `follow_up_funnel.reminders[].expired` (integer, optional): Of the deliveries for which this was the last stage, those that expired. - `follow_up_funnel.reminders[].revoked` (integer, optional): Of the deliveries for which this was the last stage, those revoked. - `follow_up_funnel.reminders[].not_matched` (integer, optional): Of the deliveries for which this was the last stage, those that ended `consignment_failed`: the recipient's identification did not meet what the delivery required. - `follow_up_funnel.reminders[].number` (integer, optional) - `follow_up_funnel.reminders[].channels` (object, optional) - `follow_up_funnel.reminders[].channels.email` (integer, optional) - `follow_up_funnel.reminders[].channels.sms` (integer, optional) - `follow_up_funnel.reminders[].channels.alternate_contact` (integer, optional) - `follow_up_funnel.outcome` (object, optional): Where every counted delivery stands now, each in exactly one: `delivered`, `in_progress`, `unreachable` (bounced), `expired`, `revoked`, or `not_matched` (ended `consignment_failed`: the recipient's identification did not meet what the delivery required). - `follow_up_funnel.outcome.delivered` (integer, optional) - `follow_up_funnel.outcome.in_progress` (integer, optional) - `follow_up_funnel.outcome.unreachable` (integer, optional) - `follow_up_funnel.outcome.expired` (integer, optional) - `follow_up_funnel.outcome.revoked` (integer, optional) - `follow_up_funnel.outcome.not_matched` (integer, optional) - `follow_up_funnel.confirmed_direct` (integer, optional): Deliveries whose recipient confirmed receipt after the first notice, with no follow-up. - `follow_up_funnel.rescued_by_followups` (integer, optional): Deliveries whose recipient confirmed receipt only after a follow-up. - `follow_up_funnel.delivered_total` (integer, optional): All delivered deliveries: `confirmed_direct` plus `rescued_by_followups`. - `kpis` (object, optional): Only on GET /batches/{id}. - `kpis.delivered` (integer, optional): Deliveries in the batch that were delivered, on the first notice or after follow-ups. - `kpis.delivered_pct` (number, optional): `delivered` as a percentage of the batch's deliveries, rounded to one decimal (e.g. `87.5`); 0 for an empty batch. - `kpis.in_progress` (integer, optional): Deliveries still under way: not yet delivered and not ended. - `kpis.median_time_to_delivery` (string · nullable, optional): ISO 8601 duration. - `kpis.bounce_rate` (number, optional): The share of notified deliveries that could not be reached (bounced), as a fraction from 0 to 1 rounded to four decimals; 0 when none have been notified. - `kpis.rescued_by_followups` (integer, optional): Delivered deliveries whose recipient confirmed only after a follow-up, not on the first notice. - `kpis.spend` (object, optional): An estimate of the batch's spend so far: the SMS messages sent for it (`sms_messages`), priced at today's prices, excluding VAT (`amount`, in the minor unit of `currency`). `lookups` is 0, and `estimated` is always true: your invoice is what counts. - `kpis.spend.sms_messages` (integer, optional) - `kpis.spend.lookups` (integer, optional) - `kpis.spend.amount` (integer, optional) - `kpis.spend.currency` (string, optional) - `kpis.spend.estimated` (boolean, optional) - `kpis.vs_previous` (object · nullable, optional): How this batch compares with the previous batch your organisation sent out in the same mode (cancelled batches left out): that batch's ID and the difference in delivered percentage, in percentage points. null until this batch is sent out, or when there is no earlier batch. - `kpis.vs_previous.batch_id` (string, optional) - `kpis.vs_previous.delivered_pct_delta` (number, optional): Percentage points. ```json { "id": "bat_01M6R27T8SXWNM911FS53W4S9F", "object": "batch", "name": "November notices", "status": "draft", "sending_mode": "on_finalize", "follow_up_policy_id": null, "allow_policy_override": true, "follow_ups_paused": false, "scheduled_at": null, "send_window": { "tz": "Atlantic/Reykjavik", "end": "21:00", "start": "09:00" }, "defaults": {}, "files": [], "accepting_deliveries": true, "counts": { "total": 0, "delivered": 0, "in_progress": 0, "ended": 0 }, "created_by": { "user_id": "usr_01M6R27SYD4B50414S0BZ4KBKR", "via": "api_key" }, "livemode": false, "created": "2026-11-06T09:15:05.881Z", "sent_out_at": null, "following_up_at": null, "completed_at": null, "cancelled_at": null, "follow_up_funnel": { "as_of": "2026-11-06T09:15:05.894Z", "policy_id": null, "total": 0, "not_yet_notified": 0, "notice": { "delivered": 0, "in_progress": 0, "unreachable": 0, "expired": 0, "revoked": 0, "not_matched": 0, "sent": 0, "bounced": 0, "renotified_via_lookup": 0, "renotified_delivered": 0, "email_opened": 0 }, "reminders": [], "outcome": { "delivered": 0, "in_progress": 0, "unreachable": 0, "expired": 0, "revoked": 0, "not_matched": 0 }, "confirmed_direct": 0, "rescued_by_followups": 0, "delivered_total": 0 }, "funnel": { "total": 0, "notified": 0, "opened": 0, "authenticated": 0, "confirmed_direct": 0, "rescued_by_followups": 0, "delivered_total": 0, "expired": 0, "revoked": 0, "auth_failed": 0, "bounced": 0, "paper_pending": 0, "needs_address": 0, "bounce_rate": 0, "median_time_to_confirm": null }, "kpis": { "delivered": 0, "delivered_pct": 0, "in_progress": 0, "median_time_to_delivery": null, "bounce_rate": 0, "rescued_by_followups": 0, "spend": { "sms_messages": 0, "lookups": 0, "amount": 0, "currency": "ISK", "estimated": true }, "vs_previous": null } } ``` --- # Events Source: https://parasend.com/docs/api/events Global audit event log (polling fallback for webhooks). ## Endpoints - [List events](https://parasend.com/docs/api/events/list.md): `GET /v1/events` - [Retrieve an event](https://parasend.com/docs/api/events/retrieve.md): `GET /v1/events/{id}` - [See where an event was sent](https://parasend.com/docs/api/events/webhooks.md): `GET /v1/events/{id}/webhooks` ## The Event object Thin payload + fetch — no recipient PII or document names; consumers fetch the resource, re-authorized through the key matrix. - `id` (string, optional): The event's ID, starting `evt_`. - `object` (string, optional): Always `event`. - `type` (string, optional): Every event type. `delivery.*` events follow a delivery from creation to its end, `batch.*` a batch, and `member.*`, `email_sender.*` and `recipient_domain.*` your account. `delivery.email_opened` is a hint (the e-mail client loaded an image), never proof of receipt; `delivery.confirmed` is the recipient confirming receipt. `delivery.auth_failed` carries a `reason`, such as `personal_code_mismatch` or `id_document_mismatch`. The `secret.*`, `request.*` and `lookup.*` events belong to features that are not available yet. New types may be added: ignore types you do not know. One of: `delivery.created`, `delivery.rejected`, `delivery.notified`, `delivery.opened`, `delivery.email_opened`, `delivery.auth_failed`, `delivery.confirmed`, `delivery.downloaded`, `delivery.delivered`, `delivery.reminded`, `delivery.bounced`, `delivery.expired`, `delivery.revoked`, `delivery.consignment_failed`, `delivery.evidence_sealed`, `delivery.evidence_timestamp_deferred`, `delivery.evidence_timestamp_recovered`, `delivery.paper_pending_approval`, `delivery.paper_dispatched`, `delivery.paper_failed`, `document.previewed`, `batch.finalized`, `batch.completed`, `lookup.completed`, `consent.granted`, `consent.withdrawn`, `invitation.sent`, `link.opened`, `otp.sent`, `otp.verified`, `otp.locked`, `secret.attached`, `secret.available`, `secret.revealed`, `secret.copied`, `secret.expired`, `secret.exhausted`, `secret.revoked`, `request.sent`, `request.opened`, `request.fulfilled`, `request.expired`, `request.closed`, `request.reopened`, `deletion.scheduled`, `recipient_domain.verified`, `recipient_domain.degraded`, `recipient_domain.failed`, `member.suspended`, `member.restored`, `member.removed`, `batch.cancelled`, `batch.policy_changed`, `delivery.policy_changed`, `email_sender.verified`, `email_sender.degraded`, `email_sender.failed`, `email_sender.restored`. - `created` (string · date-time, optional): When it was created, in ISO 8601 (UTC). - `resource` (object, optional): What the event is about: its `type` (such as `delivery`, `batch` or `recipient_domain`) and `id`. The event carries no personal data; fetch the resource for its details. - `resource.type` (string, optional) - `resource.id` (string, optional) - `batch_id` (string · nullable, optional): The batch it belongs to (`bat_…`), or null when it is not part of a batch. - `reason` (string · nullable, optional): Reason code where the type defines one (delivery.auth_failed → pin_mismatch, provider_failure, timeout, cancelled; delivery.consignment_failed → assurance_not_met; delivery.rejected → the catalogue code of the refusing rule). - `data` (object · nullable, optional): Details for some event types; never personal data, never file names. Absent for most types. ```json { "id": "evt_01M6R27T08JTMBJ4B6GQP90838", "object": "event", "type": "delivery.created", "created": "2026-11-06T09:15:05.604Z", "resource": { "type": "delivery", "id": "dlv_01M6R27T05512SCV3852TPM2XY" }, "batch_id": null, "reason": null, "data": null } ``` --- # Webhook endpoints Source: https://parasend.com/docs/api/webhooks Parasend tells your system about events by sending a small JSON payload to your HTTPS endpoint; fetch the resource for its full state. Every request is signed with HMAC-SHA256 over `timestamp.body` in the `Parasend-Signature` header; refuse a timestamp more than 5 minutes old. Failed attempts are retried with exponential backoff for up to 3 days. An endpoint may also require Parasend to authenticate (`auth.mode`): `none` (the signature only, the default), `static_bearer`, or `oauth2_client_credentials` against your own identity provider. The signature is sent whatever the mode. Parasend connects from fixed IP addresses, so you can allow-list them. ## Endpoints - [Add a webhook endpoint](https://parasend.com/docs/api/webhooks/create.md): `POST /v1/webhook_endpoints` - [List webhook endpoints](https://parasend.com/docs/api/webhooks/list.md): `GET /v1/webhook_endpoints` - [Retrieve a webhook endpoint](https://parasend.com/docs/api/webhooks/retrieve.md): `GET /v1/webhook_endpoints/{id}` - [Update a webhook endpoint](https://parasend.com/docs/api/webhooks/update.md): `PATCH /v1/webhook_endpoints/{id}` - [Delete a webhook endpoint](https://parasend.com/docs/api/webhooks/delete.md): `DELETE /v1/webhook_endpoints/{id}` - [Send a test event](https://parasend.com/docs/api/webhooks/test.md): `POST /v1/webhook_endpoints/{id}/test` - [Replay events](https://parasend.com/docs/api/webhooks/replay.md): `POST /v1/webhook_endpoints/{id}/replay` - [List an endpoint's attempts](https://parasend.com/docs/api/webhooks/attempts.md): `GET /v1/webhook_endpoints/{id}/attempts` - [Roll an endpoint's signing secret](https://parasend.com/docs/api/webhooks/roll-secret.md): `POST /v1/webhook_endpoints/{id}/roll_secret` ## The WebhookEndpoint object - `id` (string, optional): The webhook endpoint's ID, starting `whk_`. - `object` (string, optional): Always `webhook_endpoint`. - `url` (string, optional): The address Parasend posts events to: HTTPS, on port 443, on a public host. - `event_types` (array of string, optional): One of: `delivery.created`, `delivery.rejected`, `delivery.notified`, `delivery.opened`, `delivery.email_opened`, `delivery.auth_failed`, `delivery.confirmed`, `delivery.downloaded`, `delivery.delivered`, `delivery.reminded`, `delivery.bounced`, `delivery.expired`, `delivery.revoked`, `delivery.consignment_failed`, `delivery.evidence_sealed`, `delivery.evidence_timestamp_deferred`, `delivery.evidence_timestamp_recovered`, `delivery.paper_pending_approval`, `delivery.paper_dispatched`, `delivery.paper_failed`, `document.previewed`, `batch.finalized`, `batch.completed`, `lookup.completed`, `consent.granted`, `consent.withdrawn`, `invitation.sent`, `link.opened`, `otp.sent`, `otp.verified`, `otp.locked`, `secret.attached`, `secret.available`, `secret.revealed`, `secret.copied`, `secret.expired`, `secret.exhausted`, `secret.revoked`, `request.sent`, `request.opened`, `request.fulfilled`, `request.expired`, `request.closed`, `request.reopened`, `deletion.scheduled`, `recipient_domain.verified`, `recipient_domain.degraded`, `recipient_domain.failed`, `member.suspended`, `member.restored`, `member.removed`, `batch.cancelled`, `batch.policy_changed`, `delivery.policy_changed`, `email_sender.verified`, `email_sender.degraded`, `email_sender.failed`, `email_sender.restored`. - `secret` (string · nullable, optional): HMAC signing secret. Present only in the creation response. - `auth` (object, optional): How Parasend authenticates to your endpoint, in addition to the HMAC signature, which is always sent. Secrets are write-only: accepted on create and update, never returned. The endpoint shows `mode`, the settings that are not secret, and whether a secret is set. - `auth.mode` (string, required): none = HMAC only. static_bearer = an `Authorization` header carrying `Bearer ` with a customer-supplied token. oauth2_client_credentials = a token obtained from the customer's IdP by the client-credentials grant, cached until expiry, sent as the bearer. One of: `none`, `static_bearer`, `oauth2_client_credentials`. Default: `"none"`. - `auth.static_bearer` (object · nullable, optional): With `mode: static_bearer`: the `token` Parasend sends as `Authorization: Bearer `, at most 4,096 characters (write-only; responses show `token_set`). null for any other mode. - `auth.static_bearer.token_set` (boolean, optional) - `auth.oauth2` (object · nullable, optional): With `mode: oauth2_client_credentials`: your token endpoint (`token_url`, HTTPS), `client_id`, `client_secret` (write-only; responses show `client_secret_set`), optional `scope` and `audience`, and `auth_style`. null for any other mode. - `auth.oauth2.token_url` (string · uri, optional): HTTPS only. - `auth.oauth2.client_id` (string, optional) - `auth.oauth2.client_secret_set` (boolean, optional) - `auth.oauth2.scope` (string · nullable, optional) - `auth.oauth2.audience` (string · nullable, optional) - `auth.oauth2.auth_style` (string, optional): One of: `client_secret_basic`, `client_secret_post`. Default: `"client_secret_basic"`. - `status` (string, optional): `enabled`: events are sent to it. `disabled`: paused; new events are not queued for it, and pending retries are cancelled when they come due. One of: `enabled`, `disabled`. - `created` (string · date-time, optional): When it was created, in ISO 8601 (UTC). - `livemode` (boolean, optional): `true` in live mode, `false` in test mode. - `description` (string · nullable, optional): Your own note about the endpoint. - `created_by` (object · nullable, optional): Who created the endpoint: a person, or the holder of the API key used. null for endpoints created before this was recorded. - `created_by.user_id` (string, optional) - `created_by.name` (string · nullable, optional) - `created_by.via` (string, optional): One of: `portal`, `api_key`. - `exhausted_events` (integer, optional): Events that ran out of retries and have not been replayed successfully. More than 0 marks the endpoint in the portal. - `last_exhausted_at` (string · date-time · nullable, optional): When an event last ran out of retries on this endpoint, in ISO 8601 (UTC); null if none ever has. - `retrying_events` (integer, optional): Events with at least one failed attempt whose retries are still running. - `last_attempt` (object · nullable, optional): The endpoint's latest attempt, in brief; null before the first. - `previous_secret_expires_at` (string · date-time · nullable, optional): Set for 24 hours after a roll; null otherwise. - `delivery_sources` (object · nullable, optional): null: every delivery's events. ```json { "id": "whk_01M6R27T4KCSN7M0CHFGVH7JRA", "object": "webhook_endpoint", "url": "https://hooks.fyrirtaeki.example/parasend", "event_types": [ "delivery.notified", "delivery.confirmed", "delivery.evidence_sealed" ], "description": "Case system", "secret": null, "auth": { "mode": "none", "static_bearer": null, "oauth2": null }, "delivery_sources": null, "status": "enabled", "livemode": false, "exhausted_events": 0, "last_exhausted_at": null, "retrying_events": 0, "last_attempt": null, "created_by": { "user_id": "usr_01M6R27SYD4B50414S0BZ4KBKR", "name": "Jóna Jónsdóttir", "via": "api_key" }, "previous_secret_expires_at": null, "created": "2026-11-06T09:15:05.746Z" } ``` --- # Evidence Source: https://parasend.com/docs/api/evidence One evidence record per delivery, built item by item as events occur and complete at terminal state. Immutable and read-only by definition — no permission can grant create or delete. The recipient of a confirmed or consignment_failed delivery can obtain the same artefacts on both tiers (N7, SL-14b) — through the delivery session that confirmed, at parasend.is/me, or by the documented interested-party procedure. ## Endpoints - [Get the Delivery Certificate (PDF)](https://parasend.com/docs/api/evidence/certificate.md): `GET /v1/deliveries/{id}/evidence/document` - [Get the evidence record (XML)](https://parasend.com/docs/api/evidence/record.md): `GET /v1/deliveries/{id}/evidence/record` - [Get a recipient's verification photo](https://parasend.com/docs/api/evidence/recipient-photo.md): `GET /v1/deliveries/{id}/recipient_photos/{kind}` --- # Record check Source: https://parasend.com/docs/api/verify Checking a Delivery Certificate. ## Endpoints - [Check a delivery record by its evidence code](https://parasend.com/docs/api/verify/check.md): `POST /v1/public/verify` ## The PublicVerification object One of three answers: `found: false`; `found: true, ready: false` (ask again in a few seconds); or the record. - `object` (string, required): Always `public_verification`. One of: `public_verification`. - `found` (boolean, required): Whether a delivery record has this evidence ID. A mistyped, malformed or unknown code all get the same answer: `found: false` and nothing more. - `ready` (boolean, optional): False while the record's latest event is still being sealed: ask again in a few seconds. Present only when `found` is true. - `evidence_id` (string, optional): The evidence record's ID (`evd_…`), as printed on the Delivery Certificate. - `delivery_id` (string, optional): The ID of the delivery (`dlv_…`) the record belongs to. - `test` (boolean, optional): A test-mode delivery: nothing was sent. - `issuer` (object · nullable, optional): Who issued the evidence, as sealed in the record: the provider's legal `name` and its `identifier`. null when the record does not say. - `issuer.name` (string · nullable, optional) - `issuer.identifier` (string · nullable, optional) - `sender` (object · nullable, optional): The sender: `kind` (`organisation` or `individual`); for a company its `name`, with `basis` saying how the name was established, and its `kennitala` (Icelandic company number) when confirmed in the company registry. A person is never named. null when the record names no sender. - `sender.kind` (string, optional): One of: `organisation`, `individual`. - `sender.basis` (string · nullable, optional): `registry`: confirmed in the company registry. `declared`: as the sender gave it. `note-only`: an older record that names the company only in free text, which is not shown. Null for an individual. One of: `registry`, `declared`, `note-only`. - `sender.name` (string · nullable, optional): A company's name. A person is never named. - `sender.kennitala` (string · nullable, optional): Registry-confirmed companies only. - `recipient` (object, optional): The recipient, never by name: `kind` is `individual` when a person identified themselves (else null), and `identified` gives the method, the eIDAS assurance level (`High`, `Substantial`, `Low` or `None`), whether the identity itself was verified, and when. - `recipient.kind` (string · nullable, optional): One of: `individual`. - `recipient.identified` (object · nullable, optional) - `recipient.identified.method` (string · nullable, optional) - `recipient.identified.level` (string · nullable, optional): One of: `High`, `Substantial`, `Low`, `None`. - `recipient.identified.verified` (boolean, optional) - `recipient.identified.at` (string · date-time · nullable, optional) - `created_at` (string · date-time · nullable, optional): When the delivery's first sealed event happened, in ISO 8601 (UTC); null when the record has none. - `record_sealed_at` (string · date-time · nullable, optional): When the record as a whole was sealed, in ISO 8601 (UTC); null while it is not. - `seals` (object, optional): Parasend's check of the stored record: `intact`, every seal checks out and the record is the one asked for; `items`, the number of sealed events; `envelope_sealed`, whether the record as a whole is sealed. - `seals.intact` (boolean, optional) - `seals.items` (integer, optional) - `seals.envelope_sealed` (boolean, optional) - `events` (array of object, optional): Every sealed event, in order: Parasend's event type (`event`, such as `delivery.created`), the ETSI evidence event (`etsi_event`, such as `SubmissionAcceptance`), its `time` and its reason codes (`reasons`). Never free text or addresses. - `events[].event` (string · nullable, optional) - `events[].etsi_event` (string, optional) - `events[].time` (string · date-time · nullable, optional) - `events[].reasons` (array of string, optional) - `documents` (array of object, optional): The delivered documents, each by its SHA-256 digest only (64 hexadecimal characters), never by file name. - `documents[].sha256` (string · nullable, optional) --- # Follow-up policies Source: https://parasend.com/docs/api/follow-up-policies Reminder/escalation and notification policies. Escalation steps `{after_days, channel, conditions[], min_score?, requires_approval?}` with channels email | sms | alternate_email | paper. ## Endpoints - [Create a follow-up policy](https://parasend.com/docs/api/follow-up-policies/create.md): `POST /v1/reminder_policies` - [List follow-up policies](https://parasend.com/docs/api/follow-up-policies/list.md): `GET /v1/reminder_policies` - [Retrieve a follow-up policy](https://parasend.com/docs/api/follow-up-policies/retrieve.md): `GET /v1/reminder_policies/{id}` - [Update a follow-up policy](https://parasend.com/docs/api/follow-up-policies/update.md): `PATCH /v1/reminder_policies/{id}` - [Delete a follow-up policy](https://parasend.com/docs/api/follow-up-policies/delete.md): `DELETE /v1/reminder_policies/{id}` - [List a follow-up policy's versions](https://parasend.com/docs/api/follow-up-policies/versions.md): `GET /v1/reminder_policies/{id}/versions` - [Preview a follow-up step](https://parasend.com/docs/api/follow-up-policies/preview-step.md): `POST /v1/reminder_policies/preview` - [Duplicate a follow-up policy](https://parasend.com/docs/api/follow-up-policies/duplicate.md): `POST /v1/reminder_policies/{id}/duplicate` - [Reset one of Parasend's policies](https://parasend.com/docs/api/follow-up-policies/reset.md): `POST /v1/reminder_policies/{id}/reset` ## The ReminderPolicy object - `name` (string, required): Your name for the policy: 1–100 characters, without control characters; leading and trailing spaces are trimmed. - `steps` (array of object, required) - `steps[].after_days` (integer, required): Days after the first notice when the step is due: a whole number from 1 to 365, never smaller than the step before it. The reminder goes within the send window and at least 24 hours after the delivery's last message. - `steps[].channel` (string, required): How the step's reminder goes: `email` or `sms`. A step whose channel the recipient has no contact for is skipped. `alternate_email` and `paper` are refused with `policy_step_invalid` until they are available. One of: `email`, `sms`, `alternate_email`, `paper`. - `steps[].conditions` (array of string, optional): What must all hold for the step to go, else it is skipped: `not_delivered` (always included, listed or not); `email_not_opened`, no e-mail to the recipient has been opened (a hint, not proof); `email_bounced`, an e-mail to the recipient bounced or failed. One of: `not_delivered`, `email_not_opened`, `email_bounced`. - `steps[].requires_approval` (boolean · nullable, optional): Paper defaults to approval-required. - `steps[].available` (boolean, optional): Responses only. False for a step that cannot run yet and is skipped (only the paper step of `rpol_debt_collection`); true otherwise. - `steps[].message` (object, optional): The wording a follow-up step's reminder sends. Left out: Parasend's standard reminder. `preset` picks Parasend's wording; `custom` holds your own, by language (Plan 1–3 and Enterprise). A field or language left out comes from the preset. - `steps[].message.preset` (string, optional): `standard` is the usual reminder; `firmer` and `final` are Parasend's stronger wordings. One of: `standard`, `firmer`, `final`. Default: `"standard"`. - `steps[].message.custom` (object, optional): Your own wording, by language (`is`, `en`); a field or a language left out comes from `preset`. Needs Plan 1–3 or Enterprise; otherwise refused with `theme_knob_plan_gated`. - `steps[].message.custom.is` (object, optional): Plain text. An e-mail step takes `subject`, `heading` and `text`; an SMS step `text` only. Variables: {{recipient.name}}, {{recipient.first_name}}, {{sender.name}}, {{delivery.title}}, {{delivery.sent_on}}, {{delivery.expires_on}}, {{files.count}}; an SMS only the two dates. No links, e-mail addresses or phone numbers. An e-mail may hold the gender word {{recipient.gender: male | female | other}}. - `steps[].message.custom.en` (object, optional): Plain text. An e-mail step takes `subject`, `heading` and `text`; an SMS step `text` only. Variables: {{recipient.name}}, {{recipient.first_name}}, {{sender.name}}, {{delivery.title}}, {{delivery.sent_on}}, {{delivery.expires_on}}, {{files.count}}; an SMS only the two dates. No links, e-mail addresses or phone numbers. An e-mail may hold the gender word {{recipient.gender: male | female | other}}. - `steps[].message.source` (object, optional): The saved wording and version this step's wording was loaded from. A record only: the step keeps its own copy. Not on a saved wording's own message. - `steps[].message.source.wording` (string, required) - `steps[].message.source.version` (integer, required) - `stop_on` (string, optional): When the follow-ups stop: `confirmed`, the only value, once the recipient confirms receipt. - `is_account_default` (boolean, optional): The organisation's pre-fill for new batches and deliveries; never applied by itself. Needs organization.update. Default: `false`. - `id` (string, optional): The policy's ID. Parasend's own policies are `rpol_default`, `rpol_debt_collection` and `rpol_none`. - `object` (string, optional): Always `reminder_policy`. - `system` (boolean, optional): One of Parasend's own policies (`rpol_default`, `rpol_debt_collection`, `rpol_none`). The steps of the first two can be made your organisation's own version (`customized`); none can be renamed or deleted. - `customized` (boolean, optional): Your organisation's own version of one of Parasend's policies is in use. - `version` (integer, optional): The version in use in this organisation — for one of Parasend's, its own while customized. - `version_id` (string, optional): The follow-up policy version's ID, starting `rpv_`. - `in_use_by_batches` (integer, optional): Open batches (draft or sending) that name the policy. - `in_use_by_deliveries` (integer, optional): Deliveries that cite the policy and are not yet confirmed or ended. - `created_at` (string · date-time, optional): When the policy was created, in ISO 8601 (UTC). - `updated_at` (string · date-time, optional): When the policy last changed (renamed, given a new version, or archived), in ISO 8601 (UTC). For one of Parasend's policies with your own version, when your version last changed. - `archived_at` (string · date-time · nullable, optional): When the policy was archived with `DELETE /reminder_policies/{id}`, in ISO 8601 (UTC); null while it is in use. Parasend's own policies are never archived. ```json { "id": "rpol_01M6R27T5NK3830GCN9QPS9M95", "object": "reminder_policy", "name": "Payment reminders", "system": false, "version": 1, "version_id": "rpv_01M6R27T5NB7JTSEZ0XV7BWETG", "steps": [ { "channel": "email", "after_days": 3, "conditions": [ "not_delivered" ], "available": true }, { "channel": "email", "message": { "preset": "firmer" }, "after_days": 7, "conditions": [ "not_delivered" ], "available": true }, { "channel": "sms", "after_days": 10, "conditions": [ "not_delivered" ], "available": true } ], "stop_on": "confirmed", "customized": false, "is_account_default": false, "in_use_by_batches": 0, "in_use_by_deliveries": 0, "created_at": "2026-11-06T09:15:05.781Z", "updated_at": "2026-11-06T09:15:05.781Z", "archived_at": null } ``` --- # Saved wordings Source: https://parasend.com/docs/api/wordings Reminder/escalation and notification policies. Escalation steps `{after_days, channel, conditions[], min_score?, requires_approval?}` with channels email | sms | alternate_email | paper. ## Endpoints - [Save a wording](https://parasend.com/docs/api/wordings/create.md): `POST /v1/wordings` - [List saved wordings](https://parasend.com/docs/api/wordings/list.md): `GET /v1/wordings` - [Retrieve a saved wording](https://parasend.com/docs/api/wordings/retrieve.md): `GET /v1/wordings/{id}` - [Update a saved wording](https://parasend.com/docs/api/wordings/update.md): `PATCH /v1/wordings/{id}` - [Archive a saved wording](https://parasend.com/docs/api/wordings/archive.md): `DELETE /v1/wordings/{id}` - [List a wording's versions](https://parasend.com/docs/api/wordings/versions.md): `GET /v1/wordings/{id}/versions` - [Retrieve a wording version](https://parasend.com/docs/api/wordings/version.md): `GET /v1/wordings/{id}/versions/{version}` - [Preview a wording](https://parasend.com/docs/api/wordings/preview.md): `POST /v1/wordings/preview` - [Check what wordings may contain](https://parasend.com/docs/api/wordings/capabilities.md): `GET /v1/wordings/capabilities` ## The Wording object A saved wording: your organisation's text for notices or follow-up steps, kept with its versions. - `id` (string, optional): The wording's ID, starting `wrd_`. - `object` (string, optional): Always `wording`. - `name` (string, optional): Your name for the wording, 1–100 characters. Recipients never see it. - `use` (string, optional): `notice` or `reminder`; fixed once the wording is made. One of: `reminder`, `notice`. - `channel` (string, optional): The channel the wording is for, `email` or `sms`; fixed once the wording is made. One of: `email`, `sms`. - `is_default` (boolean, optional): The first-notice wording for its channel. - `version` (integer, optional): The current version's number: 1 for the first, one more for each change to `message`. - `version_id` (string, optional): The wording version's ID, starting `wrv_`. - `message` (object, optional): The wording a follow-up step's reminder sends. Left out: Parasend's standard reminder. `preset` picks Parasend's wording; `custom` holds your own, by language (Plan 1–3 and Enterprise). A field or language left out comes from the preset. - `message.preset` (string, optional): `standard` is the usual reminder; `firmer` and `final` are Parasend's stronger wordings. One of: `standard`, `firmer`, `final`. Default: `"standard"`. - `message.custom` (object, optional): Your own wording, by language (`is`, `en`); a field or a language left out comes from `preset`. Needs Plan 1–3 or Enterprise; otherwise refused with `theme_knob_plan_gated`. - `message.custom.is` (object, optional): Plain text. An e-mail step takes `subject`, `heading` and `text`; an SMS step `text` only. Variables: {{recipient.name}}, {{recipient.first_name}}, {{sender.name}}, {{delivery.title}}, {{delivery.sent_on}}, {{delivery.expires_on}}, {{files.count}}; an SMS only the two dates. No links, e-mail addresses or phone numbers. An e-mail may hold the gender word {{recipient.gender: male | female | other}}. - `message.custom.is.subject` (string, optional): One line. Empty is the preset's. - `message.custom.is.heading` (string, optional): One line, in place of the reminder's heading. - `message.custom.is.text` (string, optional): A paragraph under the heading; for an SMS, between the sender's name and the link (3.5). - `message.custom.en` (object, optional): Plain text. An e-mail step takes `subject`, `heading` and `text`; an SMS step `text` only. Variables: {{recipient.name}}, {{recipient.first_name}}, {{sender.name}}, {{delivery.title}}, {{delivery.sent_on}}, {{delivery.expires_on}}, {{files.count}}; an SMS only the two dates. No links, e-mail addresses or phone numbers. An e-mail may hold the gender word {{recipient.gender: male | female | other}}. - `message.custom.en.subject` (string, optional): One line. Empty is the preset's. - `message.custom.en.heading` (string, optional): One line, in place of the reminder's heading. - `message.custom.en.text` (string, optional): A paragraph under the heading; for an SMS, between the sender's name and the link (3.5). - `message.source` (object, optional): The saved wording and version this step's wording was loaded from. A record only: the step keeps its own copy. Not on a saved wording's own message. - `message.source.wording` (string, required) - `message.source.version` (integer, required) - `created_at` (string · date-time, optional): When the wording was saved, in ISO 8601 (UTC). - `updated_at` (string · date-time, optional): When the wording last changed (renamed, given a new version, made or no longer the default, or archived), in ISO 8601 (UTC). - `archived_at` (string · date-time · nullable, optional): When the wording was archived with `DELETE /wordings/{id}`, in ISO 8601 (UTC); null while it is in use. Policies that loaded it keep their copy. - `used_by` (object, optional): How many follow-up policies use this wording, and how many of them use an older version. - `used_by.policies` (integer, optional) - `used_by.policies_on_older_version` (integer, optional) ```json { "id": "wrd_01M6R27T6WN5ZPHV4DA9SGQ3G6", "object": "wording", "name": "Second reminder", "use": "reminder", "channel": "email", "is_default": false, "version": 1, "version_id": "wrv_01M6R27T6WR1V5QFE0E3SKRHNW", "message": { "preset": "firmer" }, "created_at": "2026-11-06T09:15:05.820Z", "updated_at": "2026-11-06T09:15:05.820Z", "archived_at": null, "used_by": { "policies": 0, "policies_on_older_version": 0 } } ``` --- # Brand profiles Source: https://parasend.com/docs/api/brand-profiles Eight plan-gated theme knobs, WCAG contrast validated at save. Trust chrome and "Powered by Parasend · by Justikal" are outside customer control on every tier. ## Endpoints - [Create a brand profile](https://parasend.com/docs/api/brand-profiles/create.md): `POST /v1/brand_profiles` - [List brand profiles](https://parasend.com/docs/api/brand-profiles/list.md): `GET /v1/brand_profiles` - [Retrieve a brand profile](https://parasend.com/docs/api/brand-profiles/retrieve.md): `GET /v1/brand_profiles/{id}` - [Update a brand profile](https://parasend.com/docs/api/brand-profiles/update.md): `PATCH /v1/brand_profiles/{id}` - [Delete a brand profile](https://parasend.com/docs/api/brand-profiles/delete.md): `DELETE /v1/brand_profiles/{id}` - [List a brand profile's versions](https://parasend.com/docs/api/brand-profiles/versions.md): `GET /v1/brand_profiles/{id}/versions` - [Preview a brand profile](https://parasend.com/docs/api/brand-profiles/preview.md): `POST /v1/brand_profiles/preview` ## The BrandProfileV1 object - `sender_display_name` (string, required): Your name as recipients see it, in the notice ("Delivery from …") and on the recipient page. - `sender_display_name_dative` (string · nullable, required): Your name in the Icelandic dative case, for Icelandic notices ("Sending frá …"). null when not set; the notice then says "Sending frá:" followed by `sender_display_name`. - `logo` (object · nullable, required): The logo: its asset ID (`bas_…`), the path of its PNG, the PNG's SHA-256, and its size in CSS pixels. null when there is no logo; the monogram is shown instead. - `monogram` (string, required): 1–3 upper-case letters or digits shown where there is no logo. Unless you set it, the first letters of the first two words of `sender_display_name`, leaving out company forms such as hf. and ehf. - `primary_color` (string, required): Your brand colour, `#RRGGBB` in upper case: the header band, links, and the buttons unless `theme.colors.button_background` sets their own. - `contrast_ratio` (number, required): The button text (white unless theme.colors.button_text) on primary_color, two decimals, rounded down. - `header_style` (string, required): The header of the recipient page and the notice e-mail: `band`, your `primary_color` across the top with the logo on it; `letterhead`, a plain header with a rule in your colour along its top; `minimal`, a plain header with your logo or name. One of: `band`, `letterhead`, `minimal`. - `support` (object, required): Your own contact for recipients, in the support line of the recipient page: `email`, `phone` and `url`, each null when not set. - `support.email` (string · nullable, optional) - `support.phone` (string · nullable, optional) - `support.url` (string · nullable, optional) - `expired_message` (object, required): Your own words on the page a recipient sees when their link has expired, in Icelandic (`is`) and English (`en`). Plain text; each null when not set. - `expired_message.is` (string · nullable, optional) - `expired_message.en` (string · nullable, optional) - `reply_to` (string · nullable, required): The Reply-To address of the e-mails sent to recipients (the notice and reminders), so replies come to you. null: no Reply-To. - `id` (string, required): The brand profile's ID, starting `bp_`. - `object` (string, required): Always `brand_profile`. - `name` (string, required): Your name for the profile, 1–100 characters. Recipients never see it. - `is_default` (boolean, required): Whether this is your organisation's default profile, used for any delivery that names none (directly or through its batch). Only one profile is the default. - `version` (integer, required): The current version's number. - `version_id` (string, required): `bpv_…`: the version a delivery created now would use. - `used_by` (object, required): How much the profile is used: `deliveries`, the number of deliveries sent with one of its versions. - `used_by.deliveries` (integer, required): Deliveries that froze one of its versions (a count; names none). - `created_at` (string · date-time, required): When the profile was created, in ISO 8601 (UTC). - `updated_at` (string · date-time, required): When the profile last changed (renamed, made or no longer the default, given a new version, or archived), in ISO 8601 (UTC). - `archived_at` (string · date-time · nullable, required): When the profile was archived with `DELETE /brand_profiles/{id}`, in ISO 8601 (UTC); null while it is in use. Deliveries already sent with it keep its look. - `sms_sender_name` (object · nullable, optional): The SMS sender name and Parasend's answer to it, or null (PARASEND). - `sms_sender_name.name` (string, required) - `sms_sender_name.state` (string, required): One of: `pending`, `approved`, `rejected`. - `sms_sender_name.ready` (boolean, required): Approved and set up: SMS to Icelandic numbers show it. - `sms_sender_name.reason` (string · nullable, required): Why Parasend did not approve it. - `icon` (object · nullable, optional): The tab icon, or null. - `icon.asset_id` (string, optional): The brand asset's ID, starting `bas_`. - `icon.url` (string, optional): The 180 px PNG (/brand_assets/{id}/content). - `icon.small_url` (string, optional): The 32 px PNG (?size=small). - `icon.sha256` (string, optional): Of the 180 px PNG. - `recipient_domain_id` (string · nullable, optional): The link domain (`cdm_…`), or null. - `email_sender_id` (string · nullable, optional): The e-mail sender (`es_…`), or null. - `theme` (object, optional): The look a version sets. Every field may be null (Parasend's own) and, in a PATCH, left out (unchanged); `theme: null` resets every field. Checked when saved: every text and background pair must reach a contrast of 4.5:1. No CSS. - `theme.colors` (object · nullable, optional): Colours of the recipient page, each `#RRGGBB` or null for Parasend's default; `button_background` and `button_text` colour the e-mail's button too. In an update, `colors: null` resets them all. - `theme.colors.page_background` (string · nullable, optional): Default #F5F6FA. - `theme.colors.card_background` (string · nullable, optional): Default #FFFFFF. - `theme.colors.text` (string · nullable, optional): Default #0E1526. - `theme.colors.secondary_text` (string · nullable, optional): Default #5B6472. - `theme.colors.button_background` (string · nullable, optional): The buttons' colour, on the page and in the e-mail. Default `primary_color`. - `theme.colors.button_text` (string · nullable, optional): Default #FFFFFF. - `theme.colors.header_background` (string · nullable, optional) - `theme.colors.header_text` (string · nullable, optional) - `theme.colors.divider` (string · nullable, optional): Default #E6E9F2. - `theme.button_radius` (integer · nullable, optional): Default 12. - `theme.card_radius` (integer · nullable, optional): Default 14. - `theme.card_shadow` (boolean · nullable, optional): Default true. - `theme.font` (string · nullable, optional): The font, served by the recipient page. Default `inter`. One of: `inter`, `system`, `source_sans`, `source_serif`. - `theme.logo_height` (integer · nullable, optional): CSS px. Default the header style's own. - `theme.header_align` (string · nullable, optional): Default left. One of: `left`, `center`. - `theme.show_safety_note` (boolean · nullable, optional): The note “Er þetta öruggt?” (Is this safe?) on the page and in the e-mail. Default true. - `theme.show_support_line` (boolean · nullable, optional): Default true. - `theme.show_sender_line` (boolean · nullable, optional): The “Sendandi:” (Sender:) line. The page leaves it out only where the header names the sender in words; false always leaves it out of the e-mail's footer. Default true. - `theme.card_footer` (string · nullable, optional): default: the date; custom: the sender's words and the date; hidden: none. custom needs card_footer_text. One of: `default`, `custom`, `hidden`. - `theme.card_footer_text` (object · nullable, optional): Your own words for the card's footer when `card_footer` is `custom`, in Icelandic (`is`) and English (`en`): plain text, up to 200 characters each. `custom` needs at least one of them. - `theme.card_footer_text.is` (string · nullable, optional) - `theme.card_footer_text.en` (string · nullable, optional) - `theme.layout` (string · nullable, optional): The page layout. Default `card`; every other layout needs `page_image_asset_id`. One of: `card`, `split`, `banner`, `backdrop`. - `theme.page_image_asset_id` (string · nullable, optional): `bas_…` of a photo uploaded with POST /brand_assets?kind=image. - `theme.logo_background` (string · nullable, optional): The header's logo on a white tile (`tile`, or null) or straight on the colour (`none`). Not set with a new logo: `none` for a transparent logo, `tile` otherwise. One of: `tile`, `none`. - `theme.show_service_label` (boolean · nullable, optional): The label above the e-mail's headline that names the service (“Electronic registered delivery” in English). null is on. The e-mail only. - `theme.show_name_with_logo` (boolean · nullable, optional): The sender's name beside the logo in the header, in every header style. null is off. - `contrasts` (object, optional): The contrast ratio of every colour pair when saved, rounded down to two decimals: `button_text_on_primary` (the button text on the button's colour), `links_on_card`, `text_on_card`, `secondary_text_on_card`, `header_text_on_header`, and `logo_on_header` for a logo straight on the band (a warning below 3, never a refusal). ```json { "id": "bp_01M6R27T817SW9MENZEV3KSZZX", "object": "brand_profile", "name": "Fyrirtæki ehf.", "is_default": false, "version": 1, "version_id": "bpv_01M6R27T82QZMJ98K4RGVQBRF0", "sender_display_name": "Fyrirtæki ehf.", "sender_display_name_dative": null, "logo": { "asset_id": "bas_01M6R27T7R9RDBHE6X72ZRW3YC", "url": "/v1/brand_assets/bas_01M6R27T7R9RDBHE6X72ZRW3YC/content", "sha256": "f72bdd36e6ce104c53c8f21bd9e618e113a66f4d6729d43a4cb07b3f1373addb", "width": 200, "height": 48 }, "monogram": "F", "sms_sender_name": null, "icon": null, "primary_color": "#1E5BB8", "contrast_ratio": 6.47, "header_style": "band", "support": { "email": null, "phone": null, "url": null }, "expired_message": { "is": null, "en": null }, "reply_to": null, "recipient_domain_id": null, "email_sender_id": null, "theme": { "colors": { "page_background": null, "card_background": null, "text": null, "secondary_text": null, "button_background": null, "button_text": null, "header_background": null, "header_text": null, "divider": null }, "button_radius": null, "card_radius": null, "card_shadow": null, "font": null, "logo_height": null, "header_align": null, "show_safety_note": null, "show_support_line": null, "show_sender_line": null, "card_footer": null, "card_footer_text": { "is": null, "en": null }, "layout": null, "page_image_asset_id": null, "logo_background": "tile", "show_name_with_logo": null, "show_service_label": null }, "contrasts": { "text_on_card": 18.19, "links_on_card": 6.47, "header_text_on_header": 6.47, "button_text_on_primary": 6.47, "secondary_text_on_card": 5.98 }, "used_by": { "deliveries": 0 }, "created_at": "2026-11-06T09:15:05.856Z", "updated_at": "2026-11-06T09:15:05.856Z", "archived_at": null } ``` --- # Brand assets Source: https://parasend.com/docs/api/brand-assets Eight plan-gated theme knobs, WCAG contrast validated at save. Trust chrome and "Powered by Parasend · by Justikal" are outside customer control on every tier. ## Endpoints - [Upload a brand asset](https://parasend.com/docs/api/brand-assets/upload.md): `POST /v1/brand_assets` - [Retrieve a brand asset](https://parasend.com/docs/api/brand-assets/retrieve.md): `GET /v1/brand_assets/{id}` - [Download a brand asset](https://parasend.com/docs/api/brand-assets/download.md): `GET /v1/brand_assets/{id}/content` ## The BrandAsset object - `id` (string, required): The brand asset's ID, starting `bas_`. - `object` (string, required): Always `brand_asset`. - `kind` (string, required): `logo`, `image` (a page photo) or `icon` (the browser tab's icon). One of: `logo`, `image`, `icon`. - `content_type` (string, required): What is served — a logo's or an icon's PNG, a photo's WebP. One of: `image/png`, `image/webp`. - `source_type` (string, required): What was uploaded (a logo's is kept, never served; a photo's is not kept). One of: `image/png`, `image/svg+xml`, `image/jpeg`, `image/webp`. - `sha256` (string, required): Of the PNG (a photo's large WebP). - `width` (integer, required): A logo: CSS pixels (the PNG is 3× this). A photo: pixels of the large rendition. An icon: 180. - `height` (integer, required): A logo: CSS pixels. A photo: pixels of the large rendition. An icon: 180. - `url` (string, required): The path of the image itself, `/v1/brand_assets/{id}/content`: a logo's or an icon's PNG, or a photo's large WebP. Fetch it with your API key, like any other request. - `created_at` (string · date-time, required): When the asset was uploaded, in ISO 8601 (UTC). - `small` (object, optional): A photo: the small rendition, about 800 pixels wide. An icon: the 32 pixel PNG. - `small.url` (string, optional) - `small.width` (integer, optional) - `small.height` (integer, optional) - `transparent_background` (boolean · nullable, optional): A logo only: whether most of its outer edge is transparent. null for a photo or an icon. ```json { "id": "bas_01M6R27T7R9RDBHE6X72ZRW3YC", "object": "brand_asset", "kind": "logo", "content_type": "image/png", "source_type": "image/png", "sha256": "f72bdd36e6ce104c53c8f21bd9e618e113a66f4d6729d43a4cb07b3f1373addb", "width": 200, "height": 48, "url": "/v1/brand_assets/bas_01M6R27T7R9RDBHE6X72ZRW3YC/content", "transparent_background": false, "created_at": "2026-11-06T09:15:05.848Z" } ``` --- # Link domains Source: https://parasend.com/docs/api/link-domains ## Endpoints - [List link domains](https://parasend.com/docs/api/link-domains/list.md): `GET /v1/recipient_domains` - [Retrieve a link domain](https://parasend.com/docs/api/link-domains/retrieve.md): `GET /v1/recipient_domains/{id}` - [Check a link domain](https://parasend.com/docs/api/link-domains/check.md): `POST /v1/recipient_domains/{id}/check` ## The RecipientDomain object - `id` (string, required): The link domain's ID, starting `cdm_`. - `object` (string, required): Always `recipient_domain`. - `hostname` (string, required): The hostname your delivery links use, such as `afhending.fyrirtaeki.example`: lower case, an internationalised name in its ASCII (`xn--`) form. At least three labels, so not a bare domain, and never under parasend.com. - `state` (string, required): `pending_dns`: the ownership TXT or the CNAME is not seen yet. `verifying`: both are seen and the domain is being set up. `issuing_cert`: its certificate is being issued. `active`: links use it. `degraded`: it was active and a check now fails. `failed`: it could not be set up, or stayed degraded for 72 hours. `removed`: taken off your list. One of: `pending_dns`, `verifying`, `issuing_cert`, `active`, `degraded`, `failed`, `removed`. - `reason` (string · nullable, required): Why it waits, is degraded or failed: ownership_not_found, cname_not_found, lookup_failed, tenants_off, certificate_pending, deploying, dns_not_pointing, claimed_elsewhere, hostname_in_use_elsewhere (the hostname is still on another CloudFront distribution: remove it there, then Check now), certificate_, tenant_missing, tenant_disabled, degraded_72h, organization_erased. - `records` (array of object, required): The ownership TXT and the CNAME, expected and seen; a caa record only when CAA is published. - `records[].record` (string, required): Which record this is: `ownership`, the TXT record that proves the hostname is yours; `cname`, the CNAME that points it at Parasend; `caa`, your CAA record, listed only when you publish one. One of: `ownership`, `cname`, `caa`. - `records[].type` (string, required): The DNS record type: `TXT` for `ownership`, `CNAME` for `cname`, `CAA` for `caa`. One of: `TXT`, `CNAME`, `CAA`. - `records[].name` (string, required): The DNS name to publish at (for caa, the name whose CAA governs the hostname). - `records[].value` (string, required): What it should hold. - `records[].seen` (array of string, required): What the last check found there. - `records[].ok` (boolean, required): Whether the last check found the record as it should be. - `records[].via` (string, optional): Ownership proved by the parent domain, which your organisation alone has verified for e-mail. One of: `parent_domain`. - `caa_hint` (boolean, required): CAA is published and names no Amazon issuer, so no certificate can be issued. - `certificate` (object · nullable, required): The hostname's TLS certificate, by its `status`; null until one has been requested. - `certificate.status` (string, optional): pending-validation, issued, failed, validation-timed-out, revoked, expired or inactive. - `used_by` (object, required): How many brand profiles in use name this domain in their current version (`brand_profiles`). - `used_by.brand_profiles` (integer, required): Profiles in use whose current version names it (a count). - `verified_at` (string · date-time · nullable, required): When both DNS records were first seen together; the hostname is your organisation's from then on. - `active_since` (string · date-time · nullable, required): When the domain last became `active`, in ISO 8601 (UTC); null if it never has. - `degraded_since` (string · date-time · nullable, required): When the domain, while active, began failing its checks, in ISO 8601 (UTC); null when it is not degraded. After 72 hours degraded it becomes `failed`. - `failed_at` (string · date-time · nullable, required): When the domain became `failed`, in ISO 8601 (UTC); null otherwise. - `last_checked_at` (string · date-time · nullable, required): When its DNS was last checked, in ISO 8601 (UTC); null before the first check. Parasend checks every 15 minutes, and when you call `POST /recipient_domains/{id}/check`. - `created_at` (string · date-time, required): When the domain was added, in ISO 8601 (UTC). - `removed_at` (string · date-time · nullable, required): When the domain was removed with `DELETE /recipient_domains/{id}`, in ISO 8601 (UTC); null while it is on your list. --- # Prices Source: https://parasend.com/docs/api/billing ## Endpoints - [Get the price list](https://parasend.com/docs/api/billing/prices.md): `GET /v1/billing/prices` ## The PriceList object - `object` (string, required): Always `price_list`. - `currency` (string, required): The currency of the prices, from the `currency` query parameter: `ISK` (the default) or `EUR`. Amounts are integers in its minor unit (whole krónur for ISK, cents for EUR). One of: `ISK`, `EUR`. - `on` (string · date, required): The date the prices apply on, `YYYY-MM-DD`, from the `on` query parameter; by default today (UTC). - `prices_exclude_vat` (boolean, required): Always true: every price excludes VAT. - `vat_percent` (number, required): VAT added on Icelandic invoices (24). - `plans` (array of object, required): The self-serve plans, pay as you go and the monthly plans: each one's monthly fee, the deliveries the fee includes, and the price of a delivery and of a document request. - `plans[].plan` (string, required): Door 1: payg. Door 2: p500, p1000, p5000. Door 3: enterprise (by contract). Replaces free / plan_1–3. One of: `payg`, `p500`, `p1000`, `p5000`, `enterprise`. - `plans[].door` (integer, required): One of: `1`, `2`. - `plans[].rails` (array of string, required): One of: `card`, `bank_claim`. - `plans[].monthly_fee` (integer · nullable, required): Minor units; null for pay as you go. - `plans[].included_deliveries` (integer · nullable, required) - `plans[].delivery_price` (integer · nullable, required): Pay as you go: each delivery; plans: each delivery over the included volume. - `plans[].request_price` (integer · nullable, required): A document request is priced as a delivery and counts towards plan volume. - `per_use` (array of object, required): Prices per use, the same on every plan: an eID verification (per verified party, once per delivery), an SMS in Iceland (each notice, reminder or code), and a passport or ID-card check (per verified recipient, once per delivery). - `per_use[].product` (string, required): The Payday product code. - `per_use[].unit_amount` (integer, required) - `sms_international` (array of object, required): SMS abroad, per destination country, in two price zones. A country that is not listed cannot receive SMS from Parasend. - `sms_international[].country` (string, required): ISO 3166-1 alpha-2. - `sms_international[].unit_amount` (integer, required) ```json { "object": "price_list", "currency": "ISK", "on": "2026-11-06", "prices_exclude_vat": true, "vat_percent": 24, "plans": [ { "plan": "payg", "door": 1, "rails": [ "card" ], "monthly_fee": null, "included_deliveries": null, "delivery_price": 311, "request_price": 311 }, { "plan": "p500", "door": 2, "rails": [ "card", "bank_claim" ], "monthly_fee": 143500, "included_deliveries": 500, "delivery_price": 287, "request_price": 287 }, { "plan": "p1000", "door": 2, "rails": [ "card", "bank_claim" ], "monthly_fee": 263000, "included_deliveries": 1000, "delivery_price": 263, "request_price": 263 }, { "plan": "p5000", "door": 2, "rails": [ "card", "bank_claim" ], "monthly_fee": 1200000, "included_deliveries": 5000, "delivery_price": 240, "request_price": 240 } ], "per_use": [ { "product": "PS-SECRET", "unit_amount": 39 }, { "product": "PS-QUAL", "unit_amount": 340 }, { "product": "PS-EID", "unit_amount": 29 }, { "product": "PS-SMS-IS", "unit_amount": 25 }, { "product": "PS-VIDEO", "unit_amount": 215 } ], "sms_international": [ { "country": "AD", "unit_amount": 50 }, { "country": "AG", "unit_amount": 50 }, { "country": "AI", "unit_amount": 25 }, { "country": "AL", "unit_amount": 50 }, { "country": "AO", "unit_amount": 25 }, { "country": "AT", "unit_amount": 50 }, { "country": "BG", "unit_amount": 50 }, { "country": "BH", "unit_amount": 25 }, { "country": "BN", "unit_amount": 25 }, { "country": "BO", "unit_amount": 50 }, { "country": "BW", "unit_amount": 50 }, { "country": "CH", "unit_amount": 25 }, { "country": "CK", "unit_amount": 50 }, { "country": "CV", "unit_amount": 50 }, { "country": "CY", "unit_amount": 25 }, { "country": "CZ", "unit_amount": 25 }, { "country": "DE", "unit_amount": 50 }, { "country": "DJ", "unit_amount": 50 }, { "country": "DK", "unit_amount": 25 }, { "country": "EE", "unit_amount": 25 }, { "country": "ER", "unit_amount": 50 }, { "country": "FI", "unit_amount": 50 }, { "country": "FO", "unit_amount": 25 }, { "country": "FR", "unit_amount": 25 }, { "country": "GE", "unit_amount": 50 }, { "country": "GF", "unit_amount": 50 }, { "country": "GG", "unit_amount": 25 }, { "country": "GI", "unit_amount": 25 }, { "country": "GL", "unit_amount": 25 }, { "country": "GP", "unit_amount": 50 }, { "country": "GR", "unit_amount": 25 }, { "country": "GY", "unit_amount": 50 }, { "country": "HK", "unit_amount": 25 }, { "country": "HR", "unit_amount": 50 }, { "country": "IM", "unit_amount": 25 }, { "country": "IT", "unit_amount": 25 }, { "country": "JE", "unit_amount": 25 }, { "country": "JM", "unit_amount": 50 }, { "country": "JP", "unit_amount": 25 }, { "country": "LI", "unit_amount": 25 }, { "country": "LT", "unit_amount": 25 }, { "country": "LU", "unit_amount": 25 }, { "country": "LV", "unit_amount": 25 }, { "country": "MD", "unit_amount": 50 }, { "country": "ME", "unit_amount": 50 }, { "country": "MK", "unit_amount": 25 }, { "country": "MO", "unit_amount": 25 }, { "country": "MQ", "unit_amount": 50 }, { "country": "MS", "unit_amount": 25 }, { "country": "MT", "unit_amount": 25 }, { "country": "MU", "unit_amount": 50 }, { "country": "MX", "unit_amount": 25 }, { "country": "NA", "unit_amount": 25 }, { "country": "NC", "unit_amount": 50 }, { "country": "NF", "unit_amount": 25 }, { "country": "NL", "unit_amount": 50 }, { "country": "NO", "unit_amount": 25 }, { "country": "NU", "unit_amount": 50 }, { "country": "PA", "unit_amount": 50 }, { "country": "PF", "unit_amount": 50 }, { "country": "PL", "unit_amount": 25 }, { "country": "PT", "unit_amount": 25 }, { "country": "PW", "unit_amount": 25 }, { "country": "RE", "unit_amount": 50 }, { "country": "RO", "unit_amount": 25 }, { "country": "SB", "unit_amount": 25 }, { "country": "SE", "unit_amount": 25 }, { "country": "SI", "unit_amount": 50 }, { "country": "SK", "unit_amount": 25 }, { "country": "SM", "unit_amount": 50 }, { "country": "SR", "unit_amount": 50 }, { "country": "ST", "unit_amount": 50 }, { "country": "TL", "unit_amount": 50 }, { "country": "TO", "unit_amount": 50 }, { "country": "TV", "unit_amount": 25 }, { "country": "UA", "unit_amount": 50 }, { "country": "VU", "unit_amount": 50 }, { "country": "WS", "unit_amount": 50 } ] } ``` --- # Create a delivery `POST https://api.parasend.com/v1/deliveries` Source: https://parasend.com/docs/api/deliveries/create Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Send an `Idempotency-Key` header: a retry with the same key returns the first answer. Creates a delivery and sends its notice. The recipient needs at least an `email` or a `phone`; the address actually used is printed on the Delivery Certificate. Upload the files first with POST /files and pass their `fil_` IDs in `files`. To let only one person open it, set `recipient.conditional_authentication` with their `personal_code` (eID, `verification_level: high`) or `id_document_number` (ID document check, `substantial`). Send an `Idempotency-Key`: a retry with the same key returns the first response and never sends twice. ## Body (application/json) - `recipient` (object, required): Who receives a delivery. At least one of `email` and `phone`; `phone` is required at `verification_level: medium`, where the SMS code goes. With `conditional_authentication` true, only the named person can open it: `personal_code` is required at `high` (only an eID with that kennitala passes) and `id_document_number` at `substantial` (only that identity document passes). Without it, the recipient still verifies at the level you set, and their verified identity is recorded and returned to you. A mismatch is the event `delivery.auth_failed`, with the reason `personal_code_mismatch` or `id_document_mismatch`. - `recipient.verification_level` (string, required): How the recipient must identify themselves before they see the documents: `high`, eID; `substantial`, a passport or ID-card check with a selfie; `medium`, a one-time code sent by SMS when they open the link; `low`, the link alone. A stronger way is always accepted. One of: `high`, `substantial`, `medium`, `low`. - `recipient.name` (string · nullable, optional): The recipient's name as you gave it, trimmed: up to 255 characters, no control characters. It is sealed into the evidence as given and never checked; null when none was given. `verified_name` holds the name from their eID or passport. - `recipient.email` (string · email · nullable, optional): The recipient's e-mail address, where e-mail notices and reminders go; null when not given. At least one of `email` and `phone` is required. - `recipient.phone` (string · nullable, optional): E.164 - `recipient.address` (object · nullable, optional) - `recipient.address.line1` (string, optional): The first address line, such as the street and number. Required. - `recipient.address.line2` (string · nullable, optional): A second address line, or null. - `recipient.address.postal_code` (string, optional): The postal code. Required. - `recipient.address.city` (string, optional): The town or city. Required. - `recipient.address.country` (string, optional): ISO 3166-1 alpha-2 - `recipient.address.source` (string · nullable, optional): Where the address came from, as you state it: `thjodskra`, Iceland's national registry (Þjóðskrá); `customer_csv`, your own records, imported from a file; `manual`, entered by hand. Default `manual`. One of: `thjodskra`, `customer_csv`, `manual`. - `recipient.conditional_authentication` (boolean, optional): When true, only the person you name can open the delivery: at `high` their eID must carry `personal_code`, and at `substantial` their passport or ID card must carry `id_document_number`. Default false: the recipient still identifies themselves at the level, and you get back the identity they used. Default: `false`. - `recipient.personal_code` (string · nullable, optional): The recipient's Icelandic personal code (kennitala). Required when `conditional_authentication` is true at `high`. - `recipient.id_document_number` (string · nullable, optional): The number of the recipient's passport or ID card. Required when `conditional_authentication` is true at `substantial`. - `files` (array of string, required): The files to send: the `fil_` IDs that Upload a file returned. - `options` (object, optional) - `options.notify_channels` (array of string, optional): How the recipient is told — the invitation and every reminder go to each channel listed; each needs its contact (recipient.email, recipient.phone). Default — e-mail when there is an address, else SMS. Told on both, a failure on one channel bounces the delivery only once the other cannot arrive either. One of: `email`, `sms`. - `options.auth_methods` (array of string, optional): Which ways of identifying the recipient to offer within `recipient.verification_level`, in your order of preference. The recipient may always use a way at least as strong as the weakest one you list. Left out: every way the level allows (an SMS code only when the recipient has a phone). One of: `audkenni`, `smart_id`, `sms_otp`, `passkey`, `videoid`, `password`, `link_only`. - `options.reminder_policy` (string · nullable, optional): The follow-up policy's ID (`rpol_…`): the reminders this delivery sends while it waits. - `options.brand_profile_id` (string · nullable, optional): The brand profile's ID (`bp_…`). One set on the delivery wins over the batch's, and the batch's over the account's default. - `options.expires_in_days` (integer, optional): How many days the recipient has to confirm receipt before the delivery expires: a whole number from 1 to 365. Default 30. For a delivery in a batch, the days count from when the batch is sent out. - `options.sender_display` (string, optional): What the recipient and the evidence show as the sender. `legal_person` (your organisation's default) shows the organisation, with an opaque reference to the person who sent it; that person's name, personal code and e-mail are not shown. `individual` names the person who sent it as well. Your choice per delivery or template; a personal account is always `individual`. One of: `legal_person`, `individual`. - `options.allow_parasend_links` (boolean, optional): Send even though your brand profile's link domain is not working, with Parasend's own links (warning `link_domain_unavailable`). Without it such a delivery is refused with 409 `link_domain_unavailable`. Default: `false`. - `options.notice_wording` (object, optional): Which saved wording the first notice uses, per channel: a wording ID, null for Parasend's own, or left out for your organisation's default. - `options.notice_wording.email` (string · nullable, optional) - `options.notice_wording.sms` (string · nullable, optional) - `metadata` (object, optional): Your own key–value pairs, kept with the object and returned as you sent them. Never put personal data here. - `title` (string, optional): The subject the recipient sees in the notice and on their page. - `message` (string, optional): A plain-text note shown before sign-in. Line breaks are kept. It is not sealed into the evidence. - `language` (string, optional): The language of the notice and of the recipient page. One of: `is`, `en`. Default: `"is"`. ## Example: Send a PDF by e-mail ```bash curl -X POST "https://api.parasend.com/v1/deliveries" \ -H "Authorization: Bearer $PARASEND_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "recipient": { "name": "Anna Björk Jónsdóttir", "email": "anna.bjork@example.com", "verification_level": "high" }, "files": [ "fil_01M6R27SZK99V6N6WG4GK15T6R" ], "title": "Final notice: invoice 2026-114", "language": "en", "metadata": { "case_ref": "IN-2026-114" } }' ``` ```js const res = await fetch('https://api.parasend.com/v1/deliveries', { method: 'POST', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, 'Idempotency-Key': crypto.randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ recipient: { name: 'Anna Björk Jónsdóttir', email: 'anna.bjork@example.com', verification_level: 'high', }, files: ['fil_01M6R27SZK99V6N6WG4GK15T6R'], title: 'Final notice: invoice 2026-114', language: 'en', metadata: { case_ref: 'IN-2026-114', }, }), }); const data = await res.json(); ``` ## Example: Only this kennitala may open it ```bash curl -X POST "https://api.parasend.com/v1/deliveries" \ -H "Authorization: Bearer $PARASEND_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "recipient": { "name": "Gervimaður Eyjaálfa", "email": "gervimadur@example.com", "verification_level": "high", "conditional_authentication": true, "personal_code": "0101304339" }, "files": [ "fil_01M6R27SZK99V6N6WG4GK15T6R" ], "title": "Court summons", "language": "en" }' ``` ```js const res = await fetch('https://api.parasend.com/v1/deliveries', { method: 'POST', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, 'Idempotency-Key': crypto.randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ recipient: { name: 'Gervimaður Eyjaálfa', email: 'gervimadur@example.com', verification_level: 'high', conditional_authentication: true, personal_code: '0101304339', }, files: ['fil_01M6R27SZK99V6N6WG4GK15T6R'], title: 'Court summons', language: 'en', }), }); const data = await res.json(); ``` ## Example: E-mail and SMS code ```bash curl -X POST "https://api.parasend.com/v1/deliveries" \ -H "Authorization: Bearer $PARASEND_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "recipient": { "name": "Anna Björk Jónsdóttir", "email": "anna.bjork@example.com", "phone": "+3540000000", "verification_level": "medium" }, "files": [ "fil_01M6R27SZK99V6N6WG4GK15T6R" ], "title": "Terms of your loan are changing", "language": "en", "options": { "notify_channels": [ "email", "sms" ], "expires_in_days": 14 } }' ``` ```js const res = await fetch('https://api.parasend.com/v1/deliveries', { method: 'POST', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, 'Idempotency-Key': crypto.randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ recipient: { name: 'Anna Björk Jónsdóttir', email: 'anna.bjork@example.com', phone: '+3540000000', verification_level: 'medium', }, files: ['fil_01M6R27SZK99V6N6WG4GK15T6R'], title: 'Terms of your loan are changing', language: 'en', options: { notify_channels: ['email', 'sms'], expires_in_days: 14, }, }), }); const data = await res.json(); ``` ## Response 201 Created. Includes the signed `delivery_url` for the recipient. ```json { "id": "dlv_01M6R27T05512SCV3852TPM2XY", "object": "delivery", "kind": "documents", "direction": "outbound", "content_type": "files", "status": "created", "last_activity": null, "title": "Final notice: invoice 2026-114", "message": null, "language": "en", "certificate_language": "is", "recipient": { "name": "Anna Björk Jónsdóttir", "verified_name": null, "verified_method": null, "verification_photos": [], "email": "anna.bjork@example.com", "phone": null, "address": null, "verification_level": "high", "conditional_authentication": false, "personal_code": null, "id_document_number": null }, "files": [ { "file_id": "fil_01M6R27SZK99V6N6WG4GK15T6R", "path": "test-notice.pdf", "primary": true } ], "batch_id": null, "delivery_url": "https://parasend.com/d/dlv_01M6R27T05512SCV3852TPM2XY.5HUZcfzvQDAcjpVG.pUZ-uQ3Zeu8y3UB-1O0rXywI30QlFNxQ", "short_url": "https://parasend.com/d/RM1RyXl0", "evidence_policy": "urn:parasend:policy:evidence:standard:1", "sender_display": "legal_person", "operator_reference": "OP-7K3Q-M2", "recipient_ref": "rcp_01M6R27T05ZNSRNMAW2CA8MTF6", "private": false, "options": { "auth_methods": [ "audkenni", "smart_id", "passkey" ], "reminder_policy": null, "brand_profile_id": null, "expires_in_days": 30, "sender_display": "legal_person", "notify_channels": [ "email" ] }, "legal_hold": false, "deletion_scheduled_at": null, "livemode": false, "created": "2026-11-06T09:15:05.604Z", "expires_at": "2026-12-06T09:15:05.603Z", "metadata": { "case_ref": "IN-2026-114" }, "origin_request": null, "reminder_waiting": null, "email_waiting": null, "follow_up": { "policy_id": null, "policy_name": null, "policy_source": "none", "state": "done", "reminders_sent": 0, "last_step": null, "next_step": null, "paused": false } } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # List deliveries `GET https://api.parasend.com/v1/deliveries` Source: https://parasend.com/docs/api/deliveries/list Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Lists your deliveries a page at a time (`limit`, `cursor`). Each row carries its `kind`. `status` is where a delivery is in its life; activity such as reminders and opens is in `last_activity`, never in `status`. ## Query parameters - `q` (string, optional): Free text, 2–200 characters, matched case-insensitively as part of the delivery title, the recipient's name or the recipient's e-mail, or exactly against a delivery id. Never matches personal codes or phone numbers — use `pin` for a personal code. `%` and `_` are ordinary characters. - `pin` (string, optional): The recipient's personal code (e.g. kennitala), exact match only; spaces and hyphens are ignored (120389-4569 = 1203894569). A part of a code matches nothing. - `recipient_email` (string · email, optional): The recipient's e-mail address, exact match, case-insensitive. - `created_before` (string · date-time, optional): Only deliveries created strictly before this time. With `created_after`, a date range. - `sort` (string, optional): `created_desc` (default) newest first; `created_asc` oldest first; `title_asc` / `title_desc` by title, case-insensitive; `recipient_asc` / `recipient_desc` by the recipient's name, else e-mail, else phone, case-insensitive; `status_asc` / `status_desc` by status in lifecycle order — on its way (created, paper_pending_approval, notified, paper_dispatched, opened, authenticated, auth_failed), then received (confirmed, delivered, evidence_sealed), then ended without delivery (expired, revoked, bounced, consignment_failed). Within one title, recipient or status the newest comes first. A cursor is only valid with the sort it was issued under (otherwise invalid_request / cursor_invalid). One of: `created_desc`, `created_asc`, `title_asc`, `title_desc`, `status_asc`, `status_desc`, `recipient_asc`, `recipient_desc`. Default: `"created_desc"`. ## Example: List deliveries ```bash curl "https://api.parasend.com/v1/deliveries" \ -H "Authorization: Bearer $PARASEND_KEY" ``` ```js const res = await fetch('https://api.parasend.com/v1/deliveries', { method: 'GET', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, }, }); const data = await res.json(); ``` ## Response 200 Paginated list. ```json { "data": [ { "id": "dlv_01M6R27T1MRPH7P4ZSR0WKTBQD", "object": "delivery", "kind": "documents", "direction": "outbound", "content_type": "files", "status": "notified", "last_activity": { "at": "2026-11-06T09:15:05.675Z", "type": "notified" }, "title": "Terms of your loan are changing", "message": null, "language": "en", "certificate_language": "is", "recipient": { "name": "Anna Björk Jónsdóttir", "verified_name": null, "verified_method": null, "verification_photos": [], "email": "anna.bjork@example.com", "phone": "+3540000000", "address": null, "verification_level": "medium", "conditional_authentication": false, "personal_code": null, "id_document_number": null }, "files": [ { "file_id": "fil_01M6R27SZK99V6N6WG4GK15T6R", "path": "test-notice.pdf", "primary": true } ], "batch_id": null, "delivery_url": null, "short_url": null, "evidence_policy": "urn:parasend:policy:evidence:standard:1", "sender_display": "legal_person", "operator_reference": "OP-7K3Q-M2", "recipient_ref": "rcp_01M6R27T1MZJTGQZYDP9MF2ZGT", "private": false, "options": { "auth_methods": [ "sms_otp", "audkenni", "smart_id", "passkey", "videoid" ], "reminder_policy": null, "brand_profile_id": null, "expires_in_days": 14, "sender_display": "legal_person", "notify_channels": [ "email", "sms" ] }, "legal_hold": false, "deletion_scheduled_at": null, "livemode": false, "created": "2026-11-06T09:15:05.651Z", "expires_at": "2026-11-20T09:15:05.651Z", "metadata": {}, "origin_request": null, "reminder_waiting": null, "email_waiting": null, "follow_up": { "policy_id": null, "policy_name": null, "policy_source": "none", "state": "done", "reminders_sent": 0, "last_step": null, "next_step": null, "paused": false } }, { "id": "dlv_01M6R27T13NKWVJRJBTHZDP6Y1", "object": "delivery", "kind": "documents", "direction": "outbound", "content_type": "files", "status": "notified", "last_activity": { "at": "2026-11-06T09:15:05.671Z", "type": "notified" }, "title": "Court summons", "message": null, "language": "en", "certificate_language": "is", "recipient": { "name": "Gervimaður Eyjaálfa", "verified_name": null, "verified_method": null, "verification_photos": [], "email": "gervimadur@example.com", "phone": null, "address": null, "verification_level": "high", "conditional_authentication": true, "personal_code": "0101304339", "id_document_number": null }, "files": [ { "file_id": "fil_01M6R27SZK99V6N6WG4GK15T6R", "path": "test-notice.pdf", "primary": true } ], "batch_id": null, "delivery_url": null, "short_url": null, "evidence_policy": "urn:parasend:policy:evidence:standard:1", "sender_display": "legal_person", "operator_reference": "OP-7K3Q-M2", "recipient_ref": "rcp_01M6R27T1389NCD4SG4VXJKZAJ", "private": false, "options": { "auth_methods": [ "audkenni", "smart_id", "passkey" ], "reminder_policy": null, "brand_profile_id": null, "expires_in_days": 30, "sender_display": "legal_person", "notify_channels": [ "email" ] }, "legal_hold": false, "deletion_scheduled_at": null, "livemode": false, "created": "2026-11-06T09:15:05.634Z", "expires_at": "2026-12-06T09:15:05.634Z", "metadata": {}, "origin_request": null, "reminder_waiting": null, "email_waiting": null, "follow_up": { "policy_id": null, "policy_name": null, "policy_source": "none", "state": "done", "reminders_sent": 0, "last_step": null, "next_step": null, "paused": false } }, { "id": "dlv_01M6R27T05512SCV3852TPM2XY", "object": "delivery", "kind": "documents", "direction": "outbound", "content_type": "files", "status": "notified", "last_activity": { "at": "2026-11-06T09:15:05.667Z", "type": "notified" }, "title": "Final notice: invoice 2026-114", "message": null, "language": "en", "certificate_language": "is", "recipient": { "name": "Anna Björk Jónsdóttir", "verified_name": null, "verified_method": null, "verification_photos": [], "email": "anna.bjork@example.com", "phone": null, "address": null, "verification_level": "high", "conditional_authentication": false, "personal_code": null, "id_document_number": null }, "files": [ { "file_id": "fil_01M6R27SZK99V6N6WG4GK15T6R", "path": "test-notice.pdf", "primary": true } ], "batch_id": null, "delivery_url": null, "short_url": null, "evidence_policy": "urn:parasend:policy:evidence:standard:1", "sender_display": "legal_person", "operator_reference": "OP-7K3Q-M2", "recipient_ref": "rcp_01M6R27T05ZNSRNMAW2CA8MTF6", "private": false, "options": { "auth_methods": [ "audkenni", "smart_id", "passkey" ], "reminder_policy": null, "brand_profile_id": null, "expires_in_days": 30, "sender_display": "legal_person", "notify_channels": [ "email" ] }, "legal_hold": false, "deletion_scheduled_at": null, "livemode": false, "created": "2026-11-06T09:15:05.604Z", "expires_at": "2026-12-06T09:15:05.603Z", "metadata": { "case_ref": "IN-2026-114" }, "origin_request": null, "reminder_waiting": null, "email_waiting": null, "follow_up": { "policy_id": null, "policy_name": null, "policy_source": "none", "state": "done", "reminders_sent": 0, "last_step": null, "next_step": null, "paused": false } } ], "has_more": false, "next_cursor": null } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Retrieve a delivery `GET https://api.parasend.com/v1/deliveries/{id}` Source: https://parasend.com/docs/api/deliveries/retrieve Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Retrieves a delivery. A delivery scheduled for deletion still reads normally, with `deletion_scheduled_at` set, until it is removed; after that it is 404 `delivery_not_found`. ## Path parameters - `id` (string, required): The delivery's ID, starting `dlv_`. ## Example: Retrieve a delivery ```bash curl "https://api.parasend.com/v1/deliveries/dlv_01M6R27T05512SCV3852TPM2XY" \ -H "Authorization: Bearer $PARASEND_KEY" ``` ```js const res = await fetch('https://api.parasend.com/v1/deliveries/dlv_01M6R27T05512SCV3852TPM2XY', { method: 'GET', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, }, }); const data = await res.json(); ``` ## Response 200 The delivery. ```json { "id": "dlv_01M6R27T05512SCV3852TPM2XY", "object": "delivery", "kind": "documents", "direction": "outbound", "content_type": "files", "status": "notified", "last_activity": { "at": "2026-11-06T09:15:05.667Z", "type": "notified" }, "title": "Final notice: invoice 2026-114", "message": null, "language": "en", "certificate_language": "is", "recipient": { "name": "Anna Björk Jónsdóttir", "verified_name": null, "verified_method": null, "verification_photos": [], "email": "anna.bjork@example.com", "phone": null, "address": null, "verification_level": "high", "conditional_authentication": false, "personal_code": null, "id_document_number": null }, "files": [ { "file_id": "fil_01M6R27SZK99V6N6WG4GK15T6R", "path": "test-notice.pdf", "primary": true } ], "batch_id": null, "delivery_url": null, "short_url": "https://parasend.com/d/RM1RyXl0", "evidence_policy": "urn:parasend:policy:evidence:standard:1", "sender_display": "legal_person", "operator_reference": "OP-7K3Q-M2", "recipient_ref": "rcp_01M6R27T05ZNSRNMAW2CA8MTF6", "private": false, "options": { "auth_methods": [ "audkenni", "smart_id", "passkey" ], "reminder_policy": null, "brand_profile_id": null, "expires_in_days": 30, "sender_display": "legal_person", "notify_channels": [ "email" ] }, "legal_hold": false, "deletion_scheduled_at": null, "livemode": false, "created": "2026-11-06T09:15:05.604Z", "expires_at": "2026-12-06T09:15:05.603Z", "metadata": { "case_ref": "IN-2026-114" }, "origin_request": null, "reminder_waiting": null, "email_waiting": null, "follow_up": { "policy_id": null, "policy_name": null, "policy_source": "none", "state": "done", "reminders_sent": 0, "last_step": null, "next_step": null, "paused": false } } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Check a contact before sending `POST https://api.parasend.com/v1/deliveries/contact_check` Source: https://parasend.com/docs/api/deliveries/check-contact Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Send an `Idempotency-Key` header: a retry with the same key returns the first answer. Whether the last message from this organisation (in this mode) to the e-mail address or phone number failed — an e-mail that bounced, an SMS that never reported delivery to the phone (after 15 minutes) or failed, or a contact on the block list. Null warning for a contact never tried, or whose last attempt succeeded. A POST so the contact stays out of URLs and logs; the answer names no delivery or recipient. ## Body (application/json) - `email` (string, optional) - `phone` (string, optional): E.164 ## Example: Check an e-mail address ```bash curl -X POST "https://api.parasend.com/v1/deliveries/contact_check" \ -H "Authorization: Bearer $PARASEND_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "email": "anna.bjork@example.com" }' ``` ```js const res = await fetch('https://api.parasend.com/v1/deliveries/contact_check', { method: 'POST', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, 'Idempotency-Key': crypto.randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ email: 'anna.bjork@example.com', }), }); const data = await res.json(); ``` ## Response 200 One answer per contact given; null for a contact not given (or a phone not in E.164). ```json { "object": "contact_check", "email": { "warning": null }, "phone": null } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Send a reminder `POST https://api.parasend.com/v1/deliveries/{id}/remind` Source: https://parasend.com/docs/api/deliveries/remind Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Send an `Idempotency-Key` header: a retry with the same key returns the first answer. Capped at 10 per recipient per day. The response includes the remaining quota so clients can show "1 of 10 used today". ## Path parameters - `id` (string, required): The delivery's ID, starting `dlv_`. ## Example: Send a reminder ```bash curl -X POST "https://api.parasend.com/v1/deliveries/dlv_01M6R27T05512SCV3852TPM2XY/remind" \ -H "Authorization: Bearer $PARASEND_KEY" \ -H "Idempotency-Key: $(uuidgen)" ``` ```js const res = await fetch('https://api.parasend.com/v1/deliveries/dlv_01M6R27T05512SCV3852TPM2XY/remind', { method: 'POST', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, 'Idempotency-Key': crypto.randomUUID(), }, }); const data = await res.json(); ``` ## Response 200 Reminder dispatched. ```json { "delivery_id": "dlv_01M6R27T05512SCV3852TPM2XY", "reminded_at": "2026-11-06T09:15:05.736Z", "reminders_used_today": 1, "reminders_remaining_today": 9 } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Revoke a delivery `POST https://api.parasend.com/v1/deliveries/{id}/revoke` Source: https://parasend.com/docs/api/deliveries/revoke Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Send an `Idempotency-Key` header: a retry with the same key returns the first answer. Blocked after `confirmed` (409 `delivery_already_confirmed`). The sender remedy post-confirmation is download-access expiry, not retraction. ## Path parameters - `id` (string, required): The delivery's ID, starting `dlv_`. ## Example: Revoke a delivery ```bash curl -X POST "https://api.parasend.com/v1/deliveries/dlv_01M6R27T05512SCV3852TPM2XY/revoke" \ -H "Authorization: Bearer $PARASEND_KEY" \ -H "Idempotency-Key: $(uuidgen)" ``` ```js const res = await fetch('https://api.parasend.com/v1/deliveries/dlv_01M6R27T05512SCV3852TPM2XY/revoke', { method: 'POST', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, 'Idempotency-Key': crypto.randomUUID(), }, }); const data = await res.json(); ``` ## Response 200 Revoked. ```json { "id": "dlv_01M6R27T05512SCV3852TPM2XY", "object": "delivery", "kind": "documents", "direction": "outbound", "content_type": "files", "status": "revoked", "last_activity": { "at": "2026-11-06T09:15:05.969Z", "type": "revoked" }, "title": "Final notice: invoice 2026-114", "message": null, "language": "en", "certificate_language": "is", "recipient": { "name": "Anna Björk Jónsdóttir", "verified_name": null, "verified_method": null, "verification_photos": [], "email": "anna.bjork@example.com", "phone": null, "address": { "city": "Kópavogur", "line1": "Akralind 3", "line2": null, "source": "manual", "country": "IS", "postal_code": "201" }, "verification_level": "high", "conditional_authentication": false, "personal_code": null, "id_document_number": null }, "files": [ { "file_id": "fil_01M6R27SZK99V6N6WG4GK15T6R", "path": "test-notice.pdf", "primary": true } ], "batch_id": null, "delivery_url": null, "short_url": null, "evidence_policy": "urn:parasend:policy:evidence:standard:1", "sender_display": "legal_person", "operator_reference": "OP-7K3Q-M2", "recipient_ref": "rcp_01M6R27T05ZNSRNMAW2CA8MTF6", "private": false, "options": { "auth_methods": [ "audkenni", "smart_id", "passkey" ], "reminder_policy": null, "brand_profile_id": null, "expires_in_days": 30, "sender_display": "legal_person", "notify_channels": [ "email" ] }, "legal_hold": false, "deletion_scheduled_at": null, "livemode": false, "created": "2026-11-06T09:15:05.604Z", "expires_at": "2026-12-06T09:15:05.603Z", "metadata": { "case_ref": "IN-2026-114" }, "origin_request": null, "reminder_waiting": null, "email_waiting": null, "follow_up": { "policy_id": null, "policy_name": null, "policy_source": "none", "state": "done", "reminders_sent": 0, "last_step": null, "next_step": null, "paused": false } } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Delete a delivery `DELETE https://api.parasend.com/v1/deliveries/{id}` Source: https://parasend.com/docs/api/deliveries/delete Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Schedules permanent removal of the delivery's documents, the recipient's contact details and the other data you can change, backups included. The sealed evidence record is not deleted: Parasend keeps it for the retention period from the delivery's last event, and the parties entitled to it can still obtain it. A delivery under legal hold (`legal_hold: true`) answers 409 `delete_blocked_legal_hold`, with the case reference in `error.detail`. ## Path parameters - `id` (string, required): The delivery's ID, starting `dlv_`. ## Example: Delete a delivery ```bash curl -X DELETE "https://api.parasend.com/v1/deliveries/dlv_01M6R27T05512SCV3852TPM2XY" \ -H "Authorization: Bearer $PARASEND_KEY" ``` ```js const res = await fetch('https://api.parasend.com/v1/deliveries/dlv_01M6R27T05512SCV3852TPM2XY', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, }, }); const data = await res.json(); ``` ## Response 202 Deletion scheduled. ```json { "delivery_id": "dlv_01M6R27T05512SCV3852TPM2XY", "delete_by": "2026-12-06T09:15:05.999Z", "evidence_retained_until": "2033-11-06T09:15:05.969Z" } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # List a delivery's events `GET https://api.parasend.com/v1/deliveries/{id}/events` Source: https://parasend.com/docs/api/deliveries/events Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Every event of the delivery: the same events your webhooks get. ## Path parameters - `id` (string, required): The delivery's ID, starting `dlv_`. ## Query parameters - `limit` (integer, optional): Default: `25`. - `cursor` (string, optional) ## Example: List a delivery's events ```bash curl "https://api.parasend.com/v1/deliveries/dlv_01M6R27T05512SCV3852TPM2XY/events" \ -H "Authorization: Bearer $PARASEND_KEY" ``` ```js const res = await fetch('https://api.parasend.com/v1/deliveries/dlv_01M6R27T05512SCV3852TPM2XY/events', { method: 'GET', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, }, }); const data = await res.json(); ``` ## Response 200 Paginated events. ```json { "data": [ { "id": "evt_01M6R27T08JTMBJ4B6GQP90838", "object": "event", "type": "delivery.created", "created": "2026-11-06T09:15:05.604Z", "resource": { "type": "delivery", "id": "dlv_01M6R27T05512SCV3852TPM2XY" }, "batch_id": null, "reason": null, "data": null }, { "id": "evt_01M6R27T23ZMZPJHEANJAPNSZ7", "object": "event", "type": "delivery.notified", "created": "2026-11-06T09:15:05.666Z", "resource": { "type": "delivery", "id": "dlv_01M6R27T05512SCV3852TPM2XY" }, "batch_id": null, "reason": null, "data": { "channel": "email" } } ], "has_more": false, "next_cursor": null } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # List a delivery's e-mails and SMS `GET https://api.parasend.com/v1/deliveries/{id}/notifications` Source: https://parasend.com/docs/api/deliveries/notifications Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Every invitation and reminder sent to the recipient, oldest first, with what the provider reported back. A send that was refused, or could not be sent after retries, is listed with `refused: true`. ## Path parameters - `id` (string, required): The delivery's ID. ## Example: List a delivery's e-mails and SMS ```bash curl "https://api.parasend.com/v1/deliveries/dlv_01M6R27T05512SCV3852TPM2XY/notifications" \ -H "Authorization: Bearer $PARASEND_KEY" ``` ```js const res = await fetch('https://api.parasend.com/v1/deliveries/dlv_01M6R27T05512SCV3852TPM2XY/notifications', { method: 'GET', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, }, }); const data = await res.json(); ``` ## Response 200 The notifications. ```json { "object": "list", "data": [ { "object": "notification", "id": "nts_01M6R27T238K4DF2KXZ9KS7F7X", "kind": "invitation", "channel": "email", "sent_to": "anna.bjork@example.com", "sent_at": "2026-11-06T09:15:05.666Z", "event_id": "evt_01M6R27T23ZMZPJHEANJAPNSZ7", "refused": false, "report": null, "sent_from": "parasend" } ] } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # List a delivery's files `GET https://api.parasend.com/v1/deliveries/{id}/files` Source: https://parasend.com/docs/api/deliveries/files Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Name, path, bytes, sha256, primary flag per manifest entry. Available to any key with `deliveries.read`. ## Path parameters - `id` (string, required): The delivery's ID, starting `dlv_`. ## Example: List a delivery's files ```bash curl "https://api.parasend.com/v1/deliveries/dlv_01M6R27T05512SCV3852TPM2XY/files" \ -H "Authorization: Bearer $PARASEND_KEY" ``` ```js const res = await fetch('https://api.parasend.com/v1/deliveries/dlv_01M6R27T05512SCV3852TPM2XY/files', { method: 'GET', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, }, }); const data = await res.json(); ``` ## Response 200 The delivery's files. ```json { "data": [ { "file_id": "fil_01M6R27SZK99V6N6WG4GK15T6R", "name": "test-notice.pdf", "path": "test-notice.pdf", "bytes": 691, "sha256": "1b0cff41d97c8dcf92b7de986d66d12fb94e68f6db19821aa4b263dcfb0b6225", "primary": true } ] } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Download a delivered file `GET https://api.parasend.com/v1/deliveries/{id}/files/{fileId}/content` Source: https://parasend.com/docs/api/deliveries/download-file Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Requires the `documents.read` permission. **Every call emits `document.previewed` into the delivery's audit trail, attributed to the acting key/user. No exceptions, no quiet mode.** This is the privacy-gated document preview behind the portal's reveal step. ## Path parameters - `id` (string, required): The delivery's ID, starting `dlv_`. - `fileId` (string, required) ## Example: Download a delivered file ```bash curl "https://api.parasend.com/v1/deliveries/dlv_01M6R27T05512SCV3852TPM2XY/files/fil_01M6R27SZK99V6N6WG4GK15T6R/content" \ -H "Authorization: Bearer $PARASEND_KEY" ``` ```js const res = await fetch('https://api.parasend.com/v1/deliveries/dlv_01M6R27T05512SCV3852TPM2XY/files/fil_01M6R27SZK99V6N6WG4GK15T6R/content', { method: 'GET', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, }, }); const data = await res.json(); ``` ## Response 200 File bytes. Content type: application/octet-stream. Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Set the recipient's postal address `PATCH https://api.parasend.com/v1/deliveries/{id}/address` Source: https://parasend.com/docs/api/deliveries/set-address Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). For the paper escalation channel. Rejected after paper dispatch or in terminal states (409 `address_patch_state_invalid`). ## Path parameters - `id` (string, required): The delivery's ID, starting `dlv_`. ## Body (application/json) - `line1` (string, optional): The first address line, such as the street and number. Required. - `line2` (string · nullable, optional): A second address line, or null. - `postal_code` (string, optional): The postal code. Required. - `city` (string, optional): The town or city. Required. - `country` (string, optional): ISO 3166-1 alpha-2 - `source` (string · nullable, optional): Where the address came from, as you state it: `thjodskra`, Iceland's national registry (Þjóðskrá); `customer_csv`, your own records, imported from a file; `manual`, entered by hand. Default `manual`. One of: `thjodskra`, `customer_csv`, `manual`. ## Example: Set the postal address ```bash curl -X PATCH "https://api.parasend.com/v1/deliveries/dlv_01M6R27T05512SCV3852TPM2XY/address" \ -H "Authorization: Bearer $PARASEND_KEY" \ -H "Content-Type: application/json" \ -d '{ "line1": "Akralind 3", "postal_code": "201", "city": "Kópavogur", "country": "IS" }' ``` ```js const res = await fetch('https://api.parasend.com/v1/deliveries/dlv_01M6R27T05512SCV3852TPM2XY/address', { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ line1: 'Akralind 3', postal_code: '201', city: 'Kópavogur', country: 'IS', }), }); const data = await res.json(); ``` ## Response 200 Updated delivery. ```json { "id": "dlv_01M6R27T05512SCV3852TPM2XY", "object": "delivery", "kind": "documents", "direction": "outbound", "content_type": "files", "status": "notified", "last_activity": { "at": "2026-11-06T09:15:05.667Z", "type": "notified" }, "title": "Final notice: invoice 2026-114", "message": null, "language": "en", "certificate_language": "is", "recipient": { "name": "Anna Björk Jónsdóttir", "verified_name": null, "verified_method": null, "verification_photos": [], "email": "anna.bjork@example.com", "phone": null, "address": { "city": "Kópavogur", "line1": "Akralind 3", "line2": null, "source": "manual", "country": "IS", "postal_code": "201" }, "verification_level": "high", "conditional_authentication": false, "personal_code": null, "id_document_number": null }, "files": [ { "file_id": "fil_01M6R27SZK99V6N6WG4GK15T6R", "path": "test-notice.pdf", "primary": true } ], "batch_id": null, "delivery_url": null, "short_url": null, "evidence_policy": "urn:parasend:policy:evidence:standard:1", "sender_display": "legal_person", "operator_reference": "OP-7K3Q-M2", "recipient_ref": "rcp_01M6R27T05ZNSRNMAW2CA8MTF6", "private": false, "options": { "auth_methods": [ "audkenni", "smart_id", "passkey" ], "reminder_policy": null, "brand_profile_id": null, "expires_in_days": 30, "sender_display": "legal_person", "notify_channels": [ "email" ] }, "legal_hold": false, "deletion_scheduled_at": null, "livemode": false, "created": "2026-11-06T09:15:05.604Z", "expires_at": "2026-12-06T09:15:05.603Z", "metadata": { "case_ref": "IN-2026-114" }, "origin_request": null, "reminder_waiting": null, "email_waiting": null, "follow_up": { "policy_id": null, "policy_name": null, "policy_source": "none", "state": "done", "reminders_sent": 0, "last_step": null, "next_step": null, "paused": false } } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Change a delivery's follow-up policy `POST https://api.parasend.com/v1/deliveries/{id}/policy_change` Source: https://parasend.com/docs/api/deliveries/change-policy Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Send an `Idempotency-Key` header: a retry with the same key returns the first answer. Changes one delivery's follow-up policy. With `dry_run: true`, shows what would change and changes nothing. ## Path parameters - `id` (string, required) ## Body (application/json) - `policy_id` (string · nullable, required): The new follow-up policy's ID (`rpol_…`), or null (or `rpol_none`) for none. - `scope` (string, optional): For a batch only. One of: `batch_followers`, `all_pending`. Default: `"batch_followers"`. - `start` (object, optional): Where notified deliveries start in the new policy: `{"from": "first"}` (the default), its first reminder, or `{"from": "step", "step": n}`, step n counting from 1. The first notice is never sent again; deliveries not yet notified follow the new policy from its start. - `start.from` (string, optional): One of: `first`, `step`. - `start.step` (integer, optional) - `extend_expiry_to` (string · date-time · nullable, optional): Move the affected deliveries' expiry to this time (ISO 8601), which must be in the future. An expiry is only ever moved later, never past a year from the delivery's creation, and never past the deletion of its documents. - `dry_run` (boolean, optional): When true, nothing changes: the response says what the change would do. Default false. Default: `false`. ## Example: Preview a policy change ```bash curl -X POST "https://api.parasend.com/v1/deliveries/dlv_01M6R27T05512SCV3852TPM2XY/policy_change" \ -H "Authorization: Bearer $PARASEND_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "policy_id": "rpol_01M6R27T5NK3830GCN9QPS9M95", "dry_run": true }' ``` ```js const res = await fetch('https://api.parasend.com/v1/deliveries/dlv_01M6R27T05512SCV3852TPM2XY/policy_change', { method: 'POST', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, 'Idempotency-Key': crypto.randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ policy_id: 'rpol_01M6R27T5NK3830GCN9QPS9M95', dry_run: true, }), }); const data = await res.json(); ``` ## Response 200 What changed — or, with dry_run, what would. ```json { "object": "policy_change", "dry_run": true, "policy_id": "rpol_01M6R27T5NK3830GCN9QPS9M95", "start_step": "first", "affected": 1, "unchanged": { "ended": 0, "own_policy": 0 }, "not_yet_notified": 0, "messages": { "email": 2, "sms": 0 }, "first_send_at": "2026-11-07T09:15:05.667Z", "estimate": { "currency": "ISK", "amount": 0, "vat": 0, "total_amount": 0 }, "after_expiry": 0, "held_by_gap": 1, "in_flight": 0, "schedule": [ { "step": 1, "channel": "email", "day_offset": 0, "due_at": "2026-11-07T09:15:05.667Z" }, { "step": 2, "channel": "email", "day_offset": 4, "due_at": "2026-11-11T09:15:05.667Z" }, { "step": 3, "channel": "sms", "day_offset": 11, "due_at": "2026-11-18T09:15:05.667Z" } ], "expires_at": null, "expiry_capped": 0 } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Get a delivery's cost `GET https://api.parasend.com/v1/deliveries/{id}/cost` Source: https://parasend.com/docs/api/deliveries/cost Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Every billable item the delivery has caused so far (the delivery itself, each SMS, each eID verification, each passport or ID-card check) with the unit price, the amount, VAT and `total_amount`, in your billing currency. Needs `deliveries.read`; a member or key that sees only its own deliveries gets 404 for anyone else's. **Prices.** Once a month is invoiced, an item has the price it was invoiced at (`invoice_id`). Before that, the price of the plan in effect now; for a month that has ended but is not yet invoiced, the plan on that month's last day, since a month is invoiced at the prices of its last day. A delivery that a plan's monthly fee covers is priced at the plan's price per included delivery, not at 0. Enterprise: the contract's price per committed delivery less its discount, and above the commitment the overage price less the discount. A discount comes off each unit price, so an item can differ by one króna from the invoice. Extras (eID, SMS, passport checks) are at the price list and never discounted, and monthly fees are never spread over deliveries. With no plan, Pay-as-you-go. See GET /billing/prices and parasend.com/pricing. **Free and taken back.** A delivery on a free transaction costs 0, every item included (`free: true`). A bounce after sending takes the delivery charge back; SMS already sent stay. **`final`** is false while the cost can still change: the delivery is still under way, its month is not invoiced, or a plan change this month would reprice it. It is true once the delivery has ended and every item is on an issued invoice. A recipient who opens a delivery confirmed by SMS code again gets a new code, a new item, and `final` is false again until that is invoiced. **Test mode** is never billed. A test delivery's cost is worked out from what it did, priced as if live at the plan in effect now, so you can build against real-looking numbers. `final` is true once a test delivery has ended. ## Path parameters - `id` (string, required): The delivery's ID, starting `dlv_`. ## Example: Get a delivery's cost ```bash curl "https://api.parasend.com/v1/deliveries/dlv_01M6R27T05512SCV3852TPM2XY/cost" \ -H "Authorization: Bearer $PARASEND_KEY" ``` ```js const res = await fetch('https://api.parasend.com/v1/deliveries/dlv_01M6R27T05512SCV3852TPM2XY/cost', { method: 'GET', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, }, }); const data = await res.json(); ``` ## Response 200 The cost so far. ```json { "object": "delivery_cost", "delivery_id": "dlv_01M6R27T05512SCV3852TPM2XY", "livemode": false, "currency": "ISK", "as_of": "2026-11-06T09:15:05.721Z", "final": false, "free": false, "plan": null, "items": [ { "kind": "delivery", "description": { "is": "Sending", "en": "Delivery" }, "country": null, "quantity": 1, "unit_amount": 311, "amount": 311, "vat": 75, "total_amount": 386, "invoice_id": null } ], "amount": 311, "vat": 75, "total_amount": 386, "invoices": [] } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Get the cost of several deliveries `GET https://api.parasend.com/v1/deliveries/costs` Source: https://parasend.com/docs/api/deliveries/costs Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). The same pricing as GET /deliveries/{id}/cost, for a group of deliveries: the items added up, the totals, and one subtotal per delivery. Give exactly one of `delivery_ids`, `batch_id` or `pin`. Needs `deliveries.read`. Typical use, a debt collector's invoice to a debtor: list the debtor's deliveries with GET /deliveries?pin=…, pick the ones for this claim, then ask for their cost here with `delivery_ids`. Or pass `pin` to cost everything sent to that person. Only deliveries you may see count: `pin` and `batch_id` leave the others out, and a `delivery_ids` entry you may not see is `delivery_not_found`. The totals cover the whole group; `limit` and `cursor` page the per-delivery subtotals, newest first. Query strings never reach Parasend's logs, so `pin` is safe here. **A period.** `from` and `to` keep the items that arose in it (when the notice, reminder or eID happened, not when the delivery was created), so a monthly run counts each cost once. Both dates are inclusive, whole days in UTC; either may be left out. A bounce in the period of a delivery charged earlier is a credit line with a negative quantity, so a past period never changes and the periods add up to the whole. Only deliveries with something in the period are listed. `final` is true once nothing more can arise in the period and every item in it is invoiced. Example: `?pin=0101304339&from=2026-10-01&to=2026-10-31`. ## Query parameters - `delivery_ids` (string, optional): Up to 100 delivery ids (dlv_…), comma-separated. A repeated id counts once. - `batch_id` (string, optional): Every delivery in the batch (bat_…). - `pin` (string, optional): Every delivery to the recipient with this personal code (e.g. kennitala), exact match only; spaces and hyphens are ignored — as `pin` on GET /deliveries. - `from` (string · date, optional): The first day of the period, inclusive, in UTC (Iceland's time). Items that arose before it are left out. - `to` (string · date, optional): The last day of the period, inclusive, in UTC (Iceland's time). Items that arose after it are left out. - `limit` (integer, optional): Default: `25`. - `cursor` (string, optional) ## Example: The cost of chosen deliveries ```bash curl "https://api.parasend.com/v1/deliveries/costs?delivery_ids=dlv_01M6R27T05512SCV3852TPM2XY" \ -H "Authorization: Bearer $PARASEND_KEY" ``` ```js const res = await fetch('https://api.parasend.com/v1/deliveries/costs?delivery_ids=dlv_01M6R27T05512SCV3852TPM2XY', { method: 'GET', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, }, }); const data = await res.json(); ``` ## Response 200 The group's cost so far. ```json { "object": "delivery_cost_summary", "livemode": false, "currency": "ISK", "as_of": "2026-11-06T09:15:05.729Z", "from": null, "to": null, "final": false, "delivery_count": 1, "items": [ { "kind": "delivery", "description": { "is": "Sending", "en": "Delivery" }, "country": null, "quantity": 1, "unit_amount": 311, "amount": 311, "vat": 75, "total_amount": 386 } ], "amount": 311, "vat": 75, "total_amount": 386, "invoices": [], "deliveries": [ { "delivery_id": "dlv_01M6R27T05512SCV3852TPM2XY", "status": "notified", "final": false, "free": false, "amount": 311, "vat": 75, "total_amount": 386 } ], "has_more": false, "next_cursor": null } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Get delivery counts `GET https://api.parasend.com/v1/deliveries/summary` Source: https://parasend.com/docs/api/deliveries/summary Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). The caller's organisation and mode, deliveries created in the last `days` days (default 30) — how many, how they ended, and the median time from sending to confirmation. Counts only; no personal data. ## Query parameters - `days` (integer, optional): Default: `30`. ## Example: Get delivery counts ```bash curl "https://api.parasend.com/v1/deliveries/summary" \ -H "Authorization: Bearer $PARASEND_KEY" ``` ```js const res = await fetch('https://api.parasend.com/v1/deliveries/summary', { method: 'GET', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, }, }); const data = await res.json(); ``` ## Response 200 The summary. ```json { "object": "delivery_summary", "livemode": false, "days": 30, "since": "2026-10-07T09:15:05.960Z", "total": 5, "in_progress": 5, "delivered": 0, "not_delivered": 0, "success_rate": null, "median_confirm_seconds": null, "any": true } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Upload a file `POST https://api.parasend.com/v1/files` Source: https://parasend.com/docs/api/files/upload Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Send an `Idempotency-Key` header: a retry with the same key returns the first answer. Uploads a file to send, as `multipart/form-data` with one `file` part, up to 32 MB. The file can be used in a delivery as soon as the upload completes and its SHA-256 is recorded. ## Body (multipart/form-data) - `file` (string · binary, optional) ## Example: Upload a PDF ```bash curl -X POST "https://api.parasend.com/v1/files" \ -H "Authorization: Bearer $PARASEND_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -F "file=@test-notice.pdf" ``` ```js import { readFile } from 'node:fs/promises'; const form = new FormData(); form.append('file', new Blob([await readFile('test-notice.pdf')], { type: 'application/pdf' }), 'test-notice.pdf'); const res = await fetch('https://api.parasend.com/v1/files', { method: 'POST', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, 'Idempotency-Key': crypto.randomUUID(), }, body: form, }); const data = await res.json(); ``` ## Response 201 Uploaded. ```json { "id": "fil_01M6R27SZK99V6N6WG4GK15T6R", "object": "file", "name": "test-notice.pdf", "bytes": 691, "sha256": "1b0cff41d97c8dcf92b7de986d66d12fb94e68f6db19821aa4b263dcfb0b6225", "created": "2026-11-06T09:15:05.587Z" } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Retrieve a file `GET https://api.parasend.com/v1/files/{id}` Source: https://parasend.com/docs/api/files/retrieve Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Retrieves a file's details: its name, size and SHA-256. ## Path parameters - `id` (string, required): The file's ID, starting `fil_`. ## Example: Retrieve a file ```bash curl "https://api.parasend.com/v1/files/fil_01M6R27SZK99V6N6WG4GK15T6R" \ -H "Authorization: Bearer $PARASEND_KEY" ``` ```js const res = await fetch('https://api.parasend.com/v1/files/fil_01M6R27SZK99V6N6WG4GK15T6R', { method: 'GET', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, }, }); const data = await res.json(); ``` ## Response 200 Metadata (name, size, sha256). ```json { "id": "fil_01M6R27SZK99V6N6WG4GK15T6R", "object": "file", "name": "test-notice.pdf", "bytes": 691, "sha256": "1b0cff41d97c8dcf92b7de986d66d12fb94e68f6db19821aa4b263dcfb0b6225", "created": "2026-11-06T09:15:05.587Z" } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Create a batch `POST https://api.parasend.com/v1/batches` Source: https://parasend.com/docs/api/batches/create Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Send an `Idempotency-Key` header: a retry with the same key returns the first answer. Shared files, policies and delivery defaults (including `additional_security_source: csv_personal_code` for the portal CSV flow). `sending_mode: on_finalize` (default) holds sending until send-out; `rolling` dispatches each delivery as added. ## Body (application/json) - `name` (string, optional): A name for the batch, for your own reference: 1–200 characters, without control characters; leading and trailing spaces are trimmed. Default: `Batch` and today's date (UTC), e.g. `Batch 2026-11-06`. - `files` (array of string, optional): Files (`fil_…`) to share with every delivery in the batch, at most 20. Each must be one of your organisation's files in the same mode (test or live), or the request fails with `file_reference_invalid`. - `sending_mode` (string, optional): `on_finalize` (the default): the batch starts as a `draft`, and its deliveries wait until you send it out with `POST /batches/{id}/finalize` or until `scheduled_at`. `rolling`: the batch starts `sending` and each delivery goes out as soon as it is added; it cannot have a `scheduled_at`. One of: `on_finalize`, `rolling`. Default: `"on_finalize"`. - `defaults` (object, optional): Delivery settings for every delivery in the batch. A delivery may set its own. - `defaults.options` (object, optional) - `defaults.options.notify_channels` (array of string, optional): How the recipient is told — the invitation and every reminder go to each channel listed; each needs its contact (recipient.email, recipient.phone). Default — e-mail when there is an address, else SMS. Told on both, a failure on one channel bounces the delivery only once the other cannot arrive either. One of: `email`, `sms`. - `defaults.options.auth_methods` (array of string, optional): Which ways of identifying the recipient to offer within `recipient.verification_level`, in your order of preference. The recipient may always use a way at least as strong as the weakest one you list. Left out: every way the level allows (an SMS code only when the recipient has a phone). One of: `audkenni`, `smart_id`, `sms_otp`, `passkey`, `videoid`, `password`, `link_only`. - `defaults.options.reminder_policy` (string · nullable, optional): The follow-up policy's ID (`rpol_…`): the reminders this delivery sends while it waits. - `defaults.options.brand_profile_id` (string · nullable, optional): The brand profile's ID (`bp_…`). One set on the delivery wins over the batch's, and the batch's over the account's default. - `defaults.options.expires_in_days` (integer, optional): How many days the recipient has to confirm receipt before the delivery expires: a whole number from 1 to 365. Default 30. For a delivery in a batch, the days count from when the batch is sent out. - `defaults.options.sender_display` (string, optional): What the recipient and the evidence show as the sender. `legal_person` (your organisation's default) shows the organisation, with an opaque reference to the person who sent it; that person's name, personal code and e-mail are not shown. `individual` names the person who sent it as well. Your choice per delivery or template; a personal account is always `individual`. One of: `legal_person`, `individual`. - `defaults.options.allow_parasend_links` (boolean, optional): Send even though your brand profile's link domain is not working, with Parasend's own links (warning `link_domain_unavailable`). Without it such a delivery is refused with 409 `link_domain_unavailable`. Default: `false`. - `defaults.options.notice_wording` (object, optional): Which saved wording the first notice uses, per channel: a wording ID, null for Parasend's own, or left out for your organisation's default. - `defaults.options.notice_wording.email` (string · nullable, optional) - `defaults.options.notice_wording.sms` (string · nullable, optional) - `defaults.additional_security_source` (string · nullable, optional): `csv_personal_code`: a delivery added with a `recipient.personal_code`, and no `recipient.conditional_authentication` of its own, gets `conditional_authentication: true`, so only that person can open it. null or left out: no such rule. One of: `csv_personal_code`. - `defaults.language` (string, optional): The `language` (`is` or `en`) for deliveries added to the batch that do not set their own. - `defaults.message` (string, optional): The `message` for deliveries added to the batch that do not set their own: the plain-text note the recipient sees before signing in. - `defaults.title` (string, optional): The `title` for deliveries added to the batch that do not set their own: the subject the recipient sees in the notice and on their page. - `defaults.verification_level` (string, optional): The `recipient.verification_level` (`high`, `substantial`, `medium` or `low`) for deliveries added to the batch that do not set their own. - `follow_up_policy_id` (string · nullable, optional): `rpol_…`, or null (the default) for no policy; never the account default. `rpol_none` is the same as null. - `allow_policy_override` (boolean, optional): Whether a single delivery may follow another policy than the batch's. Default: `true`. - `scheduled_at` (string · date-time · nullable, optional): Send out at this time instead of at once (on_finalize only). - `send_window` (object, optional) - `send_window.start` (string, required): Local time HH:MM, inclusive. - `send_window.end` (string, required): Local time HH:MM, exclusive; after start, same day. - `send_window.tz` (string, optional): The time zone of `start` and `end`: always `Atlantic/Reykjavik`, which is UTC all year. ## Example: A batch of notices ```bash curl -X POST "https://api.parasend.com/v1/batches" \ -H "Authorization: Bearer $PARASEND_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "name": "November notices" }' ``` ```js const res = await fetch('https://api.parasend.com/v1/batches', { method: 'POST', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, 'Idempotency-Key': crypto.randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ name: 'November notices', }), }); const data = await res.json(); ``` ## Response 201 Created (status `draft`). ```json { "id": "bat_01M6R27T8SXWNM911FS53W4S9F", "object": "batch", "name": "November notices", "status": "draft", "sending_mode": "on_finalize", "follow_up_policy_id": null, "allow_policy_override": true, "follow_ups_paused": false, "scheduled_at": null, "send_window": { "tz": "Atlantic/Reykjavik", "end": "21:00", "start": "09:00" }, "defaults": {}, "files": [], "accepting_deliveries": true, "counts": { "total": 0, "delivered": 0, "in_progress": 0, "ended": 0 }, "created_by": { "user_id": "usr_01M6R27SYD4B50414S0BZ4KBKR", "via": "api_key" }, "livemode": false, "created": "2026-11-06T09:15:05.881Z", "sent_out_at": null, "following_up_at": null, "completed_at": null, "cancelled_at": null } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # List batches `GET https://api.parasend.com/v1/batches` Source: https://parasend.com/docs/api/batches/list Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Lists your batches, a page at a time. ## Query parameters - `limit` (integer, optional): Default: `25`. - `cursor` (string, optional) - `status` (string, optional): One or more of draft, scheduled, sending, following_up, completed, cancelled, comma-separated. - `follow_up_policy_id` (string, optional) - `created_by` (string, optional): The member (usr_…) who made the batch. - `created_after` (string · date-time, optional) - `created_before` (string · date-time, optional) - `q` (string, optional): Part of the name, or the exact bat_ id. - `sort` (string, optional): Sort by `name` or by `status` (in the order a batch moves through), newest first within each. One of: `created_desc`, `created_asc`, `name_asc`, `name_desc`, `status_asc`, `status_desc`. Default: `"created_desc"`. - `accepting_deliveries` (boolean, optional): true: only drafts and open rolling batches, the ones a delivery can join now. false: the others. ## Example: List batches ```bash curl "https://api.parasend.com/v1/batches" \ -H "Authorization: Bearer $PARASEND_KEY" ``` ```js const res = await fetch('https://api.parasend.com/v1/batches', { method: 'GET', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, }, }); const data = await res.json(); ``` ## Response 200 Paginated list. ```json { "data": [ { "id": "bat_01M6R27T8SXWNM911FS53W4S9F", "object": "batch", "name": "November notices", "status": "draft", "sending_mode": "on_finalize", "follow_up_policy_id": null, "allow_policy_override": true, "follow_ups_paused": false, "scheduled_at": null, "send_window": { "tz": "Atlantic/Reykjavik", "end": "21:00", "start": "09:00" }, "defaults": {}, "files": [], "accepting_deliveries": true, "counts": { "total": 0, "delivered": 0, "in_progress": 0, "ended": 0 }, "created_by": { "user_id": "usr_01M6R27SYD4B50414S0BZ4KBKR", "via": "api_key" }, "livemode": false, "created": "2026-11-06T09:15:05.881Z", "sent_out_at": null, "following_up_at": null, "completed_at": null, "cancelled_at": null } ], "has_more": false, "next_cursor": null } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Retrieve a batch `GET https://api.parasend.com/v1/batches/{id}` Source: https://parasend.com/docs/api/batches/retrieve Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Retrieves a batch, with its funnel: how many of its deliveries were notified, opened, verified and confirmed. The counts only go up. ## Path parameters - `id` (string, required): The batch's ID, starting `bat_`. ## Query parameters - `policy_id` (string, optional): The deliveries following this policy now; rpol_none for none. ## Example: Retrieve a batch ```bash curl "https://api.parasend.com/v1/batches/bat_01M6R27T8SXWNM911FS53W4S9F" \ -H "Authorization: Bearer $PARASEND_KEY" ``` ```js const res = await fetch('https://api.parasend.com/v1/batches/bat_01M6R27T8SXWNM911FS53W4S9F', { method: 'GET', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, }, }); const data = await res.json(); ``` ## Response 200 The batch. ```json { "id": "bat_01M6R27T8SXWNM911FS53W4S9F", "object": "batch", "name": "November notices", "status": "draft", "sending_mode": "on_finalize", "follow_up_policy_id": null, "allow_policy_override": true, "follow_ups_paused": false, "scheduled_at": null, "send_window": { "tz": "Atlantic/Reykjavik", "end": "21:00", "start": "09:00" }, "defaults": {}, "files": [], "accepting_deliveries": true, "counts": { "total": 0, "delivered": 0, "in_progress": 0, "ended": 0 }, "created_by": { "user_id": "usr_01M6R27SYD4B50414S0BZ4KBKR", "via": "api_key" }, "livemode": false, "created": "2026-11-06T09:15:05.881Z", "sent_out_at": null, "following_up_at": null, "completed_at": null, "cancelled_at": null, "follow_up_funnel": { "as_of": "2026-11-06T09:15:05.894Z", "policy_id": null, "total": 0, "not_yet_notified": 0, "notice": { "delivered": 0, "in_progress": 0, "unreachable": 0, "expired": 0, "revoked": 0, "not_matched": 0, "sent": 0, "bounced": 0, "renotified_via_lookup": 0, "renotified_delivered": 0, "email_opened": 0 }, "reminders": [], "outcome": { "delivered": 0, "in_progress": 0, "unreachable": 0, "expired": 0, "revoked": 0, "not_matched": 0 }, "confirmed_direct": 0, "rescued_by_followups": 0, "delivered_total": 0 }, "funnel": { "total": 0, "notified": 0, "opened": 0, "authenticated": 0, "confirmed_direct": 0, "rescued_by_followups": 0, "delivered_total": 0, "expired": 0, "revoked": 0, "auth_failed": 0, "bounced": 0, "paper_pending": 0, "needs_address": 0, "bounce_rate": 0, "median_time_to_confirm": null }, "kpis": { "delivered": 0, "delivered_pct": 0, "in_progress": 0, "median_time_to_delivery": null, "bounce_rate": 0, "rescued_by_followups": 0, "spend": { "sms_messages": 0, "lookups": 0, "amount": 0, "currency": "ISK", "estimated": true }, "vs_previous": null } } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Update a batch `PATCH https://api.parasend.com/v1/batches/{id}` Source: https://parasend.com/docs/api/batches/update Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Changes a batch. A draft can be changed in full; after Send out, only its name and its lock. ## Path parameters - `id` (string, required): The batch's ID, starting `bat_`. ## Body (application/json) - `name` (string · nullable, optional) - `defaults` (object, optional) - `defaults.options` (object, optional) - `defaults.options.notify_channels` (array of string, optional): How the recipient is told — the invitation and every reminder go to each channel listed; each needs its contact (recipient.email, recipient.phone). Default — e-mail when there is an address, else SMS. Told on both, a failure on one channel bounces the delivery only once the other cannot arrive either. One of: `email`, `sms`. - `defaults.options.auth_methods` (array of string, optional): Which ways of identifying the recipient to offer within `recipient.verification_level`, in your order of preference. The recipient may always use a way at least as strong as the weakest one you list. Left out: every way the level allows (an SMS code only when the recipient has a phone). One of: `audkenni`, `smart_id`, `sms_otp`, `passkey`, `videoid`, `password`, `link_only`. - `defaults.options.reminder_policy` (string · nullable, optional): The follow-up policy's ID (`rpol_…`): the reminders this delivery sends while it waits. - `defaults.options.brand_profile_id` (string · nullable, optional): The brand profile's ID (`bp_…`). One set on the delivery wins over the batch's, and the batch's over the account's default. - `defaults.options.expires_in_days` (integer, optional): How many days the recipient has to confirm receipt before the delivery expires: a whole number from 1 to 365. Default 30. For a delivery in a batch, the days count from when the batch is sent out. - `defaults.options.sender_display` (string, optional): What the recipient and the evidence show as the sender. `legal_person` (your organisation's default) shows the organisation, with an opaque reference to the person who sent it; that person's name, personal code and e-mail are not shown. `individual` names the person who sent it as well. Your choice per delivery or template; a personal account is always `individual`. One of: `legal_person`, `individual`. - `defaults.options.allow_parasend_links` (boolean, optional): Send even though your brand profile's link domain is not working, with Parasend's own links (warning `link_domain_unavailable`). Without it such a delivery is refused with 409 `link_domain_unavailable`. Default: `false`. - `defaults.options.notice_wording` (object, optional): Which saved wording the first notice uses, per channel: a wording ID, null for Parasend's own, or left out for your organisation's default. - `defaults.options.notice_wording.email` (string · nullable, optional) - `defaults.options.notice_wording.sms` (string · nullable, optional) - `defaults.additional_security_source` (string · nullable, optional): `csv_personal_code`: a delivery added with a `recipient.personal_code`, and no `recipient.conditional_authentication` of its own, gets `conditional_authentication: true`, so only that person can open it. null or left out: no such rule. One of: `csv_personal_code`. - `defaults.language` (string, optional): The `language` (`is` or `en`) for deliveries added to the batch that do not set their own. - `defaults.message` (string, optional): The `message` for deliveries added to the batch that do not set their own: the plain-text note the recipient sees before signing in. - `defaults.title` (string, optional): The `title` for deliveries added to the batch that do not set their own: the subject the recipient sees in the notice and on their page. - `defaults.verification_level` (string, optional): The `recipient.verification_level` (`high`, `substantial`, `medium` or `low`) for deliveries added to the batch that do not set their own. - `follow_up_policy_id` (string · nullable, optional) - `allow_policy_override` (boolean, optional) - `own_policy_deliveries` (string, optional): One of: `keep`, `switch`. - `scheduled_at` (string · date-time · nullable, optional) - `send_window` (object, optional) - `send_window.start` (string, required): Local time HH:MM, inclusive. - `send_window.end` (string, required): Local time HH:MM, exclusive; after start, same day. - `send_window.tz` (string, optional): The time zone of `start` and `end`: always `Atlantic/Reykjavik`, which is UTC all year. ## Example: Rename a batch ```bash curl -X PATCH "https://api.parasend.com/v1/batches/bat_01M6R27T8SXWNM911FS53W4S9F" \ -H "Authorization: Bearer $PARASEND_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "November notices, first run" }' ``` ```js const res = await fetch('https://api.parasend.com/v1/batches/bat_01M6R27T8SXWNM911FS53W4S9F', { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ name: 'November notices, first run', }), }); const data = await res.json(); ``` ## Response 200 The batch. ```json { "id": "bat_01M6R27T8SXWNM911FS53W4S9F", "object": "batch", "name": "November notices, first run", "status": "draft", "sending_mode": "on_finalize", "follow_up_policy_id": null, "allow_policy_override": true, "follow_ups_paused": false, "scheduled_at": null, "send_window": { "tz": "Atlantic/Reykjavik", "end": "21:00", "start": "09:00" }, "defaults": {}, "files": [], "accepting_deliveries": true, "counts": { "total": 0, "delivered": 0, "in_progress": 0, "ended": 0 }, "created_by": { "user_id": "usr_01M6R27SYD4B50414S0BZ4KBKR", "via": "api_key" }, "livemode": false, "created": "2026-11-06T09:15:05.881Z", "sent_out_at": null, "following_up_at": null, "completed_at": null, "cancelled_at": null } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Add deliveries to a batch `POST https://api.parasend.com/v1/batches/{id}/deliveries` Source: https://parasend.com/docs/api/batches/add-deliveries Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Send an `Idempotency-Key` header: a retry with the same key returns the first answer. **Partial-failure pattern (normative):** returns 200 with per-item results `{index, status: created | failed, delivery_id?, error?}` — the inline `error` object is byte-identical in shape to top-level errors. A call where every item fails still returns 200; only request-level faults (auth, malformed JSON, > 1,000 items) use top-level errors. Unresolved paper addresses are per-item `needs_address` results, not errors. ## Path parameters - `id` (string, required): The batch's ID, starting `bat_`. ## Body (application/json) - `deliveries` (array of object, required) - `deliveries[].recipient` (object, required): Who receives a delivery. At least one of `email` and `phone`; `phone` is required at `verification_level: medium`, where the SMS code goes. With `conditional_authentication` true, only the named person can open it: `personal_code` is required at `high` (only an eID with that kennitala passes) and `id_document_number` at `substantial` (only that identity document passes). Without it, the recipient still verifies at the level you set, and their verified identity is recorded and returned to you. A mismatch is the event `delivery.auth_failed`, with the reason `personal_code_mismatch` or `id_document_mismatch`. - `deliveries[].recipient.verification_level` (string, required): How the recipient must identify themselves before they see the documents: `high`, eID; `substantial`, a passport or ID-card check with a selfie; `medium`, a one-time code sent by SMS when they open the link; `low`, the link alone. A stronger way is always accepted. One of: `high`, `substantial`, `medium`, `low`. - `deliveries[].recipient.name` (string · nullable, optional): The recipient's name as you gave it, trimmed: up to 255 characters, no control characters. It is sealed into the evidence as given and never checked; null when none was given. `verified_name` holds the name from their eID or passport. - `deliveries[].recipient.email` (string · email · nullable, optional): The recipient's e-mail address, where e-mail notices and reminders go; null when not given. At least one of `email` and `phone` is required. - `deliveries[].recipient.phone` (string · nullable, optional): E.164 - `deliveries[].recipient.address` (object · nullable, optional) - `deliveries[].recipient.address.line1` (string, optional): The first address line, such as the street and number. Required. - `deliveries[].recipient.address.line2` (string · nullable, optional): A second address line, or null. - `deliveries[].recipient.address.postal_code` (string, optional): The postal code. Required. - `deliveries[].recipient.address.city` (string, optional): The town or city. Required. - `deliveries[].recipient.address.country` (string, optional): ISO 3166-1 alpha-2 - `deliveries[].recipient.address.source` (string · nullable, optional): Where the address came from, as you state it: `thjodskra`, Iceland's national registry (Þjóðskrá); `customer_csv`, your own records, imported from a file; `manual`, entered by hand. Default `manual`. One of: `thjodskra`, `customer_csv`, `manual`. - `deliveries[].recipient.conditional_authentication` (boolean, optional): When true, only the person you name can open the delivery: at `high` their eID must carry `personal_code`, and at `substantial` their passport or ID card must carry `id_document_number`. Default false: the recipient still identifies themselves at the level, and you get back the identity they used. Default: `false`. - `deliveries[].recipient.personal_code` (string · nullable, optional): The recipient's Icelandic personal code (kennitala). Required when `conditional_authentication` is true at `high`. - `deliveries[].recipient.id_document_number` (string · nullable, optional): The number of the recipient's passport or ID card. Required when `conditional_authentication` is true at `substantial`. - `deliveries[].files` (array of string, required): The files to send: the `fil_` IDs that Upload a file returned. - `deliveries[].options` (object, optional) - `deliveries[].options.notify_channels` (array of string, optional): How the recipient is told — the invitation and every reminder go to each channel listed; each needs its contact (recipient.email, recipient.phone). Default — e-mail when there is an address, else SMS. Told on both, a failure on one channel bounces the delivery only once the other cannot arrive either. One of: `email`, `sms`. - `deliveries[].options.auth_methods` (array of string, optional): Which ways of identifying the recipient to offer within `recipient.verification_level`, in your order of preference. The recipient may always use a way at least as strong as the weakest one you list. Left out: every way the level allows (an SMS code only when the recipient has a phone). One of: `audkenni`, `smart_id`, `sms_otp`, `passkey`, `videoid`, `password`, `link_only`. - `deliveries[].options.reminder_policy` (string · nullable, optional): The follow-up policy's ID (`rpol_…`): the reminders this delivery sends while it waits. - `deliveries[].options.brand_profile_id` (string · nullable, optional): The brand profile's ID (`bp_…`). One set on the delivery wins over the batch's, and the batch's over the account's default. - `deliveries[].options.expires_in_days` (integer, optional): How many days the recipient has to confirm receipt before the delivery expires: a whole number from 1 to 365. Default 30. For a delivery in a batch, the days count from when the batch is sent out. - `deliveries[].options.sender_display` (string, optional): What the recipient and the evidence show as the sender. `legal_person` (your organisation's default) shows the organisation, with an opaque reference to the person who sent it; that person's name, personal code and e-mail are not shown. `individual` names the person who sent it as well. Your choice per delivery or template; a personal account is always `individual`. One of: `legal_person`, `individual`. - `deliveries[].options.allow_parasend_links` (boolean, optional): Send even though your brand profile's link domain is not working, with Parasend's own links (warning `link_domain_unavailable`). Without it such a delivery is refused with 409 `link_domain_unavailable`. Default: `false`. - `deliveries[].options.notice_wording` (object, optional): Which saved wording the first notice uses, per channel: a wording ID, null for Parasend's own, or left out for your organisation's default. - `deliveries[].options.notice_wording.email` (string · nullable, optional) - `deliveries[].options.notice_wording.sms` (string · nullable, optional) - `deliveries[].metadata` (object, optional): Your own key–value pairs, kept with the object and returned as you sent them. Never put personal data here. - `deliveries[].title` (string, optional): The subject the recipient sees in the notice and on their page. - `deliveries[].message` (string, optional): A plain-text note shown before sign-in. Line breaks are kept. It is not sealed into the evidence. - `deliveries[].language` (string, optional): The language of the notice and of the recipient page. One of: `is`, `en`. Default: `"is"`. ## Example: Add two deliveries ```bash curl -X POST "https://api.parasend.com/v1/batches/bat_01M6R27T8SXWNM911FS53W4S9F/deliveries" \ -H "Authorization: Bearer $PARASEND_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "deliveries": [ { "recipient": { "name": "Anna Björk Jónsdóttir", "email": "anna.bjork@example.com", "verification_level": "high" }, "files": [ "fil_01M6R27SZK99V6N6WG4GK15T6R" ], "title": "Final notice: invoice 2026-114" }, { "recipient": { "name": "Jón Jónsson", "email": "jon.jonsson@example.com", "verification_level": "high" }, "files": [ "fil_01M6R27SZK99V6N6WG4GK15T6R" ], "title": "Final notice: invoice 2026-115" } ] }' ``` ```js const res = await fetch('https://api.parasend.com/v1/batches/bat_01M6R27T8SXWNM911FS53W4S9F/deliveries', { method: 'POST', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, 'Idempotency-Key': crypto.randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ deliveries: [ { recipient: { name: 'Anna Björk Jónsdóttir', email: 'anna.bjork@example.com', verification_level: 'high', }, files: ['fil_01M6R27SZK99V6N6WG4GK15T6R'], title: 'Final notice: invoice 2026-114', }, { recipient: { name: 'Jón Jónsson', email: 'jon.jonsson@example.com', verification_level: 'high', }, files: ['fil_01M6R27SZK99V6N6WG4GK15T6R'], title: 'Final notice: invoice 2026-115', }, ], }), }); const data = await res.json(); ``` ## Response 200 Per-item results. ```json { "object": "list", "data": [ { "index": 0, "status": "created", "delivery_id": "dlv_01M6R27T9GD2VVC1DVVE1DD6RB" }, { "index": 1, "status": "created", "delivery_id": "dlv_01M6R27T9M7Z1HH1FADRGGMEZK" } ], "created": 2, "failed": 0 } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # List a batch's deliveries `GET https://api.parasend.com/v1/batches/{id}/deliveries` Source: https://parasend.com/docs/api/batches/deliveries Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Lists the batch's deliveries. `q` matches the title, the recipient's name or e-mail in part, or a delivery ID exactly; never a personal code or a phone number. `pin`, `recipient_email` and `phone` match exactly. With `Accept: text/csv`, every match is streamed as CSV. ## Path parameters - `id` (string, required): The batch's ID, starting `bat_`. ## Query parameters - `limit` (integer, optional): Default: `25`. - `cursor` (string, optional) - `q` (string, optional) - `status` (string, optional): One of: `created`, `notified`, `opened`, `authenticated`, `auth_failed`, `confirmed`, `delivered`, `evidence_sealed`, `expired`, `revoked`, `bounced`, `consignment_failed`, `paper_pending_approval`, `paper_dispatched`. - `needs_attention` (boolean, optional) - `pin` (string, optional) - `recipient_email` (string · email, optional) - `phone` (string, optional): The whole number, E.164 or 7 Icelandic digits. - `outcome` (string, optional): One of: `delivered`, `in_progress`, `unreachable`, `expired`, `revoked`, `not_matched`. - `reminders` (integer, optional) - `policy_source` (string, optional): One of: `batch`, `own`, `none`. - `channel` (string, optional): One of: `email`, `sms`. - `sort` (string, optional) ## Example: List a batch's deliveries ```bash curl "https://api.parasend.com/v1/batches/bat_01M6R27T8SXWNM911FS53W4S9F/deliveries" \ -H "Authorization: Bearer $PARASEND_KEY" ``` ```js const res = await fetch('https://api.parasend.com/v1/batches/bat_01M6R27T8SXWNM911FS53W4S9F/deliveries', { method: 'GET', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, }, }); const data = await res.json(); ``` ## Response 200 Paginated member deliveries. ```json { "data": [ { "id": "dlv_01M6R27T9M7Z1HH1FADRGGMEZK", "object": "delivery", "kind": "documents", "direction": "outbound", "content_type": "files", "status": "created", "last_activity": null, "title": "Final notice: invoice 2026-115", "message": null, "language": "is", "certificate_language": "is", "recipient": { "name": "Jón Jónsson", "verified_name": null, "verified_method": null, "verification_photos": [], "email": "jon.jonsson@example.com", "phone": null, "address": null, "verification_level": "high", "conditional_authentication": false, "personal_code": null, "id_document_number": null }, "files": [ { "file_id": "fil_01M6R27SZK99V6N6WG4GK15T6R", "path": "test-notice.pdf", "primary": true } ], "batch_id": "bat_01M6R27T8SXWNM911FS53W4S9F", "delivery_url": null, "short_url": null, "evidence_policy": "urn:parasend:policy:evidence:standard:1", "sender_display": "legal_person", "operator_reference": "OP-7K3Q-M2", "recipient_ref": "rcp_01M6R27T9M3F3MJ7NMBYGW3V30", "private": false, "options": { "auth_methods": [ "audkenni", "smart_id", "passkey" ], "reminder_policy": null, "brand_profile_id": null, "expires_in_days": 30, "sender_display": "legal_person", "notify_channels": [ "email" ] }, "legal_hold": false, "deletion_scheduled_at": null, "livemode": false, "created": "2026-11-06T09:15:05.908Z", "expires_at": null, "metadata": {}, "origin_request": null, "reminder_waiting": null, "email_waiting": null, "follow_up": { "policy_id": null, "policy_name": null, "policy_source": "batch", "state": "done", "reminders_sent": 0, "last_step": null, "next_step": null, "paused": false } }, { "id": "dlv_01M6R27T9GD2VVC1DVVE1DD6RB", "object": "delivery", "kind": "documents", "direction": "outbound", "content_type": "files", "status": "created", "last_activity": null, "title": "Final notice: invoice 2026-114", "message": null, "language": "is", "certificate_language": "is", "recipient": { "name": "Anna Björk Jónsdóttir", "verified_name": null, "verified_method": null, "verification_photos": [], "email": "anna.bjork@example.com", "phone": null, "address": null, "verification_level": "high", "conditional_authentication": false, "personal_code": null, "id_document_number": null }, "files": [ { "file_id": "fil_01M6R27SZK99V6N6WG4GK15T6R", "path": "test-notice.pdf", "primary": true } ], "batch_id": "bat_01M6R27T8SXWNM911FS53W4S9F", "delivery_url": null, "short_url": null, "evidence_policy": "urn:parasend:policy:evidence:standard:1", "sender_display": "legal_person", "operator_reference": "OP-7K3Q-M2", "recipient_ref": "rcp_01M6R27T9GVJW7HXAWJ0T5H4EM", "private": false, "options": { "auth_methods": [ "audkenni", "smart_id", "passkey" ], "reminder_policy": null, "brand_profile_id": null, "expires_in_days": 30, "sender_display": "legal_person", "notify_channels": [ "email" ] }, "legal_hold": false, "deletion_scheduled_at": null, "livemode": false, "created": "2026-11-06T09:15:05.904Z", "expires_at": null, "metadata": {}, "origin_request": null, "reminder_waiting": null, "email_waiting": null, "follow_up": { "policy_id": null, "policy_name": null, "policy_source": "batch", "state": "done", "reminders_sent": 0, "last_step": null, "next_step": null, "paused": false } } ], "has_more": false, "next_cursor": null } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Check a batch before sending `GET https://api.parasend.com/v1/batches/{id}/preflight` Source: https://parasend.com/docs/api/batches/preflight Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). The checklist before Send out, and the most the batch can cost. ## Path parameters - `id` (string, required): The batch's ID, starting `bat_`. ## Example: Check a batch before sending ```bash curl "https://api.parasend.com/v1/batches/bat_01M6R27T8SXWNM911FS53W4S9F/preflight" \ -H "Authorization: Bearer $PARASEND_KEY" ``` ```js const res = await fetch('https://api.parasend.com/v1/batches/bat_01M6R27T8SXWNM911FS53W4S9F/preflight', { method: 'GET', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, }, }); const data = await res.json(); ``` ## Response 200 The checks and the estimate. ```json { "object": "batch_preflight", "ready": true, "checks": [ { "id": "recipients", "ok": true, "total": 2, "to_send": 2, "row_errors": 0 }, { "id": "documents", "ok": true, "without_documents": 0 }, { "id": "brand", "ok": true, "brand_profile_id": null, "name": null }, { "id": "policy", "ok": true, "follow_up_policy_id": null, "name": null, "steps": [], "own_policy": 0 }, { "id": "expiry", "ok": true, "steps_after_expiry": 0 }, { "id": "test_send", "ok": null, "available": true, "last_sent_at": null } ], "estimate": { "currency": "ISK", "amount": 622, "vat": 149, "total_amount": 771, "items": [ { "kind": "delivery", "description": { "is": "Sending", "en": "Delivery" }, "country": null, "quantity": 2, "unit_amount": 311, "amount": 622, "vat": 149, "total_amount": 771 } ], "maximum": true, "lookups": 0 } } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Send out a batch `POST https://api.parasend.com/v1/batches/{id}/finalize` Source: https://parasend.com/docs/api/batches/send-out Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Send an `Idempotency-Key` header: a retry with the same key returns the first answer. Portal verb "Send out". Moves `draft → sending` (`on_finalize` mode). Empty batches are rejected (400 `batch_empty_finalize`); already-finalized batches return 409 `batch_finalized`. ## Path parameters - `id` (string, required): The batch's ID, starting `bat_`. ## Example: Send out a batch ```bash curl -X POST "https://api.parasend.com/v1/batches/bat_01M6R27T8SXWNM911FS53W4S9F/finalize" \ -H "Authorization: Bearer $PARASEND_KEY" \ -H "Idempotency-Key: $(uuidgen)" ``` ```js const res = await fetch('https://api.parasend.com/v1/batches/bat_01M6R27T8SXWNM911FS53W4S9F/finalize', { method: 'POST', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, 'Idempotency-Key': crypto.randomUUID(), }, }); const data = await res.json(); ``` ## Response 200 Batch now `sending`. ```json { "id": "bat_01M6R27T8SXWNM911FS53W4S9F", "object": "batch", "name": "November notices, first run", "status": "sending", "sending_mode": "on_finalize", "follow_up_policy_id": null, "allow_policy_override": true, "follow_ups_paused": false, "scheduled_at": null, "send_window": { "tz": "Atlantic/Reykjavik", "end": "21:00", "start": "09:00" }, "defaults": {}, "files": [], "accepting_deliveries": false, "counts": { "total": 2, "delivered": 0, "ended": 0, "in_progress": 2 }, "created_by": { "user_id": "usr_01M6R27SYD4B50414S0BZ4KBKR", "via": "api_key" }, "livemode": false, "created": "2026-11-06T09:15:05.881Z", "sent_out_at": "2026-11-06T09:15:05.922Z", "following_up_at": null, "completed_at": null, "cancelled_at": null } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Get a batch report `GET https://api.parasend.com/v1/batches/{id}/report` Source: https://parasend.com/docs/api/batches/report Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). The batch's funnel report, as JSON, or as CSV with `Accept: text/csv`. ## Path parameters - `id` (string, required): The batch's ID, starting `bat_`. ## Example: Get a batch report ```bash curl "https://api.parasend.com/v1/batches/bat_01M6R27T8SXWNM911FS53W4S9F/report" \ -H "Authorization: Bearer $PARASEND_KEY" ``` ```js const res = await fetch('https://api.parasend.com/v1/batches/bat_01M6R27T8SXWNM911FS53W4S9F/report', { method: 'GET', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, }, }); const data = await res.json(); ``` ## Response 200 The report. ```json { "object": "batch_report", "batch": { "id": "bat_01M6R27T8SXWNM911FS53W4S9F", "object": "batch", "name": "November notices, first run", "status": "sending", "sending_mode": "on_finalize", "follow_up_policy_id": null, "allow_policy_override": true, "follow_ups_paused": false, "scheduled_at": null, "send_window": { "tz": "Atlantic/Reykjavik", "end": "21:00", "start": "09:00" }, "defaults": {}, "files": [], "accepting_deliveries": false, "counts": { "total": 2, "delivered": 0, "ended": 0, "in_progress": 2 }, "created_by": { "user_id": "usr_01M6R27SYD4B50414S0BZ4KBKR", "via": "api_key" }, "livemode": false, "created": "2026-11-06T09:15:05.881Z", "sent_out_at": "2026-11-06T09:15:05.922Z", "following_up_at": null, "completed_at": null, "cancelled_at": null }, "follow_up_funnel": { "as_of": "2026-11-06T09:15:05.930Z", "policy_id": null, "total": 2, "not_yet_notified": 2, "notice": { "delivered": 0, "in_progress": 0, "unreachable": 0, "expired": 0, "revoked": 0, "not_matched": 0, "sent": 0, "bounced": 0, "renotified_via_lookup": 0, "renotified_delivered": 0, "email_opened": 0 }, "reminders": [], "outcome": { "delivered": 0, "in_progress": 2, "unreachable": 0, "expired": 0, "revoked": 0, "not_matched": 0 }, "confirmed_direct": 0, "rescued_by_followups": 0, "delivered_total": 0 }, "funnel": { "total": 2, "notified": 0, "opened": 0, "authenticated": 0, "confirmed_direct": 0, "rescued_by_followups": 0, "delivered_total": 0, "expired": 0, "revoked": 0, "auth_failed": 0, "bounced": 0, "paper_pending": 0, "needs_address": 0, "bounce_rate": 0, "median_time_to_confirm": null }, "kpis": { "delivered": 0, "delivered_pct": 0, "in_progress": 2, "median_time_to_delivery": null, "bounce_rate": 0, "rescued_by_followups": 0, "spend": { "sms_messages": 0, "lookups": 0, "amount": 0, "currency": "ISK", "estimated": true }, "vs_previous": null } } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Pause a batch's follow-ups `POST https://api.parasend.com/v1/batches/{id}/pause` Source: https://parasend.com/docs/api/batches/pause Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Send an `Idempotency-Key` header: a retry with the same key returns the first answer. Pauses the batch's follow-ups. Pausing is a flag, not a status: the batch keeps its status. ## Path parameters - `id` (string, required): The batch's ID, starting `bat_`. ## Example: Pause a batch's follow-ups ```bash curl -X POST "https://api.parasend.com/v1/batches/bat_01M6R27T8SXWNM911FS53W4S9F/pause" \ -H "Authorization: Bearer $PARASEND_KEY" \ -H "Idempotency-Key: $(uuidgen)" ``` ```js const res = await fetch('https://api.parasend.com/v1/batches/bat_01M6R27T8SXWNM911FS53W4S9F/pause', { method: 'POST', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, 'Idempotency-Key': crypto.randomUUID(), }, }); const data = await res.json(); ``` ## Response 200 The batch, follow_ups_paused true. ```json { "id": "bat_01M6R27T8SXWNM911FS53W4S9F", "object": "batch", "name": "November notices, first run", "status": "sending", "sending_mode": "on_finalize", "follow_up_policy_id": null, "allow_policy_override": true, "follow_ups_paused": true, "scheduled_at": null, "send_window": { "tz": "Atlantic/Reykjavik", "end": "21:00", "start": "09:00" }, "defaults": {}, "files": [], "accepting_deliveries": false, "counts": { "total": 2, "delivered": 0, "ended": 0, "in_progress": 2 }, "created_by": { "user_id": "usr_01M6R27SYD4B50414S0BZ4KBKR", "via": "api_key" }, "livemode": false, "created": "2026-11-06T09:15:05.881Z", "sent_out_at": "2026-11-06T09:15:05.922Z", "following_up_at": null, "completed_at": null, "cancelled_at": null } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Resume a batch's follow-ups `POST https://api.parasend.com/v1/batches/{id}/resume` Source: https://parasend.com/docs/api/batches/resume Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Send an `Idempotency-Key` header: a retry with the same key returns the first answer. Resumes the batch's follow-ups. What waited goes out in the next sending window. ## Path parameters - `id` (string, required): The batch's ID, starting `bat_`. ## Example: Resume a batch's follow-ups ```bash curl -X POST "https://api.parasend.com/v1/batches/bat_01M6R27T8SXWNM911FS53W4S9F/resume" \ -H "Authorization: Bearer $PARASEND_KEY" \ -H "Idempotency-Key: $(uuidgen)" ``` ```js const res = await fetch('https://api.parasend.com/v1/batches/bat_01M6R27T8SXWNM911FS53W4S9F/resume', { method: 'POST', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, 'Idempotency-Key': crypto.randomUUID(), }, }); const data = await res.json(); ``` ## Response 200 The batch, follow_ups_paused false. ```json { "id": "bat_01M6R27T8SXWNM911FS53W4S9F", "object": "batch", "name": "November notices, first run", "status": "sending", "sending_mode": "on_finalize", "follow_up_policy_id": null, "allow_policy_override": true, "follow_ups_paused": false, "scheduled_at": null, "send_window": { "tz": "Atlantic/Reykjavik", "end": "21:00", "start": "09:00" }, "defaults": {}, "files": [], "accepting_deliveries": false, "counts": { "total": 2, "delivered": 0, "ended": 0, "in_progress": 2 }, "created_by": { "user_id": "usr_01M6R27SYD4B50414S0BZ4KBKR", "via": "api_key" }, "livemode": false, "created": "2026-11-06T09:15:05.881Z", "sent_out_at": "2026-11-06T09:15:05.922Z", "following_up_at": null, "completed_at": null, "cancelled_at": null } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Change a batch's follow-up policy `POST https://api.parasend.com/v1/batches/{id}/policy_change` Source: https://parasend.com/docs/api/batches/change-policy Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Send an `Idempotency-Key` header: a retry with the same key returns the first answer. Changes the follow-up policy of the batch's deliveries that have not yet confirmed. With `dry_run: true`, shows what would change and what it would cost, and changes nothing. ## Path parameters - `id` (string, required): The batch's ID, starting `bat_`. ## Body (application/json) - `policy_id` (string · nullable, required): The new follow-up policy's ID (`rpol_…`), or null (or `rpol_none`) for none. - `scope` (string, optional): For a batch only. One of: `batch_followers`, `all_pending`. Default: `"batch_followers"`. - `start` (object, optional): Where notified deliveries start in the new policy: `{"from": "first"}` (the default), its first reminder, or `{"from": "step", "step": n}`, step n counting from 1. The first notice is never sent again; deliveries not yet notified follow the new policy from its start. - `start.from` (string, optional): One of: `first`, `step`. - `start.step` (integer, optional) - `extend_expiry_to` (string · date-time · nullable, optional): Move the affected deliveries' expiry to this time (ISO 8601), which must be in the future. An expiry is only ever moved later, never past a year from the delivery's creation, and never past the deletion of its documents. - `dry_run` (boolean, optional): When true, nothing changes: the response says what the change would do. Default false. Default: `false`. ## Example: Preview a policy change for the batch ```bash curl -X POST "https://api.parasend.com/v1/batches/bat_01M6R27T8SXWNM911FS53W4S9F/policy_change" \ -H "Authorization: Bearer $PARASEND_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "policy_id": "rpol_01M6R27T5NK3830GCN9QPS9M95", "dry_run": true }' ``` ```js const res = await fetch('https://api.parasend.com/v1/batches/bat_01M6R27T8SXWNM911FS53W4S9F/policy_change', { method: 'POST', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, 'Idempotency-Key': crypto.randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ policy_id: 'rpol_01M6R27T5NK3830GCN9QPS9M95', dry_run: true, }), }); const data = await res.json(); ``` ## Response 200 What changed — or, with dry_run, what would. ```json { "object": "policy_change", "dry_run": true, "policy_id": "rpol_01M6R27T5NK3830GCN9QPS9M95", "start_step": "first", "affected": 2, "unchanged": { "ended": 0, "own_policy": 0 }, "not_yet_notified": 2, "messages": { "email": 0, "sms": 0 }, "first_send_at": null, "estimate": { "currency": "ISK", "amount": 0, "vat": 0, "total_amount": 0 }, "after_expiry": 0, "held_by_gap": 0, "in_flight": 0, "schedule": [], "expires_at": null, "expiry_capped": 0 } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Extend a batch's deliveries `POST https://api.parasend.com/v1/batches/{id}/extend_expiry` Source: https://parasend.com/docs/api/batches/extend-expiry Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Send an `Idempotency-Key` header: a retry with the same key returns the first answer. Gives the batch's pending deliveries longer to be confirmed: never past a year, nor past their documents' deletion. ## Path parameters - `id` (string, required): The batch's ID, starting `bat_`. ## Body (application/json) - `expires_at` (string · date-time, optional) - `add_days` (integer, optional) - `dry_run` (boolean, optional) ## Example: Give everyone 7 more days, as a preview ```bash curl -X POST "https://api.parasend.com/v1/batches/bat_01M6R27T8SXWNM911FS53W4S9F/extend_expiry" \ -H "Authorization: Bearer $PARASEND_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "add_days": 7, "dry_run": true }' ``` ```js const res = await fetch('https://api.parasend.com/v1/batches/bat_01M6R27T8SXWNM911FS53W4S9F/extend_expiry', { method: 'POST', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, 'Idempotency-Key': crypto.randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ add_days: 7, dry_run: true, }), }); const data = await res.json(); ``` ## Response 200 How many were extended, and how many stopped short at their limit. ```json { "object": "expiry_extension", "dry_run": true, "pending": 2, "extended": 0, "capped": 2, "latest_allowed": "2026-12-06T09:15:05.587Z", "documents_retention_days": 30 } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Duplicate a batch `POST https://api.parasend.com/v1/batches/{id}/duplicate` Source: https://parasend.com/docs/api/batches/duplicate Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Send an `Idempotency-Key` header: a retry with the same key returns the first answer. Makes a new draft batch with the same settings and no deliveries. ## Path parameters - `id` (string, required): The batch's ID, starting `bat_`. ## Example: Duplicate a batch ```bash curl -X POST "https://api.parasend.com/v1/batches/bat_01M6R27T8SXWNM911FS53W4S9F/duplicate" \ -H "Authorization: Bearer $PARASEND_KEY" \ -H "Idempotency-Key: $(uuidgen)" ``` ```js const res = await fetch('https://api.parasend.com/v1/batches/bat_01M6R27T8SXWNM911FS53W4S9F/duplicate', { method: 'POST', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, 'Idempotency-Key': crypto.randomUUID(), }, }); const data = await res.json(); ``` ## Response 201 The new draft. ```json { "id": "bat_01M6R27TAZJQCTVN3NPC630T21", "object": "batch", "name": "November notices, first run (copy)", "status": "draft", "sending_mode": "on_finalize", "follow_up_policy_id": null, "allow_policy_override": true, "follow_ups_paused": false, "scheduled_at": null, "send_window": { "tz": "Atlantic/Reykjavik", "end": "21:00", "start": "09:00" }, "defaults": {}, "files": [], "accepting_deliveries": true, "counts": { "total": 0, "delivered": 0, "in_progress": 0, "ended": 0 }, "created_by": { "user_id": "usr_01M6R27SYD4B50414S0BZ4KBKR", "via": "api_key" }, "livemode": false, "created": "2026-11-06T09:15:05.951Z", "sent_out_at": null, "following_up_at": null, "completed_at": null, "cancelled_at": null } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Cancel a batch `POST https://api.parasend.com/v1/batches/{id}/cancel` Source: https://parasend.com/docs/api/batches/cancel Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Send an `Idempotency-Key` header: a retry with the same key returns the first answer. Cancels a batch: revokes every delivery not yet confirmed, and closes the batch. Confirmed deliveries keep their evidence. ## Path parameters - `id` (string, required): The batch's ID, starting `bat_`. ## Example: Cancel a batch ```bash curl -X POST "https://api.parasend.com/v1/batches/bat_01M6R27T8SXWNM911FS53W4S9F/cancel" \ -H "Authorization: Bearer $PARASEND_KEY" \ -H "Idempotency-Key: $(uuidgen)" ``` ```js const res = await fetch('https://api.parasend.com/v1/batches/bat_01M6R27T8SXWNM911FS53W4S9F/cancel', { method: 'POST', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, 'Idempotency-Key': crypto.randomUUID(), }, }); const data = await res.json(); ``` ## Response 200 The cancelled batch, with how many deliveries were revoked and how many kept. ```json { "id": "bat_01M6R27T8SXWNM911FS53W4S9F", "object": "batch", "name": "November notices, first run", "status": "cancelled", "sending_mode": "on_finalize", "follow_up_policy_id": null, "allow_policy_override": true, "follow_ups_paused": false, "scheduled_at": null, "send_window": { "tz": "Atlantic/Reykjavik", "end": "21:00", "start": "09:00" }, "defaults": {}, "files": [], "accepting_deliveries": false, "counts": { "total": 2, "delivered": 0, "ended": 2, "in_progress": 0 }, "created_by": { "user_id": "usr_01M6R27SYD4B50414S0BZ4KBKR", "via": "api_key" }, "livemode": false, "created": "2026-11-06T09:15:05.881Z", "sent_out_at": "2026-11-06T09:15:05.922Z", "following_up_at": null, "completed_at": null, "cancelled_at": "2026-11-06T09:15:05.982Z", "cancelled": { "revoked": 2, "kept": 0 } } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # List events `GET https://api.parasend.com/v1/events` Source: https://parasend.com/docs/api/events/list Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Every event in your account, a page at a time: the same events your webhooks get. Use it to poll when you can't receive webhooks, or to catch up after an outage. ## Query parameters - `limit` (integer, optional): Default: `25`. - `cursor` (string, optional) - `type` (string, optional): One of: `delivery.created`, `delivery.rejected`, `delivery.notified`, `delivery.opened`, `delivery.email_opened`, `delivery.auth_failed`, `delivery.confirmed`, `delivery.downloaded`, `delivery.delivered`, `delivery.reminded`, `delivery.bounced`, `delivery.expired`, `delivery.revoked`, `delivery.consignment_failed`, `delivery.evidence_sealed`, `delivery.evidence_timestamp_deferred`, `delivery.evidence_timestamp_recovered`, `delivery.paper_pending_approval`, `delivery.paper_dispatched`, `delivery.paper_failed`, `document.previewed`, `batch.finalized`, `batch.completed`, `lookup.completed`, `consent.granted`, `consent.withdrawn`, `invitation.sent`, `link.opened`, `otp.sent`, `otp.verified`, `otp.locked`, `secret.attached`, `secret.available`, `secret.revealed`, `secret.copied`, `secret.expired`, `secret.exhausted`, `secret.revoked`, `request.sent`, `request.opened`, `request.fulfilled`, `request.expired`, `request.closed`, `request.reopened`, `deletion.scheduled`, `recipient_domain.verified`, `recipient_domain.degraded`, `recipient_domain.failed`, `member.suspended`, `member.restored`, `member.removed`, `batch.cancelled`, `batch.policy_changed`, `delivery.policy_changed`, `email_sender.verified`, `email_sender.degraded`, `email_sender.failed`, `email_sender.restored`. - `created_after` (string · date-time, optional) - `created_before` (string · date-time, optional): Only events created strictly before this time. With created_after, a date range (as GET /deliveries). - `resource_id` (string, optional): Only events about this resource (dlv_…, bat_…, req_…, …). Exact match. - `order` (string, optional): `asc`, the default, is the order for polling: oldest first, walking forward with the cursor. `desc` lists the newest first. A cursor works only with the order it came from. One of: `asc`, `desc`. Default: `"asc"`. ## Example: Poll for new events ```bash curl "https://api.parasend.com/v1/events?limit=3" \ -H "Authorization: Bearer $PARASEND_KEY" ``` ```js const res = await fetch('https://api.parasend.com/v1/events?limit=3', { method: 'GET', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, }, }); const data = await res.json(); ``` ## Response 200 Paginated events. ```json { "data": [ { "id": "evt_01M6R27T08JTMBJ4B6GQP90838", "object": "event", "type": "delivery.created", "created": "2026-11-06T09:15:05.604Z", "resource": { "type": "delivery", "id": "dlv_01M6R27T05512SCV3852TPM2XY" }, "batch_id": null, "reason": null, "data": null }, { "id": "evt_01M6R27T153J4657RB03NQNXW2", "object": "event", "type": "delivery.created", "created": "2026-11-06T09:15:05.634Z", "resource": { "type": "delivery", "id": "dlv_01M6R27T13NKWVJRJBTHZDP6Y1" }, "batch_id": null, "reason": null, "data": null }, { "id": "evt_01M6R27T1NHEY707SDDNNJTAZK", "object": "event", "type": "delivery.created", "created": "2026-11-06T09:15:05.651Z", "resource": { "type": "delivery", "id": "dlv_01M6R27T1MRPH7P4ZSR0WKTBQD" }, "batch_id": null, "reason": null, "data": null } ], "has_more": true, "next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0xMS0wNlQwOToxNTowNS42NTE1NDVaIiwiaWQiOiJldnRfMDFNNlIyN1QxTkhFWTcwN1NERE5OSlRBWksifQ" } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Retrieve an event `GET https://api.parasend.com/v1/events/{id}` Source: https://parasend.com/docs/api/events/retrieve Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Retrieves one event. ## Path parameters - `id` (string, required): The event's ID, starting `evt_`. ## Example: Retrieve an event ```bash curl "https://api.parasend.com/v1/events/evt_01M6R27T08JTMBJ4B6GQP90838" \ -H "Authorization: Bearer $PARASEND_KEY" ``` ```js const res = await fetch('https://api.parasend.com/v1/events/evt_01M6R27T08JTMBJ4B6GQP90838', { method: 'GET', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, }, }); const data = await res.json(); ``` ## Response 200 The event. ```json { "id": "evt_01M6R27T08JTMBJ4B6GQP90838", "object": "event", "type": "delivery.created", "created": "2026-11-06T09:15:05.604Z", "resource": { "type": "delivery", "id": "dlv_01M6R27T05512SCV3852TPM2XY" }, "batch_id": null, "reason": null, "data": null } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # See where an event was sent `GET https://api.parasend.com/v1/events/{id}/webhooks` Source: https://parasend.com/docs/api/events/webhooks Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). One row per endpoint in the event's mode that had this event queued or replayed to it — including disabled and deleted endpoints, flagged as such. Not paged (at most 20 endpoints per mode, plus deleted ones that had the event). Needs events.read for the event (visibility-scoped like GET /events/{id}) and webhooks.read with delivery visibility all. Never "delivered": status says whether the endpoint answered 2xx. ## Path parameters - `id` (string, required): The event's ID, starting `evt_`. ## Example: See where an event was sent ```bash curl "https://api.parasend.com/v1/events/evt_01M6R27T08JTMBJ4B6GQP90838/webhooks" \ -H "Authorization: Bearer $PARASEND_KEY" ``` ```js const res = await fetch('https://api.parasend.com/v1/events/evt_01M6R27T08JTMBJ4B6GQP90838/webhooks', { method: 'GET', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, }, }); const data = await res.json(); ``` ## Response 200 Outcomes per endpoint. ```json { "object": "list", "data": [ { "endpoint_id": "whk_01M6R27T4KCSN7M0CHFGVH7JRA", "url": "https://hooks.fyrirtaeki.example/parasend", "endpoint_status": "deleted", "status": "succeeded", "attempts": 1, "last_attempt": { "created": "2026-11-06T09:15:05.775Z", "ok": true, "status_code": 200, "latency_ms": 0, "failure_stage": null, "error": null, "replay": true, "test": false }, "next_retry_at": null } ] } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Add a webhook endpoint `POST https://api.parasend.com/v1/webhook_endpoints` Source: https://parasend.com/docs/api/webhooks/create Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Send an `Idempotency-Key` header: a retry with the same key returns the first answer. Adds an endpoint in the key's mode (test or live). The `url` must be HTTPS on port 443, without a user name or password, and its host a DNS name (not an IP address) whose every A and AAAA record is a public address. A host that does not resolve is refused, so a typo shows now rather than after three days of retries. `auth.oauth2.token_url` follows the same rules. The signing `secret` is in this response only: store it. The endpoint records who created it (`created_by`). Errors: `webhook_url_invalid`, `webhook_url_port_not_allowed`, `webhook_url_not_public`, `webhook_url_unresolvable`, `webhook_event_type_unknown`, `webhook_auth_config_invalid`, `webhook_delivery_sources_invalid`, `webhook_endpoint_limit_reached`. ## Body (application/json) - `url` (string, required): HTTPS only. - `event_types` (array of string, optional): One of: `delivery.created`, `delivery.rejected`, `delivery.notified`, `delivery.opened`, `delivery.email_opened`, `delivery.auth_failed`, `delivery.confirmed`, `delivery.downloaded`, `delivery.delivered`, `delivery.reminded`, `delivery.bounced`, `delivery.expired`, `delivery.revoked`, `delivery.consignment_failed`, `delivery.evidence_sealed`, `delivery.evidence_timestamp_deferred`, `delivery.evidence_timestamp_recovered`, `delivery.paper_pending_approval`, `delivery.paper_dispatched`, `delivery.paper_failed`, `document.previewed`, `batch.finalized`, `batch.completed`, `lookup.completed`, `consent.granted`, `consent.withdrawn`, `invitation.sent`, `link.opened`, `otp.sent`, `otp.verified`, `otp.locked`, `secret.attached`, `secret.available`, `secret.revealed`, `secret.copied`, `secret.expired`, `secret.exhausted`, `secret.revoked`, `request.sent`, `request.opened`, `request.fulfilled`, `request.expired`, `request.closed`, `request.reopened`, `deletion.scheduled`, `recipient_domain.verified`, `recipient_domain.degraded`, `recipient_domain.failed`, `member.suspended`, `member.restored`, `member.removed`, `batch.cancelled`, `batch.policy_changed`, `delivery.policy_changed`, `email_sender.verified`, `email_sender.degraded`, `email_sender.failed`, `email_sender.restored`. - `description` (string · nullable, optional): Your own note about the endpoint, up to 500 characters; null or left out for none. - `auth` (object, optional): How Parasend authenticates to your endpoint, in addition to the HMAC signature, which is always sent. Secrets are write-only: accepted on create and update, never returned. The endpoint shows `mode`, the settings that are not secret, and whether a secret is set. - `auth.mode` (string, required): none = HMAC only. static_bearer = an `Authorization` header carrying `Bearer ` with a customer-supplied token. oauth2_client_credentials = a token obtained from the customer's IdP by the client-credentials grant, cached until expiry, sent as the bearer. One of: `none`, `static_bearer`, `oauth2_client_credentials`. Default: `"none"`. - `auth.static_bearer` (object · nullable, optional): With `mode: static_bearer`: the `token` Parasend sends as `Authorization: Bearer `, at most 4,096 characters (write-only; responses show `token_set`). null for any other mode. - `auth.static_bearer.token` (string, optional): Never returned. Rotate by writing a new value. - `auth.oauth2` (object · nullable, optional): With `mode: oauth2_client_credentials`: your token endpoint (`token_url`, HTTPS), `client_id`, `client_secret` (write-only; responses show `client_secret_set`), optional `scope` and `audience`, and `auth_style`. null for any other mode. - `auth.oauth2.token_url` (string · uri, optional): HTTPS only. - `auth.oauth2.client_id` (string, optional) - `auth.oauth2.client_secret` (string, optional): Never returned. - `auth.oauth2.scope` (string · nullable, optional) - `auth.oauth2.audience` (string · nullable, optional) - `auth.oauth2.auth_style` (string, optional): One of: `client_secret_basic`, `client_secret_post`. Default: `"client_secret_basic"`. - `delivery_sources` (object · nullable, optional): Omitted or null = every delivery (the default; what every endpoint had before). ## Example: Add an endpoint for delivery events ```bash curl -X POST "https://api.parasend.com/v1/webhook_endpoints" \ -H "Authorization: Bearer $PARASEND_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "url": "https://hooks.fyrirtaeki.example/parasend", "description": "Case system", "event_types": [ "delivery.notified", "delivery.confirmed", "delivery.evidence_sealed" ] }' ``` ```js const res = await fetch('https://api.parasend.com/v1/webhook_endpoints', { method: 'POST', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, 'Idempotency-Key': crypto.randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ url: 'https://hooks.fyrirtaeki.example/parasend', description: 'Case system', event_types: ['delivery.notified', 'delivery.confirmed', 'delivery.evidence_sealed'], }), }); const data = await res.json(); ``` ## Response 201 Created (includes `secret`, shown only now). ```json { "id": "whk_01M6R27T4KCSN7M0CHFGVH7JRA", "object": "webhook_endpoint", "url": "https://hooks.fyrirtaeki.example/parasend", "event_types": [ "delivery.notified", "delivery.confirmed", "delivery.evidence_sealed" ], "description": "Case system", "secret": "whsec_ORQVKTHUQmNP3FrN6SNpPik8rEBsOLrSX63VnJQeaPM", "auth": { "mode": "none", "static_bearer": null, "oauth2": null }, "delivery_sources": null, "status": "enabled", "livemode": false, "exhausted_events": 0, "last_exhausted_at": null, "retrying_events": 0, "last_attempt": null, "created_by": { "user_id": "usr_01M6R27SYD4B50414S0BZ4KBKR", "name": "Jóna Jónsdóttir", "via": "api_key" }, "previous_secret_expires_at": null, "created": "2026-11-06T09:15:05.746Z" } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # List webhook endpoints `GET https://api.parasend.com/v1/webhook_endpoints` Source: https://parasend.com/docs/api/webhooks/list Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Lists your webhook endpoints in the key's mode. ## Query parameters - `limit` (integer, optional): Default: `25`. - `cursor` (string, optional) ## Example: List webhook endpoints ```bash curl "https://api.parasend.com/v1/webhook_endpoints" \ -H "Authorization: Bearer $PARASEND_KEY" ``` ```js const res = await fetch('https://api.parasend.com/v1/webhook_endpoints', { method: 'GET', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, }, }); const data = await res.json(); ``` ## Response 200 Paginated list. ```json { "data": [ { "id": "whk_01M6R27T4KCSN7M0CHFGVH7JRA", "object": "webhook_endpoint", "url": "https://hooks.fyrirtaeki.example/parasend", "event_types": [ "delivery.notified", "delivery.confirmed", "delivery.evidence_sealed" ], "description": "Case system", "secret": null, "auth": { "mode": "none", "static_bearer": null, "oauth2": null }, "delivery_sources": null, "status": "enabled", "livemode": false, "exhausted_events": 0, "last_exhausted_at": null, "retrying_events": 0, "last_attempt": null, "created_by": { "user_id": "usr_01M6R27SYD4B50414S0BZ4KBKR", "name": "Jóna Jónsdóttir", "via": "api_key" }, "previous_secret_expires_at": null, "created": "2026-11-06T09:15:05.746Z" } ], "has_more": false, "next_cursor": null } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Retrieve a webhook endpoint `GET https://api.parasend.com/v1/webhook_endpoints/{id}` Source: https://parasend.com/docs/api/webhooks/retrieve Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Retrieves a webhook endpoint. The signing secret is never shown again after the endpoint is created or its secret rolled. ## Path parameters - `id` (string, required): The webhook endpoint's ID, starting `whk_`. ## Example: Retrieve a webhook endpoint ```bash curl "https://api.parasend.com/v1/webhook_endpoints/whk_01M6R27T4KCSN7M0CHFGVH7JRA" \ -H "Authorization: Bearer $PARASEND_KEY" ``` ```js const res = await fetch('https://api.parasend.com/v1/webhook_endpoints/whk_01M6R27T4KCSN7M0CHFGVH7JRA', { method: 'GET', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, }, }); const data = await res.json(); ``` ## Response 200 The endpoint (secret never included after creation). ```json { "id": "whk_01M6R27T4KCSN7M0CHFGVH7JRA", "object": "webhook_endpoint", "url": "https://hooks.fyrirtaeki.example/parasend", "event_types": [ "delivery.notified", "delivery.confirmed", "delivery.evidence_sealed" ], "description": "Case system", "secret": null, "auth": { "mode": "none", "static_bearer": null, "oauth2": null }, "delivery_sources": null, "status": "enabled", "livemode": false, "exhausted_events": 0, "last_exhausted_at": null, "retrying_events": 0, "last_attempt": null, "created_by": { "user_id": "usr_01M6R27SYD4B50414S0BZ4KBKR", "name": "Jóna Jónsdóttir", "via": "api_key" }, "previous_secret_expires_at": null, "created": "2026-11-06T09:15:05.746Z" } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Update a webhook endpoint `PATCH https://api.parasend.com/v1/webhook_endpoints/{id}` Source: https://parasend.com/docs/api/webhooks/update Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Changes a webhook endpoint: its URL, the event types it gets, its authentication or its senders. ## Path parameters - `id` (string, required): The webhook endpoint's ID, starting `whk_`. ## Body (application/json) - `url` (string, optional): HTTPS on port 443, public host (A). - `event_types` (array of string, optional): Empty = every type, including types added later. One of: `delivery.created`, `delivery.rejected`, `delivery.notified`, `delivery.opened`, `delivery.email_opened`, `delivery.auth_failed`, `delivery.confirmed`, `delivery.downloaded`, `delivery.delivered`, `delivery.reminded`, `delivery.bounced`, `delivery.expired`, `delivery.revoked`, `delivery.consignment_failed`, `delivery.evidence_sealed`, `delivery.evidence_timestamp_deferred`, `delivery.evidence_timestamp_recovered`, `delivery.paper_pending_approval`, `delivery.paper_dispatched`, `delivery.paper_failed`, `document.previewed`, `batch.finalized`, `batch.completed`, `lookup.completed`, `consent.granted`, `consent.withdrawn`, `invitation.sent`, `link.opened`, `otp.sent`, `otp.verified`, `otp.locked`, `secret.attached`, `secret.available`, `secret.revealed`, `secret.copied`, `secret.expired`, `secret.exhausted`, `secret.revoked`, `request.sent`, `request.opened`, `request.fulfilled`, `request.expired`, `request.closed`, `request.reopened`, `deletion.scheduled`, `recipient_domain.verified`, `recipient_domain.degraded`, `recipient_domain.failed`, `member.suspended`, `member.restored`, `member.removed`, `batch.cancelled`, `batch.policy_changed`, `delivery.policy_changed`, `email_sender.verified`, `email_sender.degraded`, `email_sender.failed`, `email_sender.restored`. - `description` (string · nullable, optional): Your own note about the endpoint, up to 500 characters; null clears it. - `auth` (object, optional): How Parasend authenticates to your endpoint, in addition to the HMAC signature, which is always sent. Secrets are write-only: accepted on create and update, never returned. The endpoint shows `mode`, the settings that are not secret, and whether a secret is set. - `auth.mode` (string, required): none = HMAC only. static_bearer = an `Authorization` header carrying `Bearer ` with a customer-supplied token. oauth2_client_credentials = a token obtained from the customer's IdP by the client-credentials grant, cached until expiry, sent as the bearer. One of: `none`, `static_bearer`, `oauth2_client_credentials`. Default: `"none"`. - `auth.static_bearer` (object · nullable, optional): With `mode: static_bearer`: the `token` Parasend sends as `Authorization: Bearer `, at most 4,096 characters (write-only; responses show `token_set`). null for any other mode. - `auth.static_bearer.token` (string, optional): Never returned. Rotate by writing a new value. - `auth.oauth2` (object · nullable, optional): With `mode: oauth2_client_credentials`: your token endpoint (`token_url`, HTTPS), `client_id`, `client_secret` (write-only; responses show `client_secret_set`), optional `scope` and `audience`, and `auth_style`. null for any other mode. - `auth.oauth2.token_url` (string · uri, optional): HTTPS only. - `auth.oauth2.client_id` (string, optional) - `auth.oauth2.client_secret` (string, optional): Never returned. - `auth.oauth2.scope` (string · nullable, optional) - `auth.oauth2.audience` (string · nullable, optional) - `auth.oauth2.auth_style` (string, optional): One of: `client_secret_basic`, `client_secret_post`. Default: `"client_secret_basic"`. - `status` (string, optional): disabled pauses the endpoint: new events are not queued for it and pending retries are cancelled when they come due. One of: `enabled`, `disabled`. - `delivery_sources` (object · nullable, optional): null: every delivery's events. A change applies to events queued from then on; retries already queued go ahead. ## Example: Change the event types ```bash curl -X PATCH "https://api.parasend.com/v1/webhook_endpoints/whk_01M6R27T4KCSN7M0CHFGVH7JRA" \ -H "Authorization: Bearer $PARASEND_KEY" \ -H "Content-Type: application/json" \ -d '{ "event_types": [ "delivery.confirmed", "delivery.bounced", "delivery.expired" ] }' ``` ```js const res = await fetch('https://api.parasend.com/v1/webhook_endpoints/whk_01M6R27T4KCSN7M0CHFGVH7JRA', { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ event_types: ['delivery.confirmed', 'delivery.bounced', 'delivery.expired'], }), }); const data = await res.json(); ``` ## Response 200 Updated. ```json { "id": "whk_01M6R27T4KCSN7M0CHFGVH7JRA", "object": "webhook_endpoint", "url": "https://hooks.fyrirtaeki.example/parasend", "event_types": [ "delivery.confirmed", "delivery.bounced", "delivery.expired" ], "description": "Case system", "secret": null, "auth": { "mode": "none", "static_bearer": null, "oauth2": null }, "delivery_sources": null, "status": "enabled", "livemode": false, "exhausted_events": 0, "last_exhausted_at": null, "retrying_events": 0, "last_attempt": null, "created_by": { "user_id": "usr_01M6R27SYD4B50414S0BZ4KBKR", "name": "Jóna Jónsdóttir", "via": "api_key" }, "previous_secret_expires_at": null, "created": "2026-11-06T09:15:05.746Z" } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Delete a webhook endpoint `DELETE https://api.parasend.com/v1/webhook_endpoints/{id}` Source: https://parasend.com/docs/api/webhooks/delete Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Deletes a webhook endpoint. Events queued for it are cancelled. ## Path parameters - `id` (string, required): The webhook endpoint's ID, starting `whk_`. ## Example: Delete a webhook endpoint ```bash curl -X DELETE "https://api.parasend.com/v1/webhook_endpoints/whk_01M6R27T4KCSN7M0CHFGVH7JRA" \ -H "Authorization: Bearer $PARASEND_KEY" ``` ```js const res = await fetch('https://api.parasend.com/v1/webhook_endpoints/whk_01M6R27T4KCSN7M0CHFGVH7JRA', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, }, }); const data = await res.json(); ``` ## Response 204 Deleted. Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Send a test event `POST https://api.parasend.com/v1/webhook_endpoints/{id}/test` Source: https://parasend.com/docs/api/webhooks/test Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Send an `Idempotency-Key` header: a retry with the same key returns the first answer. Sends one signed sample event of `event_type` (any type; `delivery.created` by default), marked with the header `Parasend-Test: true`, and waits up to 10 seconds for the answer. Returns the attempt, the event sent, and the first 512 bytes of your endpoint's response as text (`response_excerpt`), which is not stored. The sample event is not kept: it cannot be replayed and never appears in GET /events. The attempt is listed in the endpoint's attempts with `test: true`. ## Path parameters - `id` (string, required): The webhook endpoint's ID, starting `whk_`. ## Body (application/json) - `event_type` (string, optional): Every event type. `delivery.*` events follow a delivery from creation to its end, `batch.*` a batch, and `member.*`, `email_sender.*` and `recipient_domain.*` your account. `delivery.email_opened` is a hint (the e-mail client loaded an image), never proof of receipt; `delivery.confirmed` is the recipient confirming receipt. `delivery.auth_failed` carries a `reason`, such as `personal_code_mismatch` or `id_document_mismatch`. The `secret.*`, `request.*` and `lookup.*` events belong to features that are not available yet. New types may be added: ignore types you do not know. One of: `delivery.created`, `delivery.rejected`, `delivery.notified`, `delivery.opened`, `delivery.email_opened`, `delivery.auth_failed`, `delivery.confirmed`, `delivery.downloaded`, `delivery.delivered`, `delivery.reminded`, `delivery.bounced`, `delivery.expired`, `delivery.revoked`, `delivery.consignment_failed`, `delivery.evidence_sealed`, `delivery.evidence_timestamp_deferred`, `delivery.evidence_timestamp_recovered`, `delivery.paper_pending_approval`, `delivery.paper_dispatched`, `delivery.paper_failed`, `document.previewed`, `batch.finalized`, `batch.completed`, `lookup.completed`, `consent.granted`, `consent.withdrawn`, `invitation.sent`, `link.opened`, `otp.sent`, `otp.verified`, `otp.locked`, `secret.attached`, `secret.available`, `secret.revealed`, `secret.copied`, `secret.expired`, `secret.exhausted`, `secret.revoked`, `request.sent`, `request.opened`, `request.fulfilled`, `request.expired`, `request.closed`, `request.reopened`, `deletion.scheduled`, `recipient_domain.verified`, `recipient_domain.degraded`, `recipient_domain.failed`, `member.suspended`, `member.restored`, `member.removed`, `batch.cancelled`, `batch.policy_changed`, `delivery.policy_changed`, `email_sender.verified`, `email_sender.degraded`, `email_sender.failed`, `email_sender.restored`. ## Example: Send a test delivery.confirmed ```bash curl -X POST "https://api.parasend.com/v1/webhook_endpoints/whk_01M6R27T4KCSN7M0CHFGVH7JRA/test" \ -H "Authorization: Bearer $PARASEND_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "event_type": "delivery.confirmed" }' ``` ```js const res = await fetch('https://api.parasend.com/v1/webhook_endpoints/whk_01M6R27T4KCSN7M0CHFGVH7JRA/test', { method: 'POST', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, 'Idempotency-Key': crypto.randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ event_type: 'delivery.confirmed', }), }); const data = await res.json(); ``` ## Response 200 The test attempt. ```json { "id": "wha_01M6R27T5AHW8DQ6B11PRVD7NA", "event_id": "evt_01M6R27T58P38M9BDCG5V1STPB", "event_type": "delivery.confirmed", "attempt_number": 1, "ok": true, "status_code": 200, "latency_ms": 1, "replay": false, "test": true, "failure_stage": null, "error": null, "created": "2026-11-06T09:15:05.771Z", "next_retry_at": null, "exhausted": false, "event": { "id": "evt_01M6R27T58P38M9BDCG5V1STPB", "object": "event", "type": "delivery.confirmed", "created": "2026-11-06T09:15:05.768Z", "resource": { "type": "delivery", "id": "dlv_00000000000000000000000000" }, "batch_id": null, "reason": null, "data": null }, "response_excerpt": "ok" } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Replay events `POST https://api.parasend.com/v1/webhook_endpoints/{id}/replay` Source: https://parasend.com/docs/api/webhooks/replay Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Send an `Idempotency-Key` header: a retry with the same key returns the first answer. Sends events to the endpoint again, signed with a fresh timestamp. An unknown `event_id` is 404 `event_not_found`. ## Path parameters - `id` (string, required): The webhook endpoint's ID, starting `whk_`. ## Body (application/json) - `event_id` (string, required): The event's ID, starting `evt_`. ## Example: Replay one event ```bash curl -X POST "https://api.parasend.com/v1/webhook_endpoints/whk_01M6R27T4KCSN7M0CHFGVH7JRA/replay" \ -H "Authorization: Bearer $PARASEND_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "event_id": "evt_01M6R27T08JTMBJ4B6GQP90838" }' ``` ```js const res = await fetch('https://api.parasend.com/v1/webhook_endpoints/whk_01M6R27T4KCSN7M0CHFGVH7JRA/replay', { method: 'POST', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, 'Idempotency-Key': crypto.randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ event_id: 'evt_01M6R27T08JTMBJ4B6GQP90838', }), }); const data = await res.json(); ``` ## Response 200 Replay attempt result. ```json { "id": "wha_01M6R27T5FDNZNP8H9PR7YDM94", "event_id": "evt_01M6R27T08JTMBJ4B6GQP90838", "event_type": "delivery.created", "attempt_number": 1, "ok": true, "status_code": 200, "latency_ms": 0, "replay": true, "test": false, "failure_stage": null, "error": null, "created": "2026-11-06T09:15:05.775Z", "next_retry_at": null, "exhausted": false } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # List an endpoint's attempts `GET https://api.parasend.com/v1/webhook_endpoints/{id}/attempts` Source: https://parasend.com/docs/api/webhooks/attempts Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Lists every attempt to send events to the endpoint. Your endpoint should de-duplicate by event ID and accept events out of order. ## Path parameters - `id` (string, required): The webhook endpoint's ID, starting `whk_`. ## Query parameters - `limit` (integer, optional): Default: `25`. - `cursor` (string, optional) - `event_id` (string, optional): evt_… — only this event's attempts (all tries and replays). - `outcome` (string, optional): succeeded = a 2xx answer; failed = anything else; exhausted = the last attempt of an event that ran out of retries and has not been replayed successfully since. One of: `succeeded`, `failed`, `exhausted`. ## Example: List an endpoint's attempts ```bash curl "https://api.parasend.com/v1/webhook_endpoints/whk_01M6R27T4KCSN7M0CHFGVH7JRA/attempts" \ -H "Authorization: Bearer $PARASEND_KEY" ``` ```js const res = await fetch('https://api.parasend.com/v1/webhook_endpoints/whk_01M6R27T4KCSN7M0CHFGVH7JRA/attempts', { method: 'GET', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, }, }); const data = await res.json(); ``` ## Response 200 Paginated attempts. ```json { "data": [ { "id": "wha_01M6R27T5FDNZNP8H9PR7YDM94", "event_id": "evt_01M6R27T08JTMBJ4B6GQP90838", "event_type": "delivery.created", "attempt_number": 1, "ok": true, "status_code": 200, "latency_ms": 0, "replay": true, "test": false, "failure_stage": null, "error": null, "created": "2026-11-06T09:15:05.775Z", "next_retry_at": null, "exhausted": false }, { "id": "wha_01M6R27T5AHW8DQ6B11PRVD7NA", "event_id": "evt_01M6R27T58P38M9BDCG5V1STPB", "event_type": "delivery.confirmed", "attempt_number": 1, "ok": true, "status_code": 200, "latency_ms": 1, "replay": false, "test": true, "failure_stage": null, "error": null, "created": "2026-11-06T09:15:05.771Z", "next_retry_at": null, "exhausted": false } ], "has_more": false, "next_cursor": null } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Roll an endpoint's signing secret `POST https://api.parasend.com/v1/webhook_endpoints/{id}/roll_secret` Source: https://parasend.com/docs/api/webhooks/roll-secret Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Send an `Idempotency-Key` header: a retry with the same key returns the first answer. Creates a new signing secret and returns the endpoint with it in `secret` — the only time it is shown. The previous secret stays valid for 24 hours (`previous_secret_expires_at`): during that window every request carries two v1 values in Parasend-Signature, `t=,v1=,v1=`, so the receiver can switch without a gap. Rolling again inside the window drops the older previous secret at once (only one previous secret is kept). Audit webhook_endpoint.secret_rolled. ## Path parameters - `id` (string, required): The webhook endpoint's ID, starting `whk_`. ## Example: Roll an endpoint's signing secret ```bash curl -X POST "https://api.parasend.com/v1/webhook_endpoints/whk_01M6R27T4KCSN7M0CHFGVH7JRA/roll_secret" \ -H "Authorization: Bearer $PARASEND_KEY" \ -H "Idempotency-Key: $(uuidgen)" ``` ```js const res = await fetch('https://api.parasend.com/v1/webhook_endpoints/whk_01M6R27T4KCSN7M0CHFGVH7JRA/roll_secret', { method: 'POST', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, 'Idempotency-Key': crypto.randomUUID(), }, }); const data = await res.json(); ``` ## Response 200 The endpoint, with the new secret in `secret`. ```json { "id": "whk_01M6R27T4KCSN7M0CHFGVH7JRA", "object": "webhook_endpoint", "url": "https://hooks.fyrirtaeki.example/parasend", "event_types": [ "delivery.confirmed", "delivery.bounced", "delivery.expired" ], "description": "Case system", "secret": "whsec_rRFB9XFIxFTnLDYnjK9XUyGO61WwvXXRNq9Nfw5cBuQ", "auth": { "mode": "none", "static_bearer": null, "oauth2": null }, "delivery_sources": null, "status": "enabled", "livemode": false, "exhausted_events": 0, "last_exhausted_at": null, "retrying_events": 0, "last_attempt": { "created": "2026-11-06T09:15:05.775Z", "ok": true, "status_code": 200, "latency_ms": 0, "failure_stage": null, "error": null, "replay": true, "test": false }, "created_by": { "user_id": "usr_01M6R27SYD4B50414S0BZ4KBKR", "name": "Jóna Jónsdóttir", "via": "api_key" }, "previous_secret_expires_at": "2026-11-07T09:15:05.963Z", "created": "2026-11-06T09:15:05.746Z" } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Get the Delivery Certificate (PDF) `GET https://api.parasend.com/v1/deliveries/{id}/evidence/document` Source: https://parasend.com/docs/api/evidence/certificate Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). The Delivery Certificate (PDF): a readable rendering of the evidence record at `/evidence/record`, with the full event log and the QR code that leads to the record check at parasend.com/verify. When the recipient verified with an ID document check, it holds the document's photo page: handle it as sensitive personal data (it needs `evidence.read`). `sealed=false`, the default, is free and carries an *unsealed* banner: each evidence item is sealed, but the record as a whole was not sealed at your request. The first `sealed=true` request seals it (billable, once) and later ones return the same file. The address the notice went to is shown in full. ## Path parameters - `id` (string, required): The delivery's ID, starting `dlv_`. ## Query parameters - `sealed` (boolean, optional): true returns the sealed PDF, sealing it on the first such request (billable, once). false, the default, returns the rendering marked unsealed. Default: `false`. ## Example: Get the Delivery Certificate (PDF) ```bash curl "https://api.parasend.com/v1/deliveries/dlv_01M6R27T05512SCV3852TPM2XY/evidence/document" \ -H "Authorization: Bearer $PARASEND_KEY" ``` ```js const res = await fetch('https://api.parasend.com/v1/deliveries/dlv_01M6R27T05512SCV3852TPM2XY/evidence/document', { method: 'GET', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, }, }); const data = await res.json(); ``` ## Response 200 Evidence PDF (unsealed, or eSealed when sealed=true). Content type: application/pdf. Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Get the evidence record (XML) `GET https://api.parasend.com/v1/deliveries/{id}/evidence/record` Source: https://parasend.com/docs/api/evidence/record Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). The evidence record in XML: the authoritative, machine-readable form of the evidence (ETSI EN 319 522-3). The Delivery Certificate at `/evidence/document` is a rendering of it. One document per delivery: the root `ps:EvidenceRecord` in the namespace `urn:parasend:evidence:1` holds the delivery's metadata and one ETSI evidence item for every evidentiary event, in order. Each item is sealed when it is made. Parasend's own data is in non-critical `erds:Extensions`, so a reader that knows only the ETSI schema still reads complete, valid items. The envelope around the items is sealed on request: `sealed=true` seals it (billable, once, then kept); the default `sealed=false` returns the record marked `sealStatus="unsealed"`, at no cost. ## Path parameters - `id` (string, required): The delivery's ID, starting `dlv_`. ## Query parameters - `sealed` (boolean, optional): true returns the envelope-sealed record, sealing it on the first such request (billable, once). false, the default, returns the record marked unsealed. Default: `false`. ## Example: Get the evidence record (XML) ```bash curl "https://api.parasend.com/v1/deliveries/dlv_01M6R27T05512SCV3852TPM2XY/evidence/record" \ -H "Authorization: Bearer $PARASEND_KEY" ``` ```js const res = await fetch('https://api.parasend.com/v1/deliveries/dlv_01M6R27T05512SCV3852TPM2XY/evidence/record', { method: 'GET', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, }, }); const data = await res.json(); ``` ## Response 200 Evidence record XML (`ps:EvidenceRecord`; sealed or marked unsealed). Content type: application/xml. Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Get a recipient's verification photo `GET https://api.parasend.com/v1/deliveries/{id}/recipient_photos/{kind}` Source: https://parasend.com/docs/api/evidence/recipient-photo Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). The passport photo page or the selfie from the ID document check the recipient's verified name came from, kept with the recipient's consent. Needs the `evidence.read` permission. The `X-Parasend-Sha256` header carries the file's SHA-256. ## Path parameters - `id` (string, required): The delivery's ID. - `kind` (string, required): One of: `document`, `selfie`. ## Example: Get a recipient's verification photo ```bash curl "https://api.parasend.com/v1/deliveries/dlv_01M6R27T05512SCV3852TPM2XY/recipient_photos/document" \ -H "Authorization: Bearer $PARASEND_KEY" ``` ```js const res = await fetch('https://api.parasend.com/v1/deliveries/dlv_01M6R27T05512SCV3852TPM2XY/recipient_photos/document', { method: 'GET', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, }, }); const data = await res.json(); ``` ## Response 200 The JPEG. Content type: image/jpeg. Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Check a delivery record by its evidence code `POST https://api.parasend.com/v1/public/verify` Source: https://parasend.com/docs/api/verify/check No API key is needed. Send an `Idempotency-Key` header: a retry with the same key returns the first answer. The check behind `parasend.com/verify`: anyone holding a Delivery Certificate can confirm that Parasend issued the record, that its seals are intact by Parasend's own check, and what it records. No key is needed; the code is the only input and is sent in the body, never in the URL. An unknown code and a malformed one get the same answer. Limited per caller to 120 lookups an hour and 20 misses in 10 minutes. ## Body (application/json) - `code` (string, required): The evidence code printed under the QR on the certificate. ## Example: Check a delivery record by its evidence code ```bash curl -X POST "https://api.parasend.com/v1/public/verify" \ -H "Idempotency-Key: $(uuidgen)" ``` ```js const res = await fetch('https://api.parasend.com/v1/public/verify', { method: 'POST', headers: { 'Idempotency-Key': crypto.randomUUID(), }, }); const data = await res.json(); ``` ## Response 200 The record, "not ready" while its latest event is being sealed, or "not found". Content type: application/json. Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Create a follow-up policy `POST https://api.parasend.com/v1/reminder_policies` Source: https://parasend.com/docs/api/follow-up-policies/create Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Send an `Idempotency-Key` header: a retry with the same key returns the first answer. Creates a follow-up policy: the reminders a delivery sends while the recipient has not confirmed receipt, step by step. ## Body (application/json) - `name` (string, required): Your name for the policy: 1–100 characters, without control characters; leading and trailing spaces are trimmed. - `steps` (array of object, required) - `steps[].after_days` (integer, required): Days after the first notice when the step is due: a whole number from 1 to 365, never smaller than the step before it. The reminder goes within the send window and at least 24 hours after the delivery's last message. - `steps[].channel` (string, required): How the step's reminder goes: `email` or `sms`. A step whose channel the recipient has no contact for is skipped. `alternate_email` and `paper` are refused with `policy_step_invalid` until they are available. One of: `email`, `sms`, `alternate_email`, `paper`. - `steps[].conditions` (array of string, optional): What must all hold for the step to go, else it is skipped: `not_delivered` (always included, listed or not); `email_not_opened`, no e-mail to the recipient has been opened (a hint, not proof); `email_bounced`, an e-mail to the recipient bounced or failed. One of: `not_delivered`, `email_not_opened`, `email_bounced`. - `steps[].requires_approval` (boolean · nullable, optional): Paper defaults to approval-required. - `steps[].message` (object, optional): The wording a follow-up step's reminder sends. Left out: Parasend's standard reminder. `preset` picks Parasend's wording; `custom` holds your own, by language (Plan 1–3 and Enterprise). A field or language left out comes from the preset. - `steps[].message.preset` (string, optional): `standard` is the usual reminder; `firmer` and `final` are Parasend's stronger wordings. One of: `standard`, `firmer`, `final`. Default: `"standard"`. - `steps[].message.custom` (object, optional): Your own wording, by language (`is`, `en`); a field or a language left out comes from `preset`. Needs Plan 1–3 or Enterprise; otherwise refused with `theme_knob_plan_gated`. - `steps[].message.custom.is` (object, optional): Plain text. An e-mail step takes `subject`, `heading` and `text`; an SMS step `text` only. Variables: {{recipient.name}}, {{recipient.first_name}}, {{sender.name}}, {{delivery.title}}, {{delivery.sent_on}}, {{delivery.expires_on}}, {{files.count}}; an SMS only the two dates. No links, e-mail addresses or phone numbers. An e-mail may hold the gender word {{recipient.gender: male | female | other}}. - `steps[].message.custom.en` (object, optional): Plain text. An e-mail step takes `subject`, `heading` and `text`; an SMS step `text` only. Variables: {{recipient.name}}, {{recipient.first_name}}, {{sender.name}}, {{delivery.title}}, {{delivery.sent_on}}, {{delivery.expires_on}}, {{files.count}}; an SMS only the two dates. No links, e-mail addresses or phone numbers. An e-mail may hold the gender word {{recipient.gender: male | female | other}}. - `steps[].message.source` (object, optional): The saved wording and version this step's wording was loaded from. A record only: the step keeps its own copy. Not on a saved wording's own message. - `steps[].message.source.wording` (string, required) - `steps[].message.source.version` (integer, required) - `stop_on` (string, optional): When the follow-ups stop: `confirmed`, the only value, once the recipient confirms receipt. - `is_account_default` (boolean, optional): The organisation's pre-fill for new batches and deliveries; never applied by itself. Needs organization.update. Default: `false`. ## Example: Two e-mail reminders, then SMS ```bash curl -X POST "https://api.parasend.com/v1/reminder_policies" \ -H "Authorization: Bearer $PARASEND_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "name": "Payment reminders", "steps": [ { "after_days": 3, "channel": "email", "conditions": [ "not_delivered" ] }, { "after_days": 7, "channel": "email", "conditions": [ "not_delivered" ], "message": { "preset": "firmer" } }, { "after_days": 10, "channel": "sms", "conditions": [ "not_delivered" ] } ] }' ``` ```js const res = await fetch('https://api.parasend.com/v1/reminder_policies', { method: 'POST', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, 'Idempotency-Key': crypto.randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ name: 'Payment reminders', steps: [ { after_days: 3, channel: 'email', conditions: ['not_delivered'], }, { after_days: 7, channel: 'email', conditions: ['not_delivered'], message: { preset: 'firmer', }, }, { after_days: 10, channel: 'sms', conditions: ['not_delivered'], }, ], }), }); const data = await res.json(); ``` ## Response 201 Created. ```json { "id": "rpol_01M6R27T5NK3830GCN9QPS9M95", "object": "reminder_policy", "name": "Payment reminders", "system": false, "version": 1, "version_id": "rpv_01M6R27T5NB7JTSEZ0XV7BWETG", "steps": [ { "channel": "email", "after_days": 3, "conditions": [ "not_delivered" ], "available": true }, { "channel": "email", "message": { "preset": "firmer" }, "after_days": 7, "conditions": [ "not_delivered" ], "available": true }, { "channel": "sms", "after_days": 10, "conditions": [ "not_delivered" ], "available": true } ], "stop_on": "confirmed", "customized": false, "is_account_default": false, "in_use_by_batches": 0, "in_use_by_deliveries": 0, "created_at": "2026-11-06T09:15:05.781Z", "updated_at": "2026-11-06T09:15:05.781Z", "archived_at": null } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # List follow-up policies `GET https://api.parasend.com/v1/reminder_policies` Source: https://parasend.com/docs/api/follow-up-policies/list Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Lists your follow-up policies and Parasend's own (`rpol_default`, `rpol_debt_collection`, `rpol_none`). ## Query parameters - `limit` (integer, optional): Default: `25`. - `cursor` (string, optional) ## Example: List follow-up policies ```bash curl "https://api.parasend.com/v1/reminder_policies" \ -H "Authorization: Bearer $PARASEND_KEY" ``` ```js const res = await fetch('https://api.parasend.com/v1/reminder_policies', { method: 'GET', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, }, }); const data = await res.json(); ``` ## Response 200 The account's follow-up policies, Parasend's own included. ```json { "data": [ { "id": "rpol_01M6R27T5NK3830GCN9QPS9M95", "object": "reminder_policy", "name": "Payment reminders", "system": false, "version": 1, "version_id": "rpv_01M6R27T5NB7JTSEZ0XV7BWETG", "steps": [ { "channel": "email", "after_days": 3, "conditions": [ "not_delivered" ], "available": true }, { "channel": "email", "message": { "preset": "firmer" }, "after_days": 7, "conditions": [ "not_delivered" ], "available": true }, { "channel": "sms", "after_days": 10, "conditions": [ "not_delivered" ], "available": true } ], "stop_on": "confirmed", "customized": false, "is_account_default": false, "in_use_by_batches": 0, "in_use_by_deliveries": 0, "created_at": "2026-11-06T09:15:05.781Z", "updated_at": "2026-11-06T09:15:05.781Z", "archived_at": null }, { "id": "rpol_none", "object": "reminder_policy", "name": "No policy", "system": true, "version": 1, "version_id": "rpv_none_v1", "steps": [], "stop_on": "confirmed", "customized": false, "is_account_default": false, "in_use_by_batches": 0, "in_use_by_deliveries": 0, "created_at": "2026-11-06T09:15:05.549Z", "updated_at": "2026-11-06T09:15:05.549Z", "archived_at": null }, { "id": "rpol_debt_collection", "object": "reminder_policy", "name": "Debt collection", "system": true, "version": 1, "version_id": "rpv_debt_collection_v1", "steps": [ { "channel": "email", "after_days": 2, "conditions": [ "not_delivered" ], "available": true }, { "channel": "sms", "after_days": 5, "conditions": [ "not_delivered" ], "available": true }, { "channel": "paper", "after_days": 10, "conditions": [ "not_delivered" ], "requires_approval": true, "available": false } ], "stop_on": "confirmed", "customized": false, "is_account_default": false, "in_use_by_batches": 0, "in_use_by_deliveries": 0, "created_at": "2026-11-06T09:15:05.547Z", "updated_at": "2026-11-06T09:15:05.548Z", "archived_at": null }, { "id": "rpol_default", "object": "reminder_policy", "name": "Default", "system": true, "version": 1, "version_id": "rpv_default_v1", "steps": [ { "channel": "email", "after_days": 3, "conditions": [ "not_delivered" ], "available": true }, { "channel": "email", "after_days": 7, "conditions": [ "not_delivered" ], "available": true } ], "stop_on": "confirmed", "customized": false, "is_account_default": false, "in_use_by_batches": 0, "in_use_by_deliveries": 0, "created_at": "2026-11-06T09:15:05.537Z", "updated_at": "2026-11-06T09:15:05.540Z", "archived_at": null } ], "has_more": false, "next_cursor": null } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Retrieve a follow-up policy `GET https://api.parasend.com/v1/reminder_policies/{id}` Source: https://parasend.com/docs/api/follow-up-policies/retrieve Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Retrieves a follow-up policy and its steps. ## Path parameters - `id` (string, required): The policy's ID. ## Example: Retrieve a follow-up policy ```bash curl "https://api.parasend.com/v1/reminder_policies/rpol_01M6R27T5NK3830GCN9QPS9M95" \ -H "Authorization: Bearer $PARASEND_KEY" ``` ```js const res = await fetch('https://api.parasend.com/v1/reminder_policies/rpol_01M6R27T5NK3830GCN9QPS9M95', { method: 'GET', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, }, }); const data = await res.json(); ``` ## Response 200 The policy. ```json { "id": "rpol_01M6R27T5NK3830GCN9QPS9M95", "object": "reminder_policy", "name": "Payment reminders", "system": false, "version": 1, "version_id": "rpv_01M6R27T5NB7JTSEZ0XV7BWETG", "steps": [ { "channel": "email", "after_days": 3, "conditions": [ "not_delivered" ], "available": true }, { "channel": "email", "message": { "preset": "firmer" }, "after_days": 7, "conditions": [ "not_delivered" ], "available": true }, { "channel": "sms", "after_days": 10, "conditions": [ "not_delivered" ], "available": true } ], "stop_on": "confirmed", "customized": false, "is_account_default": false, "in_use_by_batches": 0, "in_use_by_deliveries": 0, "created_at": "2026-11-06T09:15:05.781Z", "updated_at": "2026-11-06T09:15:05.781Z", "archived_at": null } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Update a follow-up policy `PATCH https://api.parasend.com/v1/reminder_policies/{id}` Source: https://parasend.com/docs/api/follow-up-policies/update Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Changes a follow-up policy. An archived policy answers 409 `policy_archived`. Changing the steps of `rpol_default` or `rpol_debt_collection` makes your organisation's own version of it, under the same ID, and `customized` becomes true; their names stay Parasend's. `rpol_none` takes no steps (403 `system_policy_immutable`). Any of Parasend's policies can be made your account default. ## Path parameters - `id` (string, required): The policy's ID. ## Body (application/json) - `name` (string, required): Your name for the policy: 1–100 characters, without control characters; leading and trailing spaces are trimmed. - `steps` (array of object, required) - `steps[].after_days` (integer, required): Days after the first notice when the step is due: a whole number from 1 to 365, never smaller than the step before it. The reminder goes within the send window and at least 24 hours after the delivery's last message. - `steps[].channel` (string, required): How the step's reminder goes: `email` or `sms`. A step whose channel the recipient has no contact for is skipped. `alternate_email` and `paper` are refused with `policy_step_invalid` until they are available. One of: `email`, `sms`, `alternate_email`, `paper`. - `steps[].conditions` (array of string, optional): What must all hold for the step to go, else it is skipped: `not_delivered` (always included, listed or not); `email_not_opened`, no e-mail to the recipient has been opened (a hint, not proof); `email_bounced`, an e-mail to the recipient bounced or failed. One of: `not_delivered`, `email_not_opened`, `email_bounced`. - `steps[].requires_approval` (boolean · nullable, optional): Paper defaults to approval-required. - `steps[].message` (object, optional): The wording a follow-up step's reminder sends. Left out: Parasend's standard reminder. `preset` picks Parasend's wording; `custom` holds your own, by language (Plan 1–3 and Enterprise). A field or language left out comes from the preset. - `steps[].message.preset` (string, optional): `standard` is the usual reminder; `firmer` and `final` are Parasend's stronger wordings. One of: `standard`, `firmer`, `final`. Default: `"standard"`. - `steps[].message.custom` (object, optional): Your own wording, by language (`is`, `en`); a field or a language left out comes from `preset`. Needs Plan 1–3 or Enterprise; otherwise refused with `theme_knob_plan_gated`. - `steps[].message.custom.is` (object, optional): Plain text. An e-mail step takes `subject`, `heading` and `text`; an SMS step `text` only. Variables: {{recipient.name}}, {{recipient.first_name}}, {{sender.name}}, {{delivery.title}}, {{delivery.sent_on}}, {{delivery.expires_on}}, {{files.count}}; an SMS only the two dates. No links, e-mail addresses or phone numbers. An e-mail may hold the gender word {{recipient.gender: male | female | other}}. - `steps[].message.custom.en` (object, optional): Plain text. An e-mail step takes `subject`, `heading` and `text`; an SMS step `text` only. Variables: {{recipient.name}}, {{recipient.first_name}}, {{sender.name}}, {{delivery.title}}, {{delivery.sent_on}}, {{delivery.expires_on}}, {{files.count}}; an SMS only the two dates. No links, e-mail addresses or phone numbers. An e-mail may hold the gender word {{recipient.gender: male | female | other}}. - `steps[].message.source` (object, optional): The saved wording and version this step's wording was loaded from. A record only: the step keeps its own copy. Not on a saved wording's own message. - `steps[].message.source.wording` (string, required) - `steps[].message.source.version` (integer, required) - `stop_on` (string, optional): When the follow-ups stop: `confirmed`, the only value, once the recipient confirms receipt. - `is_account_default` (boolean, optional): The organisation's pre-fill for new batches and deliveries; never applied by itself. Needs organization.update. Default: `false`. ## Example: Wait longer before the SMS ```bash curl -X PATCH "https://api.parasend.com/v1/reminder_policies/rpol_01M6R27T5NK3830GCN9QPS9M95" \ -H "Authorization: Bearer $PARASEND_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Payment reminders", "steps": [ { "after_days": 3, "channel": "email", "conditions": [ "not_delivered" ] }, { "after_days": 7, "channel": "email", "conditions": [ "not_delivered" ], "message": { "preset": "firmer" } }, { "after_days": 14, "channel": "sms", "conditions": [ "not_delivered" ] } ] }' ``` ```js const res = await fetch('https://api.parasend.com/v1/reminder_policies/rpol_01M6R27T5NK3830GCN9QPS9M95', { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ name: 'Payment reminders', steps: [ { after_days: 3, channel: 'email', conditions: ['not_delivered'], }, { after_days: 7, channel: 'email', conditions: ['not_delivered'], message: { preset: 'firmer', }, }, { after_days: 14, channel: 'sms', conditions: ['not_delivered'], }, ], }), }); const data = await res.json(); ``` ## Response 200 Updated. ```json { "id": "rpol_01M6R27T5NK3830GCN9QPS9M95", "object": "reminder_policy", "name": "Payment reminders", "system": false, "version": 2, "version_id": "rpv_01M6R27T60DW1D6WAYM11GBDER", "steps": [ { "channel": "email", "after_days": 3, "conditions": [ "not_delivered" ], "available": true }, { "channel": "email", "message": { "preset": "firmer" }, "after_days": 7, "conditions": [ "not_delivered" ], "available": true }, { "channel": "sms", "after_days": 14, "conditions": [ "not_delivered" ], "available": true } ], "stop_on": "confirmed", "customized": false, "is_account_default": false, "in_use_by_batches": 0, "in_use_by_deliveries": 0, "created_at": "2026-11-06T09:15:05.781Z", "updated_at": "2026-11-06T09:15:05.792Z", "archived_at": null } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Delete a follow-up policy `DELETE https://api.parasend.com/v1/reminder_policies/{id}` Source: https://parasend.com/docs/api/follow-up-policies/delete Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). While referenced by batches → 409 `policy_in_use` with `in_use_by_batches` count (portal shows the in-use lock). ## Path parameters - `id` (string, required): The policy's ID. ## Example: Delete a follow-up policy ```bash curl -X DELETE "https://api.parasend.com/v1/reminder_policies/rpol_01M6R27T5NK3830GCN9QPS9M95" \ -H "Authorization: Bearer $PARASEND_KEY" ``` ```js const res = await fetch('https://api.parasend.com/v1/reminder_policies/rpol_01M6R27T5NK3830GCN9QPS9M95', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, }, }); const data = await res.json(); ``` ## Response 204 Deleted. Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # List a follow-up policy's versions `GET https://api.parasend.com/v1/reminder_policies/{id}/versions` Source: https://parasend.com/docs/api/follow-up-policies/versions Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Every version of a policy, newest first. ## Path parameters - `id` (string, required): The policy's ID. ## Query parameters - `limit` (integer, optional): Default: `25`. - `cursor` (string, optional) ## Example: List a follow-up policy's versions ```bash curl "https://api.parasend.com/v1/reminder_policies/rpol_01M6R27T5NK3830GCN9QPS9M95/versions" \ -H "Authorization: Bearer $PARASEND_KEY" ``` ```js const res = await fetch('https://api.parasend.com/v1/reminder_policies/rpol_01M6R27T5NK3830GCN9QPS9M95/versions', { method: 'GET', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, }, }); const data = await res.json(); ``` ## Response 200 Paginated versions. ```json { "data": [ { "id": "rpv_01M6R27T60DW1D6WAYM11GBDER", "object": "reminder_policy_version", "policy_id": "rpol_01M6R27T5NK3830GCN9QPS9M95", "version": 2, "steps": [ { "channel": "email", "after_days": 3, "conditions": [ "not_delivered" ], "available": true }, { "channel": "email", "message": { "preset": "firmer" }, "after_days": 7, "conditions": [ "not_delivered" ], "available": true }, { "channel": "sms", "after_days": 14, "conditions": [ "not_delivered" ], "available": true } ], "stop_on": "confirmed", "created_at": "2026-11-06T09:15:05.792Z", "created_by": { "user_id": "usr_01M6R27SYD4B50414S0BZ4KBKR", "via": "api_key" } }, { "id": "rpv_01M6R27T5NB7JTSEZ0XV7BWETG", "object": "reminder_policy_version", "policy_id": "rpol_01M6R27T5NK3830GCN9QPS9M95", "version": 1, "steps": [ { "channel": "email", "after_days": 3, "conditions": [ "not_delivered" ], "available": true }, { "channel": "email", "message": { "preset": "firmer" }, "after_days": 7, "conditions": [ "not_delivered" ], "available": true }, { "channel": "sms", "after_days": 10, "conditions": [ "not_delivered" ], "available": true } ], "stop_on": "confirmed", "created_at": "2026-11-06T09:15:05.781Z", "created_by": { "user_id": "usr_01M6R27SYD4B50414S0BZ4KBKR", "via": "api_key" } } ], "has_more": false, "next_cursor": null } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Preview a follow-up step `POST https://api.parasend.com/v1/reminder_policies/preview` Source: https://parasend.com/docs/api/follow-up-policies/preview-step Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Send an `Idempotency-Key` header: a retry with the same key returns the first answer. The reminder the worker would send for this step, for a sample delivery, in the brand named (null for Parasend's own look, left out for the organisation's default). Checked as a saved step would be (param under step.). For an SMS step, also whether its own text keeps the reminder one SMS (3.5). Nothing is stored. ## Query parameters - `locale` (string, optional): One of: `is`, `en`. Default: `"is"`. ## Body (application/json) - `step` (object, required): One step of a follow-up policy: after `after_days` (a whole number of days, 1–365, never earlier than the step above), send a reminder by `email` or `sms`, with the wording in `message`. `alternate_email` and `paper` steps are refused (`policy_step_invalid`) until the contact lookup and paper mail are available. Responses add `available`: false for a step that cannot run yet (only `rpol_debt_collection`'s paper step). - `step.after_days` (integer, required): Days after the first notice when the step is due: a whole number from 1 to 365, never smaller than the step before it. The reminder goes within the send window and at least 24 hours after the delivery's last message. - `step.channel` (string, required): How the step's reminder goes: `email` or `sms`. A step whose channel the recipient has no contact for is skipped. `alternate_email` and `paper` are refused with `policy_step_invalid` until they are available. One of: `email`, `sms`, `alternate_email`, `paper`. - `step.conditions` (array of string, optional): What must all hold for the step to go, else it is skipped: `not_delivered` (always included, listed or not); `email_not_opened`, no e-mail to the recipient has been opened (a hint, not proof); `email_bounced`, an e-mail to the recipient bounced or failed. One of: `not_delivered`, `email_not_opened`, `email_bounced`. - `step.requires_approval` (boolean · nullable, optional): Paper defaults to approval-required. - `step.message` (object, optional): The wording a follow-up step's reminder sends. Left out: Parasend's standard reminder. `preset` picks Parasend's wording; `custom` holds your own, by language (Plan 1–3 and Enterprise). A field or language left out comes from the preset. - `step.message.preset` (string, optional): `standard` is the usual reminder; `firmer` and `final` are Parasend's stronger wordings. One of: `standard`, `firmer`, `final`. Default: `"standard"`. - `step.message.custom` (object, optional): Your own wording, by language (`is`, `en`); a field or a language left out comes from `preset`. Needs Plan 1–3 or Enterprise; otherwise refused with `theme_knob_plan_gated`. - `step.message.custom.is` (object, optional): Plain text. An e-mail step takes `subject`, `heading` and `text`; an SMS step `text` only. Variables: {{recipient.name}}, {{recipient.first_name}}, {{sender.name}}, {{delivery.title}}, {{delivery.sent_on}}, {{delivery.expires_on}}, {{files.count}}; an SMS only the two dates. No links, e-mail addresses or phone numbers. An e-mail may hold the gender word {{recipient.gender: male | female | other}}. - `step.message.custom.en` (object, optional): Plain text. An e-mail step takes `subject`, `heading` and `text`; an SMS step `text` only. Variables: {{recipient.name}}, {{recipient.first_name}}, {{sender.name}}, {{delivery.title}}, {{delivery.sent_on}}, {{delivery.expires_on}}, {{files.count}}; an SMS only the two dates. No links, e-mail addresses or phone numbers. An e-mail may hold the gender word {{recipient.gender: male | female | other}}. - `step.message.source` (object, optional): The saved wording and version this step's wording was loaded from. A record only: the step keeps its own copy. Not on a saved wording's own message. - `step.message.source.wording` (string, required) - `step.message.source.version` (integer, required) - `brand_profile_id` (string · nullable, optional) ## Example: Preview a firmer e-mail reminder ```bash curl -X POST "https://api.parasend.com/v1/reminder_policies/preview" \ -H "Authorization: Bearer $PARASEND_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "step": { "after_days": 7, "channel": "email", "message": { "preset": "firmer" } } }' ``` ```js const res = await fetch('https://api.parasend.com/v1/reminder_policies/preview', { method: 'POST', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, 'Idempotency-Key': crypto.randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ step: { after_days: 7, channel: 'email', message: { preset: 'firmer', }, }, }), }); const data = await res.json(); ``` ## Response 200 The rendered step. ```json { "object": "reminder_step_preview", "channel": "email", "locale": "is", "preset": "firmer", "template": { "id": "reminder.email", "version": 33 }, "email": { "from_name": "Sending frá: Fyrirtæki ehf.", "subject": "Áminning: Sýnishorn af sendingu bíður enn", "text": "Fyrirtæki ehf. bíður enn eftir staðfestingu þinni\n\nVið sendum þér skjal 2026-10-30 og höfum ekki enn fengið staðfestingu á að þú hafir móttekið það. Vinsamlegast staðfestu móttöku sem fyrst.\n\nSýnishorn af sendingu\n1 skjal · aðgengilegt til 13. nóvember 2026\n\nSkilaboð frá sendanda:\nÞetta er sýnishorn. Svona birtist sendingin viðtakanda.\n\nOpna sendinguna: https://parasend.com/d/synishorn\nÞú staðfestir móttöku með rafrænum skilríkjum. Það tekur innan við mínútu.\n\nEr þetta öruggt? Parasend biður þig aldrei um lykilorð eða kortaupplýsingar. Móttaka sendingarinnar er skráð.\n\nIn English: Fyrirtæki ehf. has sent you 1 document through Parasend, an electronic registered delivery service. Open the delivery with the button above and confirm receipt.\n\n—\nÞessi tilkynning er frá Parasend. Sendandi: Fyrirtæki ehf.\nParasend er rekjanleg rafræn afhendingarþjónusta sem Justikal ehf. rekur, Akralind 3, 201 Kópavogi.\n", "html": "Sýnishorn af sendingu
Opnaðu sendinguna og staðfestu móttöku. Aðgengileg til 13. nóvember 2026.
\"\"
Parasend
 
by Justikal

Rekjanleg rafræn afhendingarþjónusta

Fyrirtæki ehf. bíður enn eftir staðfestingu þinni

Við sendum þér skjal 2026-10-30 og höfum ekki enn fengið staðfestingu á að þú hafir móttekið það. Vinsamlegast staðfestu móttöku sem fyrst.

Sýnishorn af sendingu

1 skjal · aðgengilegt til 13. nóvember 2026

Skilaboð frá sendanda

Þetta er sýnishorn. Svona birtist sendingin viðtakanda.

Opna sendinguna

Þú staðfestir móttöku með rafrænum skilríkjum. Það tekur innan við mínútu.

Virkar hnappurinn ekki? Opnaðu þennan hlekk í vafra:
https://parasend.com/d/synishorn

 
\"\"Er þetta öruggt? Parasend biður þig aldrei um lykilorð eða kortaupplýsingar. Móttaka sendingarinnar er skráð.

In English: Fyrirtæki ehf. has sent you 1 document through Parasend, an electronic registered delivery service. Open the delivery with the button above and confirm receipt.

Þessi tilkynning er frá Parasend. Sendandi: Fyrirtæki ehf.
Parasend er rekjanleg rafræn afhendingarþjónusta sem Justikal ehf. rekur, Akralind 3, 201 Kópavogi.
Persónuvernd
", "images": { "parasend-logo-v1": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEIAAABICAYAAABCzyOOAAAFiElEQVR4nOybXWzbVBTHz7UTUQECtibpeABVQCcxxAZUo02FilAR6gQbG0tG1T3xgqbxBNsjDyABQiAKT+wFaWi8pE2abUh8C9QCTVOV0mnAQHxtaIP1cx39WLs2vofj1IgJSneuY8fO5N+DbcXHvvbf9+Pcc25CEFAkBAFFAiEsAiEsAiEsPBdiPNHUZu6jmVwKPERAmcH6+vBEbbhVanobFb6NfrrWOjWLAO9q0khFTi19KIaGlqCMlEUITII+JuMPCiEepyJ3UKk3rH4BnKfNYRImFRO5T0UaDHAZ14SglxATicb7JehtVMpOKigCNqD7TNAmo4HRGcnke0XxJ+dxXIjRRLxJQPHLJ+nuN4KTIJylTVoXkKpO5/rBQRwRYjzZUC8xZH75XXTDm6EMIOJvmhBdYBipaDb/NZSIbSHOJeN3FopfHsyvfxt4Cv5MtSUVMqBz7eHct2ADJSFGtzfdqoXFbkkvTxduAB+CCN9pAJ2hgvHOmiP5U9zrWEJMJpviBogDdLgJKgnEYV2KvdXZvvzlTDVgIBE3QqWJYCLE3VKXGzmmgYttEQhhEQhhEQhhEQhhEQhhwRNC2J/okCv8vIo9OTY6DXubqMBGOo5CmeAJIQXyPI7/UpPJPQc2GXnsvluEJvfS9H0f2MV8dga81xNoU4bSWJf98lcScj/Vx2aqW7+DHTRkec+8F0QhwUNimb4v9FljA7WzH0CVK6FGXEr1BwPTiAbNdFHtozhaI3xCTffAcWomL4ELVJQQJvqc8Sq4QMUJYTYR2v0IDlNxQhRBuGx8QRVmZ+lO5NguCPIbcBjm8Fn+RNDqaI7HSJlNQ3M9waKCEHgP2xg11nDLC9WBXASfgMtx1ru49gJxgWPHEoJKngefMJZs2kNPFObaG0Jc4NjxhGDezG0mH918E2XRXlG6COUcx4wnhAFj4AMK4fBB+Cd7zkPqIxwz1jQc4eIZgCrwiumt9ZGFq6oOURttAUWqQsZpjh2rRkSzQ2dJjQJ4wFgy3jpfVXWCRNgCiiDg/PXp/nMcW36oTuBPtLkdHGI8GW+XKNavUJBGZdXSvo5ehc6LtXadGOpPTnBt2UKQTzVMD+SYEHS/duqEH175rPjX3mYZiOwsOXuuoSnc1C9ogEN8WyYUo/ocKgyKTfVybdlCRLK5QfLqxqFCoGc9GenuZ4f2lKbhNAl9DyoEqg1HVezV4hEouqBC0NBQWrepJEQ00/cR9cS+8DJXY7lZ5AdUrlFsGjQRBXEI/M8BRXv1UJ0mFzq88jKZzOKfM2+CIspCFN1twLfBryC+se7j46wZ56XYct2mtjfWLoX1kxzb/0sCU8HtNL+vA2eZ0mcLtVakWwnbPuxoIt4hhPY0+Aga3vdF07kOsIHtcH6VgBdoNwW+AX+xK4KJbSGK01sJe8APUPsThtwFJVBSgifW3ddFI0gaPEe8XOp67JIzXXJ65glyYNjzfufBXnL0noUSKVkIc6gKLS62ejMhw+/1WWPbsqNXGo7kPquPDp6mHvshEkN5/C6BP4RxscXOULkSjiWBY+ncMdrtgDJAfeOkJmXLsnPnDI5mw2vSfZ8INB6gDnQG3ALxDErtXpVYAwfHlwVEM/keCgY0mw8MToPwFYilzeYiM3AYV9ZHmM1EnzPuAKcCOdQWKDTfEZ0Ix2PpQVbCRhXX0/0Utn+SwvavU8T6arDHCAVhd0fSuc/ARcqy7mFsZ0MdaPpbVFyz0oWIB8OFC8+sOXLsPLhMWReAUCY7QUW+SIfrVzVE6BGisD+aHmCH40vFk5Uw44n4Iyi0p+iw9e/fzPQcxUSzAuVrse7+YSgzni4JmtzScJ28JrSV3ML52ET4fdHTw1rU4QY+WxvlHcH/NSwCISwCISwCISz+AgAA//8/kCtoAAAABklEQVQDAIko0UD+kjBJAAAAAElFTkSuQmCC", "safety-shield-v1": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAGtUlEQVR4nOyaa2wUVRTHz5nZbWlBaXeLH9SofNGCkIghwUBbSsQHBsEulgRIBGLkER4GY4C2aBpDu1RD0FZFwAh8ICoru1SJxoTE2hYwKlEjLxsQX0SM+0Ab2C3dudczC8Wd2e32zuzMQkp/X2b3zn2c/9xzz8x9OOAmwwE3GUOCBztDgu2muGZ2uQzyQvW3AsruiLe1A3IIQi5YNSPfNWLYfAlwFbU4QXOPw3ccoTnUHX0fWj7rAZuxVXDJ2lm3c1leQT+XI2Jxpryc8whdtsbijpaLr/nOg03YIri4rqpM5rCaA1QhoKFhw4HHySi/gtASaQh0gsVYKthVU7UYEV4gkePAAkj8MTJxc6jRvwssImvBfW5LQpdQdSWZc/Mg57BdddsC7JHMlMvW3U0LNuS2mQJTpoCWUk327m5MsHHjAmRcs6hxlj3EDAgLdq2bc78k80/o5+jMOcn9AHcAXn4r1HDgHJjAXTfzDuB5KxD4cwO7O5wFKT4juPHjn0AAYcHuWs9Oyryo3wx2vE9FPYrzHUFvYAkIIPzKQM5HA6Z7PnyvwrE54vUfAquhBxcG2Em/dhbVeSocnK8kS6rT5LwXBDH9aUlR8xRIl6ebdVujXGjwt9OlPeHuLO8gPftSMIEEJqHx9VeuxCajtqm2DSYZmi0NdoYED3ZuOMHF62beJaFjFpd5a7ih9XewGNNR2g6Ka6vGyVLetyhJLRKTj7rWeu4Ei7lhBJdsmHWfDPAFve9GJRLoKslQDxZzQ7i0KhaY3Cnw3Zw1172H1UkJV+RDerG05BNiynXsYZqyKbov6X4flmjgcdXMHovAvqT1Lreusb85k6aFX933Rz9FJdDZBoKI9zDiP7pGitJlU8VqAg/1YLp8ajqC1K4Xm+hZhtPCTfuOQz/QrEy/IHgBBBEWjBzC2gR0pa1Qcr6iDTysLTFGk1D/q+kpPUtzaZSVKZnEXqkW3Lr/IRBEWDBDrhFMD8AlVpLGJgWkPtH9BygeBEkpE5nI0wKD1itQ1xkZEO9hwIguoUCdoOvzcYm9RO6uM4DEKY4OV43nafWaIpbGLONsqojYUfXVI6hH87Sm2NDDHFmXPq3olvwx+jQ1SHEFK64urCdbNUpC8F1z9/9rDoIcLw97W0+AACymjEm1DbpAEGHBvQzbUgpzqTJdXnUMMsam0kJexmBy5dUjVYquR6mgxCv1ab1Oh/D+lLDgbm8gRK76q6ZxTG28j0hT649MYRWp7n0V9dUDrGKgAJVaDit1CWf+rfdZP4ZVaF3rsPY/PA71Txb2l18Vnda9DbpxH7TofwuVfVhTE8fDRuowJJgjHtEkIOa7Y85Fmcok3Buhgizrumphl1E3vta+LC9W29SmsiNgAGOC4xAgF2WaROTLBioXaQwcC+Y7xgNj89WrYTfua0pdrk22hyzq6cV9YADDWy20Pv0RFZqjaRjZpFDD/q/BRoprPFNkBN0OBv8g2BiYBwYwPHlAxt5MTcMmsBkS26hPYxyawSCGBQc37W+jIKQNNoiVJeurFoBNkFctokuFNpV/H/YGDI1fFXPTQ47PpybBG8XrqkeCxdxaX+2iwfq6Pp12O1aCCUwJDm3yH6Re3p2cpk4EJDnuoxmsdXNsqsvZo3xIuwzaB0l7SWa3dkwbx/Njq0G3A0BR9BF3T9W7YBFU13skdrqmXYBzwd6La8AkMpgk2na6J7+89Djt7GnGLvX0hIKyMdFo58msNtfcNZ4NVFeKMJopzY02HTgJJjEtWCXWcer0sLLSbjLsMe0dPn14xVi81HGyDYyDFKQaqWdf1t+gqLwm7PXvgSyw5FAL9cbbZOByfTp9GLSGoo55sMUXFaroxUeHlzhH7CWrnkhzd3uw0b8UssSqUzzUK1Wfq2M45Q6H0zRHXjDQh4mrbvZkZNIe8pZ70tTxadDrn5n4lSVZuXQy0QdG7y3Md9CqBmrXsFBdGcFnh5eXFl2aOL4dvjoR19xfU13gnla6BTm+k/7wGvcFQ7/NhaN/Ci/UZcLyg2nU03XU0xvT3uRwngHfehkcW6E3jsOcoJ7SW0aR7raUrARdakPewCawEFtO4pWsf6qSS9IuqvxuMMdZhSsL7Th4aplLJ3Op89Qv0YnjthU6WSG9Riah4IOlPlUo+G0Jdkc9sc0HfgYbsP00ravO85DE6KsMMePBk8SZEQ7P0FfcN2AjuTk+XF/pcMVcc1CC1dTgZO1N3skYNIfPOPzg81kSmDKRG8FJjKz1POgEWKq6r8L4tgtNgR8gh+Rc8PVm6MjDYGdI8GDnPwAAAP//Q/PBygAAAAZJREFUAwBNHsTsz47Z7QAAAABJRU5ErkJggg==" } } } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Duplicate a follow-up policy `POST https://api.parasend.com/v1/reminder_policies/{id}/duplicate` Source: https://parasend.com/docs/api/follow-up-policies/duplicate Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Send an `Idempotency-Key` header: a retry with the same key returns the first answer. Copies the current version into a new policy of the organisation, named " (copy)" unless name is given. A step v1 cannot run (paper) is left out of the copy. ## Path parameters - `id` (string, required): The policy's ID. ## Body (application/json) - `name` (string, optional) ## Example: Copy a policy ```bash curl -X POST "https://api.parasend.com/v1/reminder_policies/rpol_01M6R27T5NK3830GCN9QPS9M95/duplicate" \ -H "Authorization: Bearer $PARASEND_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "name": "Payment reminders (copy)" }' ``` ```js const res = await fetch('https://api.parasend.com/v1/reminder_policies/rpol_01M6R27T5NK3830GCN9QPS9M95/duplicate', { method: 'POST', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, 'Idempotency-Key': crypto.randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ name: 'Payment reminders (copy)', }), }); const data = await res.json(); ``` ## Response 201 The copy. ```json { "id": "rpol_01M6R27T6BE940MSE8J194NJCE", "object": "reminder_policy", "name": "Payment reminders (copy)", "system": false, "version": 1, "version_id": "rpv_01M6R27T6BDH6TZ8MQ7GP501C8", "steps": [ { "channel": "email", "after_days": 3, "conditions": [ "not_delivered" ], "available": true }, { "channel": "email", "message": { "preset": "firmer" }, "after_days": 7, "conditions": [ "not_delivered" ], "available": true }, { "channel": "sms", "after_days": 14, "conditions": [ "not_delivered" ], "available": true } ], "stop_on": "confirmed", "customized": false, "is_account_default": false, "in_use_by_batches": 0, "in_use_by_deliveries": 0, "created_at": "2026-11-06T09:15:05.802Z", "updated_at": "2026-11-06T09:15:05.802Z", "archived_at": null } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Reset one of Parasend's policies `POST https://api.parasend.com/v1/reminder_policies/{id}/reset` Source: https://parasend.com/docs/api/follow-up-policies/reset Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Send an `Idempotency-Key` header: a retry with the same key returns the first answer. Ends the organisation's own version of rpol_default or rpol_debt_collection. Its versions stay in the history; deliveries waiting take Parasend's version from their next step. With nothing to reset it answers the policy unchanged. One of the organisation's own policies: 400 policy_not_resettable. Needs policies.update. ## Path parameters - `id` (string, required): The policy's ID. ## Body (application/json) ## Example: Reset Parasend's default policy ```bash curl -X POST "https://api.parasend.com/v1/reminder_policies/rpol_default/reset" \ -H "Authorization: Bearer $PARASEND_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{}' ``` ```js const res = await fetch('https://api.parasend.com/v1/reminder_policies/rpol_default/reset', { method: 'POST', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, 'Idempotency-Key': crypto.randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({}), }); const data = await res.json(); ``` ## Response 200 The policy, Parasend's version again. ```json { "id": "rpol_default", "object": "reminder_policy", "name": "Default", "system": true, "version": 1, "version_id": "rpv_default_v1", "steps": [ { "channel": "email", "after_days": 3, "conditions": [ "not_delivered" ], "available": true }, { "channel": "email", "after_days": 7, "conditions": [ "not_delivered" ], "available": true } ], "stop_on": "confirmed", "customized": false, "is_account_default": false, "in_use_by_batches": 0, "in_use_by_deliveries": 0, "created_at": "2026-11-06T09:15:05.537Z", "updated_at": "2026-11-06T09:15:05.540Z", "archived_at": null } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Save a wording `POST https://api.parasend.com/v1/wordings` Source: https://parasend.com/docs/api/wordings/create Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Send an `Idempotency-Key` header: a retry with the same key returns the first answer. Version 1 of a named wording for one channel. message follows a step's rules (StepMessage, without source); its custom needs Plan 1–3 or Enterprise (403 theme_knob_plan_gated, param message.custom); an SMS wording must keep the reminder one SMS (policy_sms_too_long). ## Body (application/json) - `name` (string, required): Your name for the wording: 1–100 characters, without control characters; leading and trailing spaces are trimmed. - `channel` (string, required): The channel the wording is for: `email` or `sms`. It cannot be changed later; an SMS wording's text must keep the message to one SMS. One of: `email`, `sms`. - `message` (object, required): The wording a follow-up step's reminder sends. Left out: Parasend's standard reminder. `preset` picks Parasend's wording; `custom` holds your own, by language (Plan 1–3 and Enterprise). A field or language left out comes from the preset. - `message.preset` (string, optional): `standard` is the usual reminder; `firmer` and `final` are Parasend's stronger wordings. One of: `standard`, `firmer`, `final`. Default: `"standard"`. - `message.custom` (object, optional): Your own wording, by language (`is`, `en`); a field or a language left out comes from `preset`. Needs Plan 1–3 or Enterprise; otherwise refused with `theme_knob_plan_gated`. - `message.custom.is` (object, optional): Plain text. An e-mail step takes `subject`, `heading` and `text`; an SMS step `text` only. Variables: {{recipient.name}}, {{recipient.first_name}}, {{sender.name}}, {{delivery.title}}, {{delivery.sent_on}}, {{delivery.expires_on}}, {{files.count}}; an SMS only the two dates. No links, e-mail addresses or phone numbers. An e-mail may hold the gender word {{recipient.gender: male | female | other}}. - `message.custom.is.subject` (string, optional): One line. Empty is the preset's. - `message.custom.is.heading` (string, optional): One line, in place of the reminder's heading. - `message.custom.is.text` (string, optional): A paragraph under the heading; for an SMS, between the sender's name and the link (3.5). - `message.custom.en` (object, optional): Plain text. An e-mail step takes `subject`, `heading` and `text`; an SMS step `text` only. Variables: {{recipient.name}}, {{recipient.first_name}}, {{sender.name}}, {{delivery.title}}, {{delivery.sent_on}}, {{delivery.expires_on}}, {{files.count}}; an SMS only the two dates. No links, e-mail addresses or phone numbers. An e-mail may hold the gender word {{recipient.gender: male | female | other}}. - `message.custom.en.subject` (string, optional): One line. Empty is the preset's. - `message.custom.en.heading` (string, optional): One line, in place of the reminder's heading. - `message.custom.en.text` (string, optional): A paragraph under the heading; for an SMS, between the sender's name and the link (3.5). - `message.source` (object, optional): The saved wording and version this step's wording was loaded from. A record only: the step keeps its own copy. Not on a saved wording's own message. - `message.source.wording` (string, required) - `message.source.version` (integer, required) - `use` (string, optional): `reminder` (the default) for follow-up steps, or `notice` for the first notice. One of: `reminder`, `notice`. Default: `"reminder"`. - `is_default` (boolean, optional): Notice wordings only: make this the first-notice wording for its channel. One per channel. Default: `false`. ## Example: Save a firmer e-mail wording ```bash curl -X POST "https://api.parasend.com/v1/wordings" \ -H "Authorization: Bearer $PARASEND_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "name": "Second reminder", "channel": "email", "message": { "preset": "firmer" } }' ``` ```js const res = await fetch('https://api.parasend.com/v1/wordings', { method: 'POST', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, 'Idempotency-Key': crypto.randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ name: 'Second reminder', channel: 'email', message: { preset: 'firmer', }, }), }); const data = await res.json(); ``` ## Response 201 Saved. ```json { "id": "wrd_01M6R27T6WN5ZPHV4DA9SGQ3G6", "object": "wording", "name": "Second reminder", "use": "reminder", "channel": "email", "is_default": false, "version": 1, "version_id": "wrv_01M6R27T6WR1V5QFE0E3SKRHNW", "message": { "preset": "firmer" }, "created_at": "2026-11-06T09:15:05.820Z", "updated_at": "2026-11-06T09:15:05.820Z", "archived_at": null, "used_by": { "policies": 0, "policies_on_older_version": 0 } } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # List saved wordings `GET https://api.parasend.com/v1/wordings` Source: https://parasend.com/docs/api/wordings/list Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Your organisation's saved wordings in use, newest first. ## Query parameters - `channel` (string, optional): One of: `email`, `sms`. - `use` (string, optional): `notice` (the first notice) or `reminder` (follow-up steps). One of: `reminder`, `notice`. - `limit` (integer, optional): Default: `20`. - `cursor` (string, optional) ## Example: List saved wordings ```bash curl "https://api.parasend.com/v1/wordings" \ -H "Authorization: Bearer $PARASEND_KEY" ``` ```js const res = await fetch('https://api.parasend.com/v1/wordings', { method: 'GET', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, }, }); const data = await res.json(); ``` ## Response 200 Wordings. ```json { "data": [ { "id": "wrd_01M6R27T6WN5ZPHV4DA9SGQ3G6", "object": "wording", "name": "Second reminder", "use": "reminder", "channel": "email", "is_default": false, "version": 1, "version_id": "wrv_01M6R27T6WR1V5QFE0E3SKRHNW", "message": { "preset": "firmer" }, "created_at": "2026-11-06T09:15:05.820Z", "updated_at": "2026-11-06T09:15:05.820Z", "archived_at": null, "used_by": { "policies": 0, "policies_on_older_version": 0 } } ], "has_more": false, "next_cursor": null } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Retrieve a saved wording `GET https://api.parasend.com/v1/wordings/{id}` Source: https://parasend.com/docs/api/wordings/retrieve Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Retrieves one saved wording, archived ones included. ## Path parameters - `id` (string, required) ## Example: Retrieve a saved wording ```bash curl "https://api.parasend.com/v1/wordings/wrd_01M6R27T6WN5ZPHV4DA9SGQ3G6" \ -H "Authorization: Bearer $PARASEND_KEY" ``` ```js const res = await fetch('https://api.parasend.com/v1/wordings/wrd_01M6R27T6WN5ZPHV4DA9SGQ3G6', { method: 'GET', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, }, }); const data = await res.json(); ``` ## Response 200 The wording. ```json { "id": "wrd_01M6R27T6WN5ZPHV4DA9SGQ3G6", "object": "wording", "name": "Second reminder", "use": "reminder", "channel": "email", "is_default": false, "version": 1, "version_id": "wrv_01M6R27T6WR1V5QFE0E3SKRHNW", "message": { "preset": "firmer" }, "created_at": "2026-11-06T09:15:05.820Z", "updated_at": "2026-11-06T09:15:05.820Z", "archived_at": null, "used_by": { "policies": 0, "policies_on_older_version": 0 } } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Update a saved wording `PATCH https://api.parasend.com/v1/wordings/{id}` Source: https://parasend.com/docs/api/wordings/update Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). The channel stays as it was made (invalid_parameter_value, param channel). An archived wording is not changed (409 wording_archived). ## Path parameters - `id` (string, required) ## Body (application/json) - `name` (string, optional) - `message` (object, optional): The wording a follow-up step's reminder sends. Left out: Parasend's standard reminder. `preset` picks Parasend's wording; `custom` holds your own, by language (Plan 1–3 and Enterprise). A field or language left out comes from the preset. - `message.preset` (string, optional): `standard` is the usual reminder; `firmer` and `final` are Parasend's stronger wordings. One of: `standard`, `firmer`, `final`. Default: `"standard"`. - `message.custom` (object, optional): Your own wording, by language (`is`, `en`); a field or a language left out comes from `preset`. Needs Plan 1–3 or Enterprise; otherwise refused with `theme_knob_plan_gated`. - `message.custom.is` (object, optional): Plain text. An e-mail step takes `subject`, `heading` and `text`; an SMS step `text` only. Variables: {{recipient.name}}, {{recipient.first_name}}, {{sender.name}}, {{delivery.title}}, {{delivery.sent_on}}, {{delivery.expires_on}}, {{files.count}}; an SMS only the two dates. No links, e-mail addresses or phone numbers. An e-mail may hold the gender word {{recipient.gender: male | female | other}}. - `message.custom.is.subject` (string, optional): One line. Empty is the preset's. - `message.custom.is.heading` (string, optional): One line, in place of the reminder's heading. - `message.custom.is.text` (string, optional): A paragraph under the heading; for an SMS, between the sender's name and the link (3.5). - `message.custom.en` (object, optional): Plain text. An e-mail step takes `subject`, `heading` and `text`; an SMS step `text` only. Variables: {{recipient.name}}, {{recipient.first_name}}, {{sender.name}}, {{delivery.title}}, {{delivery.sent_on}}, {{delivery.expires_on}}, {{files.count}}; an SMS only the two dates. No links, e-mail addresses or phone numbers. An e-mail may hold the gender word {{recipient.gender: male | female | other}}. - `message.custom.en.subject` (string, optional): One line. Empty is the preset's. - `message.custom.en.heading` (string, optional): One line, in place of the reminder's heading. - `message.custom.en.text` (string, optional): A paragraph under the heading; for an SMS, between the sender's name and the link (3.5). - `message.source` (object, optional): The saved wording and version this step's wording was loaded from. A record only: the step keeps its own copy. Not on a saved wording's own message. - `message.source.wording` (string, required) - `message.source.version` (integer, required) - `is_default` (boolean, optional): Notice wordings only: make this the first-notice wording for its channel. ## Example: Rename a wording ```bash curl -X PATCH "https://api.parasend.com/v1/wordings/wrd_01M6R27T6WN5ZPHV4DA9SGQ3G6" \ -H "Authorization: Bearer $PARASEND_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Second reminder (firm)" }' ``` ```js const res = await fetch('https://api.parasend.com/v1/wordings/wrd_01M6R27T6WN5ZPHV4DA9SGQ3G6', { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ name: 'Second reminder (firm)', }), }); const data = await res.json(); ``` ## Response 200 The wording as it now is. ```json { "id": "wrd_01M6R27T6WN5ZPHV4DA9SGQ3G6", "object": "wording", "name": "Second reminder (firm)", "use": "reminder", "channel": "email", "is_default": false, "version": 1, "version_id": "wrv_01M6R27T6WR1V5QFE0E3SKRHNW", "message": { "preset": "firmer" }, "created_at": "2026-11-06T09:15:05.820Z", "updated_at": "2026-11-06T09:15:05.829Z", "archived_at": null, "used_by": { "policies": 0, "policies_on_older_version": 0 } } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Archive a saved wording `DELETE https://api.parasend.com/v1/wordings/{id}` Source: https://parasend.com/docs/api/wordings/archive Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Archives a saved wording. Policies that loaded it keep their own copy. ## Path parameters - `id` (string, required) ## Example: Archive a saved wording ```bash curl -X DELETE "https://api.parasend.com/v1/wordings/wrd_01M6R27T6WN5ZPHV4DA9SGQ3G6" \ -H "Authorization: Bearer $PARASEND_KEY" ``` ```js const res = await fetch('https://api.parasend.com/v1/wordings/wrd_01M6R27T6WN5ZPHV4DA9SGQ3G6', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, }, }); const data = await res.json(); ``` ## Response 204 Archived (or already). Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # List a wording's versions `GET https://api.parasend.com/v1/wordings/{id}/versions` Source: https://parasend.com/docs/api/wordings/versions Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Every version of a saved wording, newest first. ## Path parameters - `id` (string, required) ## Example: List a wording's versions ```bash curl "https://api.parasend.com/v1/wordings/wrd_01M6R27T6WN5ZPHV4DA9SGQ3G6/versions" \ -H "Authorization: Bearer $PARASEND_KEY" ``` ```js const res = await fetch('https://api.parasend.com/v1/wordings/wrd_01M6R27T6WN5ZPHV4DA9SGQ3G6/versions', { method: 'GET', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, }, }); const data = await res.json(); ``` ## Response 200 Versions. ```json { "data": [ { "id": "wrv_01M6R27T6WR1V5QFE0E3SKRHNW", "object": "wording_version", "wording_id": "wrd_01M6R27T6WN5ZPHV4DA9SGQ3G6", "version": 1, "message": { "preset": "firmer" }, "created_at": "2026-11-06T09:15:05.820Z", "created_by": { "user_id": "usr_01M6R27SYD4B50414S0BZ4KBKR", "via": "api_key" } } ], "has_more": false, "next_cursor": null } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Retrieve a wording version `GET https://api.parasend.com/v1/wordings/{id}/versions/{version}` Source: https://parasend.com/docs/api/wordings/version Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). One version of a saved wording: what a step that names it was loaded from. ## Path parameters - `id` (string, required) - `version` (integer, required) ## Example: The first version ```bash curl "https://api.parasend.com/v1/wordings/wrd_01M6R27T6WN5ZPHV4DA9SGQ3G6/versions/1" \ -H "Authorization: Bearer $PARASEND_KEY" ``` ```js const res = await fetch('https://api.parasend.com/v1/wordings/wrd_01M6R27T6WN5ZPHV4DA9SGQ3G6/versions/1', { method: 'GET', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, }, }); const data = await res.json(); ``` ## Response 200 The version. ```json { "id": "wrv_01M6R27T6WR1V5QFE0E3SKRHNW", "object": "wording_version", "wording_id": "wrd_01M6R27T6WN5ZPHV4DA9SGQ3G6", "version": 1, "message": { "preset": "firmer" }, "created_at": "2026-11-06T09:15:05.820Z", "created_by": { "user_id": "usr_01M6R27SYD4B50414S0BZ4KBKR", "via": "api_key" } } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Preview a wording `POST https://api.parasend.com/v1/wordings/preview` Source: https://parasend.com/docs/api/wordings/preview Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Send an `Idempotency-Key` header: a retry with the same key returns the first answer. Shows a wording, saved or not, as its recipient gets it. ## Query parameters - `locale` (string, optional): One of: `is`, `en`. Default: `"is"`. ## Body (application/json) - `channel` (string, required): The channel to preview: `email` or `sms`. One of: `email`, `sms`. - `message` (object, required): The wording a follow-up step's reminder sends. Left out: Parasend's standard reminder. `preset` picks Parasend's wording; `custom` holds your own, by language (Plan 1–3 and Enterprise). A field or language left out comes from the preset. - `message.preset` (string, optional): `standard` is the usual reminder; `firmer` and `final` are Parasend's stronger wordings. One of: `standard`, `firmer`, `final`. Default: `"standard"`. - `message.custom` (object, optional): Your own wording, by language (`is`, `en`); a field or a language left out comes from `preset`. Needs Plan 1–3 or Enterprise; otherwise refused with `theme_knob_plan_gated`. - `message.custom.is` (object, optional): Plain text. An e-mail step takes `subject`, `heading` and `text`; an SMS step `text` only. Variables: {{recipient.name}}, {{recipient.first_name}}, {{sender.name}}, {{delivery.title}}, {{delivery.sent_on}}, {{delivery.expires_on}}, {{files.count}}; an SMS only the two dates. No links, e-mail addresses or phone numbers. An e-mail may hold the gender word {{recipient.gender: male | female | other}}. - `message.custom.is.subject` (string, optional): One line. Empty is the preset's. - `message.custom.is.heading` (string, optional): One line, in place of the reminder's heading. - `message.custom.is.text` (string, optional): A paragraph under the heading; for an SMS, between the sender's name and the link (3.5). - `message.custom.en` (object, optional): Plain text. An e-mail step takes `subject`, `heading` and `text`; an SMS step `text` only. Variables: {{recipient.name}}, {{recipient.first_name}}, {{sender.name}}, {{delivery.title}}, {{delivery.sent_on}}, {{delivery.expires_on}}, {{files.count}}; an SMS only the two dates. No links, e-mail addresses or phone numbers. An e-mail may hold the gender word {{recipient.gender: male | female | other}}. - `message.custom.en.subject` (string, optional): One line. Empty is the preset's. - `message.custom.en.heading` (string, optional): One line, in place of the reminder's heading. - `message.custom.en.text` (string, optional): A paragraph under the heading; for an SMS, between the sender's name and the link (3.5). - `message.source` (object, optional): The saved wording and version this step's wording was loaded from. A record only: the step keeps its own copy. Not on a saved wording's own message. - `message.source.wording` (string, required) - `message.source.version` (integer, required) - `use` (string, optional): What the wording is for: `reminder` (the default), a follow-up step's reminder, or `notice`, the first notice (whose `message.preset` must be `standard`). One of: `reminder`, `notice`. Default: `"reminder"`. - `brand_profile_id` (string · nullable, optional): The brand profile (`bp_…`) to preview with; null for Parasend's own look. Left out: your organisation's default profile. - `sample_gender` (string, optional): The gender to fill in for {{recipient.gender: …}} in the preview. One of: `male`, `female`, `other`. ## Example: Preview a notice wording ```bash curl -X POST "https://api.parasend.com/v1/wordings/preview" \ -H "Authorization: Bearer $PARASEND_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "use": "notice", "channel": "email", "message": { "preset": "standard" } }' ``` ```js const res = await fetch('https://api.parasend.com/v1/wordings/preview', { method: 'POST', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, 'Idempotency-Key': crypto.randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ use: 'notice', channel: 'email', message: { preset: 'standard', }, }), }); const data = await res.json(); ``` ## Response 200 The message, drawn by the code that sends it (object notice_preview or reminder_step_preview). ```json { "object": "notice_preview", "channel": "email", "locale": "is", "preset": "standard", "template": { "id": "invitation.email", "version": 13 }, "email": { "from_name": "Sending frá: Fyrirtæki ehf.", "subject": "Sýnishorn af sendingu", "text": "Fyrirtæki ehf. hefur sent þér 1 skjal með Parasend.\n\nSýnishorn af sendingu\n1 skjal · aðgengilegt til 13. nóvember 2026\n\nSkilaboð frá sendanda:\nÞetta er sýnishorn. Svona birtist sendingin viðtakanda.\n\nOpna sendinguna: https://parasend.com/d/synishorn\nÞú staðfestir móttöku með rafrænum skilríkjum. Það tekur innan við mínútu.\n\nEr þetta öruggt? Parasend biður þig aldrei um lykilorð eða kortaupplýsingar. Móttaka sendingarinnar er skráð.\n\nIn English: Fyrirtæki ehf. has sent you 1 document through Parasend, an electronic registered delivery service. Open the delivery with the button above and confirm receipt.\n\n—\nÞessi tilkynning er frá Parasend. Sendandi: Fyrirtæki ehf.\nParasend er rekjanleg rafræn afhendingarþjónusta sem Justikal ehf. rekur, Akralind 3, 201 Kópavogi.\n", "html": "Sýnishorn af sendingu
Opnaðu sendinguna og staðfestu móttöku. Aðgengileg til 13. nóvember 2026.
\"\"
Parasend
 
by Justikal

Rekjanleg rafræn afhendingarþjónusta

Fyrirtæki ehf. hefur sent þér 1 skjal með Parasend

Sýnishorn af sendingu

1 skjal · aðgengilegt til 13. nóvember 2026

Skilaboð frá sendanda

Þetta er sýnishorn. Svona birtist sendingin viðtakanda.

Opna sendinguna

Þú staðfestir móttöku með rafrænum skilríkjum. Það tekur innan við mínútu.

Virkar hnappurinn ekki? Opnaðu þennan hlekk í vafra:
https://parasend.com/d/synishorn

 
\"\"Er þetta öruggt? Parasend biður þig aldrei um lykilorð eða kortaupplýsingar. Móttaka sendingarinnar er skráð.

In English: Fyrirtæki ehf. has sent you 1 document through Parasend, an electronic registered delivery service. Open the delivery with the button above and confirm receipt.

Þessi tilkynning er frá Parasend. Sendandi: Fyrirtæki ehf.
Parasend er rekjanleg rafræn afhendingarþjónusta sem Justikal ehf. rekur, Akralind 3, 201 Kópavogi.
Persónuvernd
", "images": { "parasend-logo-v1": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEIAAABICAYAAABCzyOOAAAFiElEQVR4nOybXWzbVBTHz7UTUQECtibpeABVQCcxxAZUo02FilAR6gQbG0tG1T3xgqbxBNsjDyABQiAKT+wFaWi8pE2abUh8C9QCTVOV0mnAQHxtaIP1cx39WLs2vofj1IgJSneuY8fO5N+DbcXHvvbf9+Pcc25CEFAkBAFFAiEsAiEsAiEsPBdiPNHUZu6jmVwKPERAmcH6+vBEbbhVanobFb6NfrrWOjWLAO9q0khFTi19KIaGlqCMlEUITII+JuMPCiEepyJ3UKk3rH4BnKfNYRImFRO5T0UaDHAZ14SglxATicb7JehtVMpOKigCNqD7TNAmo4HRGcnke0XxJ+dxXIjRRLxJQPHLJ+nuN4KTIJylTVoXkKpO5/rBQRwRYjzZUC8xZH75XXTDm6EMIOJvmhBdYBipaDb/NZSIbSHOJeN3FopfHsyvfxt4Cv5MtSUVMqBz7eHct2ADJSFGtzfdqoXFbkkvTxduAB+CCN9pAJ2hgvHOmiP5U9zrWEJMJpviBogDdLgJKgnEYV2KvdXZvvzlTDVgIBE3QqWJYCLE3VKXGzmmgYttEQhhEQhhEQhhEQhhEQhhwRNC2J/okCv8vIo9OTY6DXubqMBGOo5CmeAJIQXyPI7/UpPJPQc2GXnsvluEJvfS9H0f2MV8dga81xNoU4bSWJf98lcScj/Vx2aqW7+DHTRkec+8F0QhwUNimb4v9FljA7WzH0CVK6FGXEr1BwPTiAbNdFHtozhaI3xCTffAcWomL4ELVJQQJvqc8Sq4QMUJYTYR2v0IDlNxQhRBuGx8QRVmZ+lO5NguCPIbcBjm8Fn+RNDqaI7HSJlNQ3M9waKCEHgP2xg11nDLC9WBXASfgMtx1ru49gJxgWPHEoJKngefMJZs2kNPFObaG0Jc4NjxhGDezG0mH918E2XRXlG6COUcx4wnhAFj4AMK4fBB+Cd7zkPqIxwz1jQc4eIZgCrwiumt9ZGFq6oOURttAUWqQsZpjh2rRkSzQ2dJjQJ4wFgy3jpfVXWCRNgCiiDg/PXp/nMcW36oTuBPtLkdHGI8GW+XKNavUJBGZdXSvo5ehc6LtXadGOpPTnBt2UKQTzVMD+SYEHS/duqEH175rPjX3mYZiOwsOXuuoSnc1C9ogEN8WyYUo/ocKgyKTfVybdlCRLK5QfLqxqFCoGc9GenuZ4f2lKbhNAl9DyoEqg1HVezV4hEouqBC0NBQWrepJEQ00/cR9cS+8DJXY7lZ5AdUrlFsGjQRBXEI/M8BRXv1UJ0mFzq88jKZzOKfM2+CIspCFN1twLfBryC+se7j46wZ56XYct2mtjfWLoX1kxzb/0sCU8HtNL+vA2eZ0mcLtVakWwnbPuxoIt4hhPY0+Aga3vdF07kOsIHtcH6VgBdoNwW+AX+xK4KJbSGK01sJe8APUPsThtwFJVBSgifW3ddFI0gaPEe8XOp67JIzXXJ65glyYNjzfufBXnL0noUSKVkIc6gKLS62ejMhw+/1WWPbsqNXGo7kPquPDp6mHvshEkN5/C6BP4RxscXOULkSjiWBY+ncMdrtgDJAfeOkJmXLsnPnDI5mw2vSfZ8INB6gDnQG3ALxDErtXpVYAwfHlwVEM/keCgY0mw8MToPwFYilzeYiM3AYV9ZHmM1EnzPuAKcCOdQWKDTfEZ0Ix2PpQVbCRhXX0/0Utn+SwvavU8T6arDHCAVhd0fSuc/ARcqy7mFsZ0MdaPpbVFyz0oWIB8OFC8+sOXLsPLhMWReAUCY7QUW+SIfrVzVE6BGisD+aHmCH40vFk5Uw44n4Iyi0p+iw9e/fzPQcxUSzAuVrse7+YSgzni4JmtzScJ28JrSV3ML52ET4fdHTw1rU4QY+WxvlHcH/NSwCISwCISwCISz+AgAA//8/kCtoAAAABklEQVQDAIko0UD+kjBJAAAAAElFTkSuQmCC", "safety-shield-v1": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAGtUlEQVR4nOyaa2wUVRTHz5nZbWlBaXeLH9SofNGCkIghwUBbSsQHBsEulgRIBGLkER4GY4C2aBpDu1RD0FZFwAh8ICoru1SJxoTE2hYwKlEjLxsQX0SM+0Ab2C3dudczC8Wd2e32zuzMQkp/X2b3zn2c/9xzz8x9OOAmwwE3GUOCBztDgu2muGZ2uQzyQvW3AsruiLe1A3IIQi5YNSPfNWLYfAlwFbU4QXOPw3ccoTnUHX0fWj7rAZuxVXDJ2lm3c1leQT+XI2Jxpryc8whdtsbijpaLr/nOg03YIri4rqpM5rCaA1QhoKFhw4HHySi/gtASaQh0gsVYKthVU7UYEV4gkePAAkj8MTJxc6jRvwssImvBfW5LQpdQdSWZc/Mg57BdddsC7JHMlMvW3U0LNuS2mQJTpoCWUk327m5MsHHjAmRcs6hxlj3EDAgLdq2bc78k80/o5+jMOcn9AHcAXn4r1HDgHJjAXTfzDuB5KxD4cwO7O5wFKT4juPHjn0AAYcHuWs9Oyryo3wx2vE9FPYrzHUFvYAkIIPzKQM5HA6Z7PnyvwrE54vUfAquhBxcG2Em/dhbVeSocnK8kS6rT5LwXBDH9aUlR8xRIl6ebdVujXGjwt9OlPeHuLO8gPftSMIEEJqHx9VeuxCajtqm2DSYZmi0NdoYED3ZuOMHF62beJaFjFpd5a7ih9XewGNNR2g6Ka6vGyVLetyhJLRKTj7rWeu4Ei7lhBJdsmHWfDPAFve9GJRLoKslQDxZzQ7i0KhaY3Cnw3Zw1172H1UkJV+RDerG05BNiynXsYZqyKbov6X4flmjgcdXMHovAvqT1Lreusb85k6aFX933Rz9FJdDZBoKI9zDiP7pGitJlU8VqAg/1YLp8ajqC1K4Xm+hZhtPCTfuOQz/QrEy/IHgBBBEWjBzC2gR0pa1Qcr6iDTysLTFGk1D/q+kpPUtzaZSVKZnEXqkW3Lr/IRBEWDBDrhFMD8AlVpLGJgWkPtH9BygeBEkpE5nI0wKD1itQ1xkZEO9hwIguoUCdoOvzcYm9RO6uM4DEKY4OV43nafWaIpbGLONsqojYUfXVI6hH87Sm2NDDHFmXPq3olvwx+jQ1SHEFK64urCdbNUpC8F1z9/9rDoIcLw97W0+AACymjEm1DbpAEGHBvQzbUgpzqTJdXnUMMsam0kJexmBy5dUjVYquR6mgxCv1ab1Oh/D+lLDgbm8gRK76q6ZxTG28j0hT649MYRWp7n0V9dUDrGKgAJVaDit1CWf+rfdZP4ZVaF3rsPY/PA71Txb2l18Vnda9DbpxH7TofwuVfVhTE8fDRuowJJgjHtEkIOa7Y85Fmcok3Buhgizrumphl1E3vta+LC9W29SmsiNgAGOC4xAgF2WaROTLBioXaQwcC+Y7xgNj89WrYTfua0pdrk22hyzq6cV9YADDWy20Pv0RFZqjaRjZpFDD/q/BRoprPFNkBN0OBv8g2BiYBwYwPHlAxt5MTcMmsBkS26hPYxyawSCGBQc37W+jIKQNNoiVJeurFoBNkFctokuFNpV/H/YGDI1fFXPTQ47PpybBG8XrqkeCxdxaX+2iwfq6Pp12O1aCCUwJDm3yH6Re3p2cpk4EJDnuoxmsdXNsqsvZo3xIuwzaB0l7SWa3dkwbx/Njq0G3A0BR9BF3T9W7YBFU13skdrqmXYBzwd6La8AkMpgk2na6J7+89Djt7GnGLvX0hIKyMdFo58msNtfcNZ4NVFeKMJopzY02HTgJJjEtWCXWcer0sLLSbjLsMe0dPn14xVi81HGyDYyDFKQaqWdf1t+gqLwm7PXvgSyw5FAL9cbbZOByfTp9GLSGoo55sMUXFaroxUeHlzhH7CWrnkhzd3uw0b8UssSqUzzUK1Wfq2M45Q6H0zRHXjDQh4mrbvZkZNIe8pZ70tTxadDrn5n4lSVZuXQy0QdG7y3Md9CqBmrXsFBdGcFnh5eXFl2aOL4dvjoR19xfU13gnla6BTm+k/7wGvcFQ7/NhaN/Ci/UZcLyg2nU03XU0xvT3uRwngHfehkcW6E3jsOcoJ7SW0aR7raUrARdakPewCawEFtO4pWsf6qSS9IuqvxuMMdZhSsL7Th4aplLJ3Op89Qv0YnjthU6WSG9Riah4IOlPlUo+G0Jdkc9sc0HfgYbsP00ravO85DE6KsMMePBk8SZEQ7P0FfcN2AjuTk+XF/pcMVcc1CC1dTgZO1N3skYNIfPOPzg81kSmDKRG8FJjKz1POgEWKq6r8L4tgtNgR8gh+Rc8PVm6MjDYGdI8GDnPwAAAP//Q/PBygAAAAZJREFUAwBNHsTsz47Z7QAAAABJRU5ErkJggg==" } } } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Check what wordings may contain `GET https://api.parasend.com/v1/wordings/capabilities` Source: https://parasend.com/docs/api/wordings/capabilities Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Whether your organisation may write a gender word into its wordings, and if not, why not. ## Example: Check what wordings may contain ```bash curl "https://api.parasend.com/v1/wordings/capabilities" \ -H "Authorization: Bearer $PARASEND_KEY" ``` ```js const res = await fetch('https://api.parasend.com/v1/wordings/capabilities', { method: 'GET', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, }, }); const data = await res.json(); ``` ## Response 200 The answer. ```json { "object": "wording_capabilities", "gender_wording": false, "gender_wording_reason": "switched_off" } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Create a brand profile `POST https://api.parasend.com/v1/brand_profiles` Source: https://parasend.com/docs/api/brand-profiles/create Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Send an `Idempotency-Key` header: a retry with the same key returns the first answer. Creates the profile and its version 1. WCAG contrast is checked at save (400 contrast_validation_failed, param primary_color, with the ratio in the message). An unknown logo (another organisation's, or none) is 400 brand_asset_reference_invalid. is_default true takes the default from the organisation's previous one. ## Body (application/json) - `name` (string, required): What the sender calls the profile (the portal list). Not shown to recipients. - `sender_display_name` (string, required): "Delivery from …" and the recipient page. - `primary_color` (string, required): Upper-cased on save. White text on it must reach 4.5:1 (rule B4). - `is_default` (boolean, optional): The organisation's default: used when a delivery names no profile. Takes it from the previous default. Default: `false`. - `sender_display_name_dative` (string · nullable, optional): The sender's name in the Icelandic dative, for “Sending frá …”. Without it the notice says “Sending frá: {name}”. - `logo_asset_id` (string · nullable, optional): bas_… from POST /brand_assets. Null or absent: the monogram is shown. - `icon_asset_id` (string · nullable, optional): `bas_…` of an icon uploaded with POST /brand_assets?kind=icon. Null or left out: the tab shows a monogram on `primary_color`. - `sms_sender_name` (string · nullable, optional): What an SMS to an Icelandic number shows as its sender, once Parasend has approved the name for your company. Verified companies only. Null: PARASEND. - `monogram` (string · nullable, optional): 1–3 letters or digits, upper-cased. Null or absent derives it from sender_display_name (first letters of its first two words, company forms such as hf./ehf. left out). - `header_style` (string, optional): The header of the recipient page and the notice e-mail: `band` (the default), your `primary_color` across the top with the logo on it; `letterhead`, a plain header with a rule in your colour along its top; `minimal`, a plain header with your logo or name. One of: `band`, `letterhead`, `minimal`. Default: `"band"`. - `support` (object · nullable, optional): The sender's own contact, shown to the recipient on the delivery's pages. - `support.email` (string · email · nullable, optional) - `support.phone` (string · nullable, optional): A telephone number, e.g. +354 440 4000. - `support.url` (string · uri · nullable, optional): https:// only. - `expired_message` (object · nullable, optional): Your own words on the page a recipient sees when the link has expired, per language. Plain text. - `expired_message.is` (string · nullable, optional) - `expired_message.en` (string · nullable, optional) - `reply_to` (string · email · nullable, optional): The notice e-mail's Reply-To address. - `recipient_domain_id` (string · nullable, optional): `cdm_…` of your organisation's link domain, or null for Parasend's own. Changing it needs the portal and a fresh sign-in. Links use it only while it is active. - `email_sender_id` (string · nullable, optional): `es_…` of your organisation's e-mail sender, or null for Parasend's address. Changing it needs the portal and a fresh sign-in. It is used only while the sender is active; otherwise the e-mail comes from Parasend's address under your brand's name. - `theme` (object · nullable, optional): The finer look of the recipient page and the e-mail: colours, corners, font, layout, and what is shown. Left out or null: Parasend's own look. In an update, a field left out stays as it is and `theme: null` resets every field. ## Example: Your company's look ```bash curl -X POST "https://api.parasend.com/v1/brand_profiles" \ -H "Authorization: Bearer $PARASEND_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "name": "Fyrirtæki ehf.", "sender_display_name": "Fyrirtæki ehf.", "primary_color": "#1E5BB8", "logo_asset_id": "bas_01M6R27T7R9RDBHE6X72ZRW3YC" }' ``` ```js const res = await fetch('https://api.parasend.com/v1/brand_profiles', { method: 'POST', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, 'Idempotency-Key': crypto.randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ name: 'Fyrirtæki ehf.', sender_display_name: 'Fyrirtæki ehf.', primary_color: '#1E5BB8', logo_asset_id: 'bas_01M6R27T7R9RDBHE6X72ZRW3YC', }), }); const data = await res.json(); ``` ## Response 201 Created. ```json { "id": "bp_01M6R27T817SW9MENZEV3KSZZX", "object": "brand_profile", "name": "Fyrirtæki ehf.", "is_default": false, "version": 1, "version_id": "bpv_01M6R27T82QZMJ98K4RGVQBRF0", "sender_display_name": "Fyrirtæki ehf.", "sender_display_name_dative": null, "logo": { "asset_id": "bas_01M6R27T7R9RDBHE6X72ZRW3YC", "url": "/v1/brand_assets/bas_01M6R27T7R9RDBHE6X72ZRW3YC/content", "sha256": "f72bdd36e6ce104c53c8f21bd9e618e113a66f4d6729d43a4cb07b3f1373addb", "width": 200, "height": 48 }, "monogram": "F", "sms_sender_name": null, "icon": null, "primary_color": "#1E5BB8", "contrast_ratio": 6.47, "header_style": "band", "support": { "email": null, "phone": null, "url": null }, "expired_message": { "is": null, "en": null }, "reply_to": null, "recipient_domain_id": null, "email_sender_id": null, "theme": { "colors": { "page_background": null, "card_background": null, "text": null, "secondary_text": null, "button_background": null, "button_text": null, "header_background": null, "header_text": null, "divider": null }, "button_radius": null, "card_radius": null, "card_shadow": null, "font": null, "logo_height": null, "header_align": null, "show_safety_note": null, "show_support_line": null, "show_sender_line": null, "card_footer": null, "card_footer_text": { "is": null, "en": null }, "layout": null, "page_image_asset_id": null, "logo_background": "tile", "show_name_with_logo": null, "show_service_label": null }, "contrasts": { "text_on_card": 18.19, "links_on_card": 6.47, "header_text_on_header": 6.47, "button_text_on_primary": 6.47, "secondary_text_on_card": 5.98 }, "used_by": { "deliveries": 0 }, "created_at": "2026-11-06T09:15:05.856Z", "updated_at": "2026-11-06T09:15:05.856Z", "archived_at": null } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # List brand profiles `GET https://api.parasend.com/v1/brand_profiles` Source: https://parasend.com/docs/api/brand-profiles/list Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Lists your brand profiles: the look of your notices and recipient pages. ## Query parameters - `limit` (integer, optional): Default: `25`. - `cursor` (string, optional) ## Example: List brand profiles ```bash curl "https://api.parasend.com/v1/brand_profiles" \ -H "Authorization: Bearer $PARASEND_KEY" ``` ```js const res = await fetch('https://api.parasend.com/v1/brand_profiles', { method: 'GET', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, }, }); const data = await res.json(); ``` ## Response 200 Paginated list. ```json { "data": [ { "id": "bp_01M6R27T817SW9MENZEV3KSZZX", "object": "brand_profile", "name": "Fyrirtæki ehf.", "is_default": false, "version": 1, "version_id": "bpv_01M6R27T82QZMJ98K4RGVQBRF0", "sender_display_name": "Fyrirtæki ehf.", "sender_display_name_dative": null, "logo": { "asset_id": "bas_01M6R27T7R9RDBHE6X72ZRW3YC", "url": "/v1/brand_assets/bas_01M6R27T7R9RDBHE6X72ZRW3YC/content", "sha256": "f72bdd36e6ce104c53c8f21bd9e618e113a66f4d6729d43a4cb07b3f1373addb", "width": 200, "height": 48 }, "monogram": "F", "sms_sender_name": null, "icon": null, "primary_color": "#1E5BB8", "contrast_ratio": 6.47, "header_style": "band", "support": { "email": null, "phone": null, "url": null }, "expired_message": { "is": null, "en": null }, "reply_to": null, "recipient_domain_id": null, "email_sender_id": null, "theme": { "colors": { "page_background": null, "card_background": null, "text": null, "secondary_text": null, "button_background": null, "button_text": null, "header_background": null, "header_text": null, "divider": null }, "button_radius": null, "card_radius": null, "card_shadow": null, "font": null, "logo_height": null, "header_align": null, "show_safety_note": null, "show_support_line": null, "show_sender_line": null, "card_footer": null, "card_footer_text": { "is": null, "en": null }, "layout": null, "page_image_asset_id": null, "logo_background": "tile", "show_name_with_logo": null, "show_service_label": null }, "contrasts": { "text_on_card": 18.19, "links_on_card": 6.47, "header_text_on_header": 6.47, "button_text_on_primary": 6.47, "secondary_text_on_card": 5.98 }, "used_by": { "deliveries": 0 }, "created_at": "2026-11-06T09:15:05.856Z", "updated_at": "2026-11-06T09:15:05.856Z", "archived_at": null } ], "has_more": false, "next_cursor": null } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Retrieve a brand profile `GET https://api.parasend.com/v1/brand_profiles/{id}` Source: https://parasend.com/docs/api/brand-profiles/retrieve Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Retrieves a brand profile and its current version. ## Path parameters - `id` (string, required): The brand profile's ID, starting `bp_`. ## Example: Retrieve a brand profile ```bash curl "https://api.parasend.com/v1/brand_profiles/bp_01M6R27T817SW9MENZEV3KSZZX" \ -H "Authorization: Bearer $PARASEND_KEY" ``` ```js const res = await fetch('https://api.parasend.com/v1/brand_profiles/bp_01M6R27T817SW9MENZEV3KSZZX', { method: 'GET', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, }, }); const data = await res.json(); ``` ## Response 200 The profile with its current version. ```json { "id": "bp_01M6R27T817SW9MENZEV3KSZZX", "object": "brand_profile", "name": "Fyrirtæki ehf.", "is_default": false, "version": 1, "version_id": "bpv_01M6R27T82QZMJ98K4RGVQBRF0", "sender_display_name": "Fyrirtæki ehf.", "sender_display_name_dative": null, "logo": { "asset_id": "bas_01M6R27T7R9RDBHE6X72ZRW3YC", "url": "/v1/brand_assets/bas_01M6R27T7R9RDBHE6X72ZRW3YC/content", "sha256": "f72bdd36e6ce104c53c8f21bd9e618e113a66f4d6729d43a4cb07b3f1373addb", "width": 200, "height": 48 }, "monogram": "F", "sms_sender_name": null, "icon": null, "primary_color": "#1E5BB8", "contrast_ratio": 6.47, "header_style": "band", "support": { "email": null, "phone": null, "url": null }, "expired_message": { "is": null, "en": null }, "reply_to": null, "recipient_domain_id": null, "email_sender_id": null, "theme": { "colors": { "page_background": null, "card_background": null, "text": null, "secondary_text": null, "button_background": null, "button_text": null, "header_background": null, "header_text": null, "divider": null }, "button_radius": null, "card_radius": null, "card_shadow": null, "font": null, "logo_height": null, "header_align": null, "show_safety_note": null, "show_support_line": null, "show_sender_line": null, "card_footer": null, "card_footer_text": { "is": null, "en": null }, "layout": null, "page_image_asset_id": null, "logo_background": "tile", "show_name_with_logo": null, "show_service_label": null }, "contrasts": { "text_on_card": 18.19, "links_on_card": 6.47, "header_text_on_header": 6.47, "button_text_on_primary": 6.47, "secondary_text_on_card": 5.98 }, "used_by": { "deliveries": 0 }, "created_at": "2026-11-06T09:15:05.856Z", "updated_at": "2026-11-06T09:15:05.856Z", "archived_at": null } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Update a brand profile `PATCH https://api.parasend.com/v1/brand_profiles/{id}` Source: https://parasend.com/docs/api/brand-profiles/update Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Changes a brand profile. A change to its look makes a new version; a delivery keeps the version it was sent with. ## Path parameters - `id` (string, required): The brand profile's ID, starting `bp_`. ## Body (application/json) - `name` (string, required): What the sender calls the profile (the portal list). Not shown to recipients. - `sender_display_name` (string, required): "Delivery from …" and the recipient page. - `primary_color` (string, required): Upper-cased on save. White text on it must reach 4.5:1 (rule B4). - `is_default` (boolean, optional): The organisation's default: used when a delivery names no profile. Takes it from the previous default. Default: `false`. - `sender_display_name_dative` (string · nullable, optional): The sender's name in the Icelandic dative, for “Sending frá …”. Without it the notice says “Sending frá: {name}”. - `logo_asset_id` (string · nullable, optional): bas_… from POST /brand_assets. Null or absent: the monogram is shown. - `icon_asset_id` (string · nullable, optional): `bas_…` of an icon uploaded with POST /brand_assets?kind=icon. Null or left out: the tab shows a monogram on `primary_color`. - `sms_sender_name` (string · nullable, optional): What an SMS to an Icelandic number shows as its sender, once Parasend has approved the name for your company. Verified companies only. Null: PARASEND. - `monogram` (string · nullable, optional): 1–3 letters or digits, upper-cased. Null or absent derives it from sender_display_name (first letters of its first two words, company forms such as hf./ehf. left out). - `header_style` (string, optional): The header of the recipient page and the notice e-mail: `band` (the default), your `primary_color` across the top with the logo on it; `letterhead`, a plain header with a rule in your colour along its top; `minimal`, a plain header with your logo or name. One of: `band`, `letterhead`, `minimal`. Default: `"band"`. - `support` (object · nullable, optional): The sender's own contact, shown to the recipient on the delivery's pages. - `support.email` (string · email · nullable, optional) - `support.phone` (string · nullable, optional): A telephone number, e.g. +354 440 4000. - `support.url` (string · uri · nullable, optional): https:// only. - `expired_message` (object · nullable, optional): Your own words on the page a recipient sees when the link has expired, per language. Plain text. - `expired_message.is` (string · nullable, optional) - `expired_message.en` (string · nullable, optional) - `reply_to` (string · email · nullable, optional): The notice e-mail's Reply-To address. - `recipient_domain_id` (string · nullable, optional): `cdm_…` of your organisation's link domain, or null for Parasend's own. Changing it needs the portal and a fresh sign-in. Links use it only while it is active. - `email_sender_id` (string · nullable, optional): `es_…` of your organisation's e-mail sender, or null for Parasend's address. Changing it needs the portal and a fresh sign-in. It is used only while the sender is active; otherwise the e-mail comes from Parasend's address under your brand's name. - `theme` (object · nullable, optional): The finer look of the recipient page and the e-mail: colours, corners, font, layout, and what is shown. Left out or null: Parasend's own look. In an update, a field left out stays as it is and `theme: null` resets every field. ## Example: Change the colour ```bash curl -X PATCH "https://api.parasend.com/v1/brand_profiles/bp_01M6R27T817SW9MENZEV3KSZZX" \ -H "Authorization: Bearer $PARASEND_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Fyrirtæki ehf.", "sender_display_name": "Fyrirtæki ehf.", "primary_color": "#0E5A8A", "logo_asset_id": "bas_01M6R27T7R9RDBHE6X72ZRW3YC" }' ``` ```js const res = await fetch('https://api.parasend.com/v1/brand_profiles/bp_01M6R27T817SW9MENZEV3KSZZX', { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ name: 'Fyrirtæki ehf.', sender_display_name: 'Fyrirtæki ehf.', primary_color: '#0E5A8A', logo_asset_id: 'bas_01M6R27T7R9RDBHE6X72ZRW3YC', }), }); const data = await res.json(); ``` ## Response 200 Updated. ```json { "id": "bp_01M6R27T817SW9MENZEV3KSZZX", "object": "brand_profile", "name": "Fyrirtæki ehf.", "is_default": false, "version": 2, "version_id": "bpv_01M6R27T8GSQN2QTAEXT32YPMY", "sender_display_name": "Fyrirtæki ehf.", "sender_display_name_dative": null, "logo": { "asset_id": "bas_01M6R27T7R9RDBHE6X72ZRW3YC", "url": "/v1/brand_assets/bas_01M6R27T7R9RDBHE6X72ZRW3YC/content", "sha256": "f72bdd36e6ce104c53c8f21bd9e618e113a66f4d6729d43a4cb07b3f1373addb", "width": 200, "height": 48 }, "monogram": "F", "sms_sender_name": null, "icon": null, "primary_color": "#0E5A8A", "contrast_ratio": 7.37, "header_style": "band", "support": { "email": null, "phone": null, "url": null }, "expired_message": { "is": null, "en": null }, "reply_to": null, "recipient_domain_id": null, "email_sender_id": null, "theme": { "colors": { "page_background": null, "card_background": null, "text": null, "secondary_text": null, "button_background": null, "button_text": null, "header_background": null, "header_text": null, "divider": null }, "button_radius": null, "card_radius": null, "card_shadow": null, "font": null, "logo_height": null, "header_align": null, "show_safety_note": null, "show_support_line": null, "show_sender_line": null, "card_footer": null, "card_footer_text": { "is": null, "en": null }, "layout": null, "page_image_asset_id": null, "logo_background": "tile", "show_name_with_logo": null, "show_service_label": null }, "contrasts": { "text_on_card": 18.19, "links_on_card": 7.37, "header_text_on_header": 7.37, "button_text_on_primary": 7.37, "secondary_text_on_card": 5.98 }, "used_by": { "deliveries": 0 }, "created_at": "2026-11-06T09:15:05.856Z", "updated_at": "2026-11-06T09:15:05.871Z", "archived_at": null } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Delete a brand profile `DELETE https://api.parasend.com/v1/brand_profiles/{id}` Source: https://parasend.com/docs/api/brand-profiles/delete Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Archives the profile: it leaves the list, new deliveries cannot name it, and it is no longer the default. Deliveries created with it keep showing the version they froze. ## Path parameters - `id` (string, required): The brand profile's ID, starting `bp_`. ## Example: Delete a brand profile ```bash curl -X DELETE "https://api.parasend.com/v1/brand_profiles/bp_01M6R27T817SW9MENZEV3KSZZX" \ -H "Authorization: Bearer $PARASEND_KEY" ``` ```js const res = await fetch('https://api.parasend.com/v1/brand_profiles/bp_01M6R27T817SW9MENZEV3KSZZX', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, }, }); const data = await res.json(); ``` ## Response 204 Deleted. Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # List a brand profile's versions `GET https://api.parasend.com/v1/brand_profiles/{id}/versions` Source: https://parasend.com/docs/api/brand-profiles/versions Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Every version, newest first. Versions never change once written. ## Path parameters - `id` (string, required): The brand profile's ID, starting `bp_`. ## Query parameters - `limit` (integer, optional): Default: `25`. - `cursor` (string, optional) ## Example: List a brand profile's versions ```bash curl "https://api.parasend.com/v1/brand_profiles/bp_01M6R27T817SW9MENZEV3KSZZX/versions" \ -H "Authorization: Bearer $PARASEND_KEY" ``` ```js const res = await fetch('https://api.parasend.com/v1/brand_profiles/bp_01M6R27T817SW9MENZEV3KSZZX/versions', { method: 'GET', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, }, }); const data = await res.json(); ``` ## Response 200 Paginated list. ```json { "data": [ { "id": "bpv_01M6R27T8GSQN2QTAEXT32YPMY", "object": "brand_profile_version", "profile_id": "bp_01M6R27T817SW9MENZEV3KSZZX", "version": 2, "sender_display_name": "Fyrirtæki ehf.", "sender_display_name_dative": null, "logo": { "asset_id": "bas_01M6R27T7R9RDBHE6X72ZRW3YC", "url": "/v1/brand_assets/bas_01M6R27T7R9RDBHE6X72ZRW3YC/content", "sha256": "f72bdd36e6ce104c53c8f21bd9e618e113a66f4d6729d43a4cb07b3f1373addb", "width": 200, "height": 48 }, "monogram": "F", "sms_sender_name": null, "icon": null, "primary_color": "#0E5A8A", "contrast_ratio": 7.37, "header_style": "band", "support": { "email": null, "phone": null, "url": null }, "expired_message": { "is": null, "en": null }, "reply_to": null, "recipient_domain_id": null, "email_sender_id": null, "theme": { "colors": { "page_background": null, "card_background": null, "text": null, "secondary_text": null, "button_background": null, "button_text": null, "header_background": null, "header_text": null, "divider": null }, "button_radius": null, "card_radius": null, "card_shadow": null, "font": null, "logo_height": null, "header_align": null, "show_safety_note": null, "show_support_line": null, "show_sender_line": null, "card_footer": null, "card_footer_text": { "is": null, "en": null }, "layout": null, "page_image_asset_id": null, "logo_background": "tile", "show_name_with_logo": null, "show_service_label": null }, "contrasts": { "text_on_card": 18.19, "links_on_card": 7.37, "header_text_on_header": 7.37, "button_text_on_primary": 7.37, "secondary_text_on_card": 5.98 }, "created_at": "2026-11-06T09:15:05.871Z", "created_by": { "user_id": "usr_01M6R27SYD4B50414S0BZ4KBKR", "via": "api_key" } }, { "id": "bpv_01M6R27T82QZMJ98K4RGVQBRF0", "object": "brand_profile_version", "profile_id": "bp_01M6R27T817SW9MENZEV3KSZZX", "version": 1, "sender_display_name": "Fyrirtæki ehf.", "sender_display_name_dative": null, "logo": { "asset_id": "bas_01M6R27T7R9RDBHE6X72ZRW3YC", "url": "/v1/brand_assets/bas_01M6R27T7R9RDBHE6X72ZRW3YC/content", "sha256": "f72bdd36e6ce104c53c8f21bd9e618e113a66f4d6729d43a4cb07b3f1373addb", "width": 200, "height": 48 }, "monogram": "F", "sms_sender_name": null, "icon": null, "primary_color": "#1E5BB8", "contrast_ratio": 6.47, "header_style": "band", "support": { "email": null, "phone": null, "url": null }, "expired_message": { "is": null, "en": null }, "reply_to": null, "recipient_domain_id": null, "email_sender_id": null, "theme": { "colors": { "page_background": null, "card_background": null, "text": null, "secondary_text": null, "button_background": null, "button_text": null, "header_background": null, "header_text": null, "divider": null }, "button_radius": null, "card_radius": null, "card_shadow": null, "font": null, "logo_height": null, "header_align": null, "show_safety_note": null, "show_support_line": null, "show_sender_line": null, "card_footer": null, "card_footer_text": { "is": null, "en": null }, "layout": null, "page_image_asset_id": null, "logo_background": "tile", "show_name_with_logo": null, "show_service_label": null }, "contrasts": { "text_on_card": 18.19, "links_on_card": 6.47, "header_text_on_header": 6.47, "button_text_on_primary": 6.47, "secondary_text_on_card": 5.98 }, "created_at": "2026-11-06T09:15:05.856Z", "created_by": { "user_id": "usr_01M6R27SYD4B50414S0BZ4KBKR", "via": "api_key" } } ], "has_more": false, "next_cursor": null } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Preview a brand profile `POST https://api.parasend.com/v1/brand_profiles/preview` Source: https://parasend.com/docs/api/brand-profiles/preview Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Send an `Idempotency-Key` header: a retry with the same key returns the first answer. Shows a draft brand profile as it would be sent and seen, with a sample delivery that says it is one: the notice e-mail and the recipient page's content. Every field is checked as POST /brand_profiles checks it, with the same errors, except that colours that fail the contrast check are drawn anyway and the refusal a save would give is in `contrast_error`. Logos and photos must be your organisation's and of the right kind (400 `brand_asset_reference_invalid`). No `Idempotency-Key` is needed. ## Query parameters - `locale` (string, optional): The sample delivery's language, which sets the language of the e-mail and the page. One of: `is`, `en`. Default: `"is"`. ## Body (application/json) ## Example: Preview a profile before saving it ```bash curl -X POST "https://api.parasend.com/v1/brand_profiles/preview" \ -H "Authorization: Bearer $PARASEND_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "sender_display_name": "Fyrirtæki ehf.", "primary_color": "#1E5BB8" }' ``` ```js const res = await fetch('https://api.parasend.com/v1/brand_profiles/preview', { method: 'POST', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, 'Idempotency-Key': crypto.randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ sender_display_name: 'Fyrirtæki ehf.', primary_color: '#1E5BB8', }), }); const data = await res.json(); ``` ## Response 200 The draft, drawn. ```json { "object": "brand_profile_preview", "locale": "is", "contrasts": { "button_text_on_primary": 6.47, "links_on_card": 6.47, "text_on_card": 18.19, "secondary_text_on_card": 5.98, "header_text_on_header": 6.47 }, "contrast_error": null, "email": { "template": "invitation.email", "version": 29, "from_name": "Sending frá: Fyrirtæki ehf.", "subject": "Sýnishorn af sendingu", "html": "Sýnishorn af sendingu
Opnaðu sendinguna og staðfestu móttöku. Aðgengileg til 20. nóvember 2026.
FFyrirtæki ehf.

Rekjanleg rafræn afhendingarþjónusta

Fyrirtæki ehf. hefur sent þér 1 skjal með Parasend

Sýnishorn af sendingu

1 skjal · aðgengilegt til 20. nóvember 2026

Skilaboð frá sendanda

Þetta er sýnishorn. Svona birtist sendingin viðtakanda.

Opna sendinguna

Þú staðfestir móttöku með rafrænum skilríkjum. Það tekur innan við mínútu.

Virkar hnappurinn ekki? Opnaðu þennan hlekk í vafra:
https://parasend.com/d/synishorn

 
\"\"Er þetta öruggt? Parasend biður þig aldrei um lykilorð eða kortaupplýsingar. Móttaka sendingarinnar er skráð.

In English: Fyrirtæki ehf. has sent you 1 document through Parasend, an electronic registered delivery service. Open the delivery with the button above and confirm receipt.

Þessi tilkynning er frá Parasend. Sendandi: Fyrirtæki ehf.
Parasend er rekjanleg rafræn afhendingarþjónusta sem Justikal ehf. rekur, Akralind 3, 201 Kópavogi.
Persónuvernd
", "text": "Fyrirtæki ehf. hefur sent þér 1 skjal með Parasend.\n\nSýnishorn af sendingu\n1 skjal · aðgengilegt til 20. nóvember 2026\n\nSkilaboð frá sendanda:\nÞetta er sýnishorn. Svona birtist sendingin viðtakanda.\n\nOpna sendinguna: https://parasend.com/d/synishorn\nÞú staðfestir móttöku með rafrænum skilríkjum. Það tekur innan við mínútu.\n\nEr þetta öruggt? Parasend biður þig aldrei um lykilorð eða kortaupplýsingar. Móttaka sendingarinnar er skráð.\n\nIn English: Fyrirtæki ehf. has sent you 1 document through Parasend, an electronic registered delivery service. Open the delivery with the button above and confirm receipt.\n\n—\nÞessi tilkynning er frá Parasend. Sendandi: Fyrirtæki ehf.\nParasend er rekjanleg rafræn afhendingarþjónusta sem Justikal ehf. rekur, Akralind 3, 201 Kópavogi.\n", "images": { "safety-shield-v1": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAGtUlEQVR4nOyaa2wUVRTHz5nZbWlBaXeLH9SofNGCkIghwUBbSsQHBsEulgRIBGLkER4GY4C2aBpDu1RD0FZFwAh8ICoru1SJxoTE2hYwKlEjLxsQX0SM+0Ab2C3dudczC8Wd2e32zuzMQkp/X2b3zn2c/9xzz8x9OOAmwwE3GUOCBztDgu2muGZ2uQzyQvW3AsruiLe1A3IIQi5YNSPfNWLYfAlwFbU4QXOPw3ccoTnUHX0fWj7rAZuxVXDJ2lm3c1leQT+XI2Jxpryc8whdtsbijpaLr/nOg03YIri4rqpM5rCaA1QhoKFhw4HHySi/gtASaQh0gsVYKthVU7UYEV4gkePAAkj8MTJxc6jRvwssImvBfW5LQpdQdSWZc/Mg57BdddsC7JHMlMvW3U0LNuS2mQJTpoCWUk327m5MsHHjAmRcs6hxlj3EDAgLdq2bc78k80/o5+jMOcn9AHcAXn4r1HDgHJjAXTfzDuB5KxD4cwO7O5wFKT4juPHjn0AAYcHuWs9Oyryo3wx2vE9FPYrzHUFvYAkIIPzKQM5HA6Z7PnyvwrE54vUfAquhBxcG2Em/dhbVeSocnK8kS6rT5LwXBDH9aUlR8xRIl6ebdVujXGjwt9OlPeHuLO8gPftSMIEEJqHx9VeuxCajtqm2DSYZmi0NdoYED3ZuOMHF62beJaFjFpd5a7ih9XewGNNR2g6Ka6vGyVLetyhJLRKTj7rWeu4Ei7lhBJdsmHWfDPAFve9GJRLoKslQDxZzQ7i0KhaY3Cnw3Zw1172H1UkJV+RDerG05BNiynXsYZqyKbov6X4flmjgcdXMHovAvqT1Lreusb85k6aFX933Rz9FJdDZBoKI9zDiP7pGitJlU8VqAg/1YLp8ajqC1K4Xm+hZhtPCTfuOQz/QrEy/IHgBBBEWjBzC2gR0pa1Qcr6iDTysLTFGk1D/q+kpPUtzaZSVKZnEXqkW3Lr/IRBEWDBDrhFMD8AlVpLGJgWkPtH9BygeBEkpE5nI0wKD1itQ1xkZEO9hwIguoUCdoOvzcYm9RO6uM4DEKY4OV43nafWaIpbGLONsqojYUfXVI6hH87Sm2NDDHFmXPq3olvwx+jQ1SHEFK64urCdbNUpC8F1z9/9rDoIcLw97W0+AACymjEm1DbpAEGHBvQzbUgpzqTJdXnUMMsam0kJexmBy5dUjVYquR6mgxCv1ab1Oh/D+lLDgbm8gRK76q6ZxTG28j0hT649MYRWp7n0V9dUDrGKgAJVaDit1CWf+rfdZP4ZVaF3rsPY/PA71Txb2l18Vnda9DbpxH7TofwuVfVhTE8fDRuowJJgjHtEkIOa7Y85Fmcok3Buhgizrumphl1E3vta+LC9W29SmsiNgAGOC4xAgF2WaROTLBioXaQwcC+Y7xgNj89WrYTfua0pdrk22hyzq6cV9YADDWy20Pv0RFZqjaRjZpFDD/q/BRoprPFNkBN0OBv8g2BiYBwYwPHlAxt5MTcMmsBkS26hPYxyawSCGBQc37W+jIKQNNoiVJeurFoBNkFctokuFNpV/H/YGDI1fFXPTQ47PpybBG8XrqkeCxdxaX+2iwfq6Pp12O1aCCUwJDm3yH6Re3p2cpk4EJDnuoxmsdXNsqsvZo3xIuwzaB0l7SWa3dkwbx/Njq0G3A0BR9BF3T9W7YBFU13skdrqmXYBzwd6La8AkMpgk2na6J7+89Djt7GnGLvX0hIKyMdFo58msNtfcNZ4NVFeKMJopzY02HTgJJjEtWCXWcer0sLLSbjLsMe0dPn14xVi81HGyDYyDFKQaqWdf1t+gqLwm7PXvgSyw5FAL9cbbZOByfTp9GLSGoo55sMUXFaroxUeHlzhH7CWrnkhzd3uw0b8UssSqUzzUK1Wfq2M45Q6H0zRHXjDQh4mrbvZkZNIe8pZ70tTxadDrn5n4lSVZuXQy0QdG7y3Md9CqBmrXsFBdGcFnh5eXFl2aOL4dvjoR19xfU13gnla6BTm+k/7wGvcFQ7/NhaN/Ci/UZcLyg2nU03XU0xvT3uRwngHfehkcW6E3jsOcoJ7SW0aR7raUrARdakPewCawEFtO4pWsf6qSS9IuqvxuMMdZhSsL7Th4aplLJ3Op89Qv0YnjthU6WSG9Riah4IOlPlUo+G0Jdkc9sc0HfgYbsP00ravO85DE6KsMMePBk8SZEQ7P0FfcN2AjuTk+XF/pcMVcc1CC1dTgZO1N3skYNIfPOPzg81kSmDKRG8FJjKz1POgEWKq6r8L4tgtNgR8gh+Rc8PVm6MjDYGdI8GDnPwAAAP//Q/PBygAAAAZJREFUAwBNHsTsz47Z7QAAAABJRU5ErkJggg==" } }, "recipient": { "landing": { "delivery_session": null, "state": "link_opened", "attempts_remaining": 5, "certificate_url": null, "passkey_offer": false, "identified_as": null, "delivery": { "title": "Sýnishorn af sendingu", "message": "Þetta er sýnishorn. Svona birtist sendingin viðtakanda.", "language": "is", "sender_display_name": "Fyrirtæki ehf.", "sender_unverified": false, "file_count": 1, "total_bytes": 245760, "files": [ { "name": "Synishorn.pdf", "path": "Synishorn.pdf", "bytes": 245760, "download_url": null } ], "zip_download_url": null, "content_type": "files", "expires_at": "2026-11-20T09:15:05.878Z", "auth_methods_offered": [ "audkenni", "smart_id", "passkey" ], "brand": { "name": "Parasend", "powered_by": "Parasend · by Justikal", "profile": { "version_id": null, "sender_display_name": "Fyrirtæki ehf.", "header_style": "band", "primary_color": "#1E5BB8", "monogram": "F", "logo": null, "icon": null, "support": null, "expired_message": { "is": null, "en": null }, "theme": { "colors": { "page_background": "#F5F6FA", "card_background": "#FFFFFF", "text": "#0E1526", "secondary_text": "#5B6472", "primary": "#1E5BB8", "button_background": "#1E5BB8", "button_text": "#FFFFFF", "header_background": "#1E5BB8", "header_text": "#FFFFFF", "divider": "#E6E9F2" }, "button_radius": 12, "card_radius": 14, "card_shadow": true, "font": "inter", "logo_height": null, "header_align": "left", "logo_background": "tile", "show_name_with_logo": false, "show_safety_note": true, "show_support_line": true, "show_sender_line": true, "card_footer": "default", "card_footer_text": { "is": null, "en": null }, "layout": "card", "page_image": null } } }, "consent_prompt_eligible": false } }, "unlocked": { "delivery_session": null, "state": "unlocked", "attempts_remaining": null, "certificate_url": "/v1/recipient/evidence/document", "passkey_offer": false, "identified_as": null, "delivery": { "title": "Sýnishorn af sendingu", "message": "Þetta er sýnishorn. Svona birtist sendingin viðtakanda.", "language": "is", "sender_display_name": "Fyrirtæki ehf.", "sender_unverified": false, "file_count": 1, "total_bytes": 245760, "files": [ { "name": "Synishorn.pdf", "path": "Synishorn.pdf", "bytes": 245760, "download_url": "/v1/recipient/files/sample/content" } ], "zip_download_url": null, "content_type": "files", "expires_at": "2026-11-20T09:15:05.878Z", "auth_methods_offered": [ "audkenni", "smart_id", "passkey" ], "brand": { "name": "Parasend", "powered_by": "Parasend · by Justikal", "profile": { "version_id": null, "sender_display_name": "Fyrirtæki ehf.", "header_style": "band", "primary_color": "#1E5BB8", "monogram": "F", "logo": null, "icon": null, "support": null, "expired_message": { "is": null, "en": null }, "theme": { "colors": { "page_background": "#F5F6FA", "card_background": "#FFFFFF", "text": "#0E1526", "secondary_text": "#5B6472", "primary": "#1E5BB8", "button_background": "#1E5BB8", "button_text": "#FFFFFF", "header_background": "#1E5BB8", "header_text": "#FFFFFF", "divider": "#E6E9F2" }, "button_radius": 12, "card_radius": 14, "card_shadow": true, "font": "inter", "logo_height": null, "header_align": "left", "logo_background": "tile", "show_name_with_logo": false, "show_safety_note": true, "show_support_line": true, "show_sender_line": true, "card_footer": "default", "card_footer_text": { "is": null, "en": null }, "layout": "card", "page_image": null } } }, "consent_prompt_eligible": false } }, "expired": { "error": { "type": "authentication_error", "code": "delivery_link_expired", "message": "This delivery link has expired.", "request_id": "req_848634cb0db0007a19364368", "brand": { "name": "Parasend", "powered_by": "Parasend · by Justikal", "profile": { "version_id": null, "sender_display_name": "Fyrirtæki ehf.", "header_style": "band", "primary_color": "#1E5BB8", "monogram": "F", "logo": null, "icon": null, "support": null, "expired_message": { "is": null, "en": null }, "theme": { "colors": { "page_background": "#F5F6FA", "card_background": "#FFFFFF", "text": "#0E1526", "secondary_text": "#5B6472", "primary": "#1E5BB8", "button_background": "#1E5BB8", "button_text": "#FFFFFF", "header_background": "#1E5BB8", "header_text": "#FFFFFF", "divider": "#E6E9F2" }, "button_radius": 12, "card_radius": 14, "card_shadow": true, "font": "inter", "logo_height": null, "header_align": "left", "logo_background": "tile", "show_name_with_logo": false, "show_safety_note": true, "show_support_line": true, "show_sender_line": true, "card_footer": "default", "card_footer_text": { "is": null, "en": null }, "layout": "card", "page_image": null } } } } }, "assets": {} } } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Upload a brand asset `POST https://api.parasend.com/v1/brand_assets` Source: https://parasend.com/docs/api/brand-assets/upload Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Send an `Idempotency-Key` header: a retry with the same key returns the first answer. Uploads a logo, a page photo or a tab icon; the image is the request body. Needs `brand_profiles.create`. **Logo** (the default): PNG or SVG, at most 1 MB (413 `logo_too_large`), drawn into a PNG that fits 200 × 48 CSS pixels at 3×, with no metadata. A file that is not what its type says is 415 `logo_type_unsupported`; an unsafe SVG is 400 `logo_svg_unsafe`. The same picture again answers 200 with the logo you already have. **Photo** (`kind=image`): JPEG, PNG or WebP, at most 5 MB (413 `image_too_large`) and at least 640 × 320 pixels (400 `image_too_small`). It is turned the right way up and drawn into two WebP renditions, at most 1600 and 800 pixels wide, with no metadata; the upload itself is not kept. **Icon** (`kind=icon`): a square PNG of at least 180 × 180 pixels (400 `icon_too_small`) or a square SVG, at most 1 MB (413 `icon_too_large`), drawn into 180 and 32 pixel PNGs. ## Query parameters - `kind` (string, optional): One of: `logo`, `image`, `icon`. Default: `"logo"`. ## Body (image/png) The request body is the file itself (image/png, image/svg+xml, image/jpeg, image/webp). ## Example: Upload a PNG logo ```bash curl -X POST "https://api.parasend.com/v1/brand_assets" \ -H "Authorization: Bearer $PARASEND_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: image/png" \ --data-binary @logo.png ``` ```js import { readFile } from 'node:fs/promises'; const res = await fetch('https://api.parasend.com/v1/brand_assets', { method: 'POST', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, 'Idempotency-Key': crypto.randomUUID(), 'Content-Type': 'image/png', }, body: await readFile('logo.png'), }); const data = await res.json(); ``` ## Response 200 The organisation already had this logo. Content type: application/json. ## Response 201 Uploaded. ```json { "id": "bas_01M6R27T7R9RDBHE6X72ZRW3YC", "object": "brand_asset", "kind": "logo", "content_type": "image/png", "source_type": "image/png", "sha256": "f72bdd36e6ce104c53c8f21bd9e618e113a66f4d6729d43a4cb07b3f1373addb", "width": 200, "height": 48, "url": "/v1/brand_assets/bas_01M6R27T7R9RDBHE6X72ZRW3YC/content", "transparent_background": false, "created_at": "2026-11-06T09:15:05.848Z" } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Retrieve a brand asset `GET https://api.parasend.com/v1/brand_assets/{id}` Source: https://parasend.com/docs/api/brand-assets/retrieve Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Retrieves a brand asset's details. ## Path parameters - `id` (string, required): The brand asset's ID, starting `bas_`. ## Example: Retrieve a brand asset ```bash curl "https://api.parasend.com/v1/brand_assets/bas_01M6R27T7R9RDBHE6X72ZRW3YC" \ -H "Authorization: Bearer $PARASEND_KEY" ``` ```js const res = await fetch('https://api.parasend.com/v1/brand_assets/bas_01M6R27T7R9RDBHE6X72ZRW3YC', { method: 'GET', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, }, }); const data = await res.json(); ``` ## Response 200 The logo. ```json { "id": "bas_01M6R27T7R9RDBHE6X72ZRW3YC", "object": "brand_asset", "kind": "logo", "content_type": "image/png", "source_type": "image/png", "sha256": "f72bdd36e6ce104c53c8f21bd9e618e113a66f4d6729d43a4cb07b3f1373addb", "width": 200, "height": 48, "url": "/v1/brand_assets/bas_01M6R27T7R9RDBHE6X72ZRW3YC/content", "transparent_background": false, "created_at": "2026-11-06T09:15:05.848Z" } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Download a brand asset `GET https://api.parasend.com/v1/brand_assets/{id}/content` Source: https://parasend.com/docs/api/brand-assets/download Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Downloads an asset. A logo: its PNG rendition, the only form Parasend serves. A photo: its large WebP, or with `size=small` the small one. An icon: its 180 pixel PNG, or with `size=small` the 32 pixel one. Cached privately for a day. ## Path parameters - `id` (string, required): The brand asset's ID, starting `bas_`. ## Query parameters - `size` (string, optional): One of: `large`, `small`. Default: `"large"`. ## Example: Download a brand asset ```bash curl "https://api.parasend.com/v1/brand_assets/bas_01M6R27T7R9RDBHE6X72ZRW3YC/content" \ -H "Authorization: Bearer $PARASEND_KEY" ``` ```js const res = await fetch('https://api.parasend.com/v1/brand_assets/bas_01M6R27T7R9RDBHE6X72ZRW3YC/content', { method: 'GET', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, }, }); const data = await res.json(); ``` ## Response 200 The PNG, or the WebP. Content type: image/png, image/webp. Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # List link domains `GET https://api.parasend.com/v1/recipient_domains` Source: https://parasend.com/docs/api/link-domains/list Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Lists your organisation's link domains: your own host names for the links in Parasend's messages. ## Example: List link domains ```bash curl "https://api.parasend.com/v1/recipient_domains" \ -H "Authorization: Bearer $PARASEND_KEY" ``` ```js const res = await fetch('https://api.parasend.com/v1/recipient_domains', { method: 'GET', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, }, }); const data = await res.json(); ``` ## Response 200 The list (never paginated; an organisation has a handful). ```json { "object": "list", "data": [], "has_more": false } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Retrieve a link domain `GET https://api.parasend.com/v1/recipient_domains/{id}` Source: https://parasend.com/docs/api/link-domains/retrieve Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Retrieves a link domain, with the DNS records it needs and where each one stands. ## Path parameters - `id` (string, required) ## Example: Retrieve a link domain ```bash curl "https://api.parasend.com/v1/recipient_domains/cdm_01M6P3RM6E8G0J2M4P6R8T0W2Y" \ -H "Authorization: Bearer $PARASEND_KEY" ``` ```js const res = await fetch('https://api.parasend.com/v1/recipient_domains/cdm_01M6P3RM6E8G0J2M4P6R8T0W2Y', { method: 'GET', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, }, }); const data = await res.json(); ``` ## Response 200 The domain, removed ones included. Content type: application/json. Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Check a link domain `POST https://api.parasend.com/v1/recipient_domains/{id}/check` Source: https://parasend.com/docs/api/link-domains/check Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). Send an `Idempotency-Key` header: a retry with the same key returns the first answer. Checks the domain's DNS records now: at most once every 30 seconds per domain, and 30 times an hour per organisation (429 `too_many_checks`). A failed domain whose records are right again goes back to verifying. A removed domain is 409 `recipient_domain_removed`. ## Path parameters - `id` (string, required) ## Example: Check a link domain ```bash curl -X POST "https://api.parasend.com/v1/recipient_domains/cdm_01M6P3RM6E8G0J2M4P6R8T0W2Y/check" \ -H "Authorization: Bearer $PARASEND_KEY" \ -H "Idempotency-Key: $(uuidgen)" ``` ```js const res = await fetch('https://api.parasend.com/v1/recipient_domains/cdm_01M6P3RM6E8G0J2M4P6R8T0W2Y/check', { method: 'POST', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, 'Idempotency-Key': crypto.randomUUID(), }, }); const data = await res.json(); ``` ## Response 200 The domain as it now stands. Content type: application/json. Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Get the price list `GET https://api.parasend.com/v1/billing/prices` Source: https://parasend.com/docs/api/billing/prices Authentication: `Authorization: Bearer ` (a `ps_test_` key in test mode, `ps_live_` in live mode). The public self-serve prices on a date (default today), excluding VAT: the three self-serve doors (pay as you go, plans 500 / 1,000 / 5,000) and the per-use prices that are the same on every plan. Enterprise levels are contract data and never listed. Any signed-in member or API key may read it. `price_list_not_found` (404) when no list is in effect for the currency on that date (there is no EUR list yet). ## Query parameters - `currency` (string, optional): One of: `ISK`, `EUR`. Default: `"ISK"`. - `on` (string · date, optional): YYYY-MM-DD; default today (UTC = Iceland). ## Example: Get the price list ```bash curl "https://api.parasend.com/v1/billing/prices" \ -H "Authorization: Bearer $PARASEND_KEY" ``` ```js const res = await fetch('https://api.parasend.com/v1/billing/prices', { method: 'GET', headers: { Authorization: `Bearer ${process.env.PARASEND_KEY}`, }, }); const data = await res.json(); ``` ## Response 200 The price list. ```json { "object": "price_list", "currency": "ISK", "on": "2026-11-06", "prices_exclude_vat": true, "vat_percent": 24, "plans": [ { "plan": "payg", "door": 1, "rails": [ "card" ], "monthly_fee": null, "included_deliveries": null, "delivery_price": 311, "request_price": 311 }, { "plan": "p500", "door": 2, "rails": [ "card", "bank_claim" ], "monthly_fee": 143500, "included_deliveries": 500, "delivery_price": 287, "request_price": 287 }, { "plan": "p1000", "door": 2, "rails": [ "card", "bank_claim" ], "monthly_fee": 263000, "included_deliveries": 1000, "delivery_price": 263, "request_price": 263 }, { "plan": "p5000", "door": 2, "rails": [ "card", "bank_claim" ], "monthly_fee": 1200000, "included_deliveries": 5000, "delivery_price": 240, "request_price": 240 } ], "per_use": [ { "product": "PS-SECRET", "unit_amount": 39 }, { "product": "PS-QUAL", "unit_amount": 340 }, { "product": "PS-EID", "unit_amount": 29 }, { "product": "PS-SMS-IS", "unit_amount": 25 }, { "product": "PS-VIDEO", "unit_amount": 215 } ], "sms_international": [ { "country": "AD", "unit_amount": 50 }, { "country": "AG", "unit_amount": 50 }, { "country": "AI", "unit_amount": 25 }, { "country": "AL", "unit_amount": 50 }, { "country": "AO", "unit_amount": 25 }, { "country": "AT", "unit_amount": 50 }, { "country": "BG", "unit_amount": 50 }, { "country": "BH", "unit_amount": 25 }, { "country": "BN", "unit_amount": 25 }, { "country": "BO", "unit_amount": 50 }, { "country": "BW", "unit_amount": 50 }, { "country": "CH", "unit_amount": 25 }, { "country": "CK", "unit_amount": 50 }, { "country": "CV", "unit_amount": 50 }, { "country": "CY", "unit_amount": 25 }, { "country": "CZ", "unit_amount": 25 }, { "country": "DE", "unit_amount": 50 }, { "country": "DJ", "unit_amount": 50 }, { "country": "DK", "unit_amount": 25 }, { "country": "EE", "unit_amount": 25 }, { "country": "ER", "unit_amount": 50 }, { "country": "FI", "unit_amount": 50 }, { "country": "FO", "unit_amount": 25 }, { "country": "FR", "unit_amount": 25 }, { "country": "GE", "unit_amount": 50 }, { "country": "GF", "unit_amount": 50 }, { "country": "GG", "unit_amount": 25 }, { "country": "GI", "unit_amount": 25 }, { "country": "GL", "unit_amount": 25 }, { "country": "GP", "unit_amount": 50 }, { "country": "GR", "unit_amount": 25 }, { "country": "GY", "unit_amount": 50 }, { "country": "HK", "unit_amount": 25 }, { "country": "HR", "unit_amount": 50 }, { "country": "IM", "unit_amount": 25 }, { "country": "IT", "unit_amount": 25 }, { "country": "JE", "unit_amount": 25 }, { "country": "JM", "unit_amount": 50 }, { "country": "JP", "unit_amount": 25 }, { "country": "LI", "unit_amount": 25 }, { "country": "LT", "unit_amount": 25 }, { "country": "LU", "unit_amount": 25 }, { "country": "LV", "unit_amount": 25 }, { "country": "MD", "unit_amount": 50 }, { "country": "ME", "unit_amount": 50 }, { "country": "MK", "unit_amount": 25 }, { "country": "MO", "unit_amount": 25 }, { "country": "MQ", "unit_amount": 50 }, { "country": "MS", "unit_amount": 25 }, { "country": "MT", "unit_amount": 25 }, { "country": "MU", "unit_amount": 50 }, { "country": "MX", "unit_amount": 25 }, { "country": "NA", "unit_amount": 25 }, { "country": "NC", "unit_amount": 50 }, { "country": "NF", "unit_amount": 25 }, { "country": "NL", "unit_amount": 50 }, { "country": "NO", "unit_amount": 25 }, { "country": "NU", "unit_amount": 50 }, { "country": "PA", "unit_amount": 50 }, { "country": "PF", "unit_amount": 50 }, { "country": "PL", "unit_amount": 25 }, { "country": "PT", "unit_amount": 25 }, { "country": "PW", "unit_amount": 25 }, { "country": "RE", "unit_amount": 50 }, { "country": "RO", "unit_amount": 25 }, { "country": "SB", "unit_amount": 25 }, { "country": "SE", "unit_amount": 25 }, { "country": "SI", "unit_amount": 50 }, { "country": "SK", "unit_amount": 25 }, { "country": "SM", "unit_amount": 50 }, { "country": "SR", "unit_amount": 50 }, { "country": "ST", "unit_amount": 50 }, { "country": "TL", "unit_amount": 50 }, { "country": "TO", "unit_amount": 50 }, { "country": "TV", "unit_amount": 25 }, { "country": "UA", "unit_amount": 50 }, { "country": "VU", "unit_amount": 50 }, { "country": "WS", "unit_amount": 50 } ] } ``` Errors: one shape, `{"error": {type, code, message, param, request_id}}`; every code is listed at https://parasend.com/docs/errors. --- # Error codes Source: https://parasend.com/docs/errors Every error: `{"error": {type, code, message, param, request_id}}`. `type` sets the HTTP status; `code` is the exact cause. | Code | Status | Type | Cause | |---|---|---|---| | `account_deletion_not_scheduled` | 409 | conflict | No deletion is scheduled. | | `account_deletion_scheduled` | 403 | permission_error | A deletion is already scheduled. | | `account_not_found` | 404 | not_found | No account with that ID. | | `address_patch_state_invalid` | 409 | conflict | The delivery has moved on too far for its postal address to be changed. | | `admin_host_only` | 403 | permission_error | The staff admin answers only on its own address. | | `already_reminded` | 409 | conflict | This request has been reminded already. | | `api_key_expired` | 401 | authentication_error | The API key has passed its expiry date. Make a new key in the portal. | | `api_key_invalid` | 401 | authentication_error | The API key is not one Parasend issued: check for a typo or a missing part. | | `api_key_ip_not_allowed` | 403 | permission_error | This API key may not be used from this IP address. | | `api_key_missing` | 401 | authentication_error | Authentication required. | | `api_key_revoked` | 401 | authentication_error | This API key has been revoked. | | `approval_already_requested` | 409 | conflict | This person has an open request already. Remind them instead. | | `approval_request_closed` | 409 | conflict | This request is no longer open. | | `approval_request_expired` | 409 | conflict | This request has expired. | | `approval_request_not_found` | 404 | not_found | No such approval request. | | `auth_assurance_insufficient` | 403 | permission_error | The recipient's electronic ID does not reach the level this delivery requires. Use another method. | | `auth_attempts_exhausted` | 403 | permission_error | Too many failed attempts; try again after 24 hours. | | `auth_cancelled_by_user` | 400 | invalid_request | The authentication was cancelled. | | `auth_method_incompatible` | 409 | conflict | An authentication method you allowed cannot assert the identity this delivery requires. | | `auth_method_not_offered` | 400 | invalid_request | The portal signs in with audkenni or smart_id. | | `auth_method_unknown` | 400 | invalid_request | method is not a known authentication method. | | `auth_not_complete` | 409 | conflict | The identity provider has not finished the authentication yet; try again in a moment. | | `auth_provider_unavailable` | 503 | api_error | This person has no personal code on file to confirm with. | | `bank_claim_not_available` | 400 | invalid_request | Bank claims are for Icelandic organisations; pay by card. | | `batch_add_limit_exceeded` | 400 | invalid_request | Too many deliveries in one call. Send at most 1,000 per call, as many calls as you need. | | `batch_closed` | 409 | conflict | The batch is closed and takes no more deliveries. | | `batch_empty_finalize` | 400 | invalid_request | This batch has no deliveries yet; add some before sending it out. | | `batch_finalized` | 409 | conflict | This batch has been sent out; it takes no more deliveries. Start a new batch, or duplicate this one. | | `batch_not_found` | 404 | not_found | No batch with that ID, in the mode of your key. | | `billing_country_locked` | 409 | conflict | This account has been invoiced in another country, so its country cannot change. Contact Parasend Support. | | `billing_setup_required` | 403 | permission_error | All free deliveries are used. To keep sending, move to Pay-as-you-go and add a card in the portal. | | `body_invalid` | 400 | invalid_request | The request body could not be parsed. | | `brand_asset_not_found` | 404 | not_found | No brand asset with that ID. | | `brand_asset_reference_invalid` | 400 | invalid_request | A logo, photo or icon the brand profile names is not your organisation's, or not of the right kind. | | `brand_profile_archived` | 409 | conflict | This brand profile is archived; create a new one instead. | | `brand_profile_not_found` | 404 | not_found | No brand profile with that ID. | | `break_glass_limit` | 400 | invalid_request | At most two admins besides the owner can sign in with eID while SSO is required. | | `break_glass_not_admin` | 400 | invalid_request | Only admins can be named (the owner always can sign in with eID). | | `card_declined` | 400 | invalid_request | The card was declined. Try another card. | | `card_mandate_changed` | 409 | conflict | The card authorisation has changed. Read it again before accepting. | | `card_mandate_required` | 400 | invalid_request | Accept the card authorisation before adding a card. | | `card_not_saved` | 400 | invalid_request | Teya does not know this card check. | | `card_payment_in_progress` | 409 | conflict | A payment of this invoice is already under way. Wait a moment and look again. | | `card_payments_not_available` | 400 | invalid_request | Card payments are not available here yet. | | `card_payments_not_enabled` | 503 | api_error | Saving cards is not switched on for Parasend at the card processor yet. Try again later. | | `card_processor_unavailable` | 503 | api_error | The card processor did not answer. Try again in a moment. | | `card_session_failed` | 409 | conflict | This card was not saved. Open the card form again. | | `card_session_not_found` | 404 | not_found | No such card session. | | `card_session_pending` | 409 | conflict | The card check is not finished yet. Try again in a moment. | | `certificate_language_fixed` | 409 | conflict | A delivery's certificate is in the language the company had set when it was sent; the other language is refused. | | `company_account_required` | 409 | conflict | A personal account has no company to verify. | | `company_already_registered` | 409 | conflict | This company already has a Parasend account. Ask its owner to invite you. | | `company_application_already_approved` | 409 | conflict | This request has already been approved. | | `company_application_closed` | 409 | conflict | This application is no longer open. | | `company_application_not_found` | 404 | not_found | No such company application. | | `company_application_pending` | 409 | conflict | You have asked for a new account for this company. Cancel that application first. | | `company_approval_not_found` | 404 | not_found | This approval link is not valid. | | `company_approval_not_yours` | 403 | permission_error | This request is for someone else. | | `company_closed` | 409 | conflict | The company registry lists this company as closed. | | `company_not_found` | 404 | not_found | No company with this code in the company registry. | | `company_registry_not_connected` | 503 | api_error | The company registry is not connected yet. | | `company_registry_unavailable` | 503 | api_error | The company registry did not answer. Try again in a moment. | | `confirmation_mismatch` | 400 | invalid_request | Type the company's name exactly as it is shown. | | `confirmation_required` | 409 | conflict | Give an e-mail address for the account; a code confirms it. | | `contrast_validation_failed` | 400 | invalid_request | A colour pair does not reach the contrast of 4.5:1. `message` names the pair. | | `country_fixed_by_eid` | 400 | invalid_request | The billing country comes from your electronic ID. | | `country_not_supported` | 400 | invalid_request | Company sign-up is for Icelandic companies for now. | | `credential_missing` | 500 | api_error | The signed-in session has no credential to issue from. | | `csrf_token_invalid` | 403 | permission_error | This request needs the Parasend-CSRF header of your current session. | | `cursor_invalid` | 400 | invalid_request | The provided cursor is not valid. | | `delete_already_scheduled` | 409 | conflict | Deletion is already scheduled for this delivery. | | `delete_blocked_legal_hold` | 409 | conflict | The delivery is under legal hold and cannot be deleted. The message names the case reference, when there is one. | | `delivery_already_confirmed` | 409 | conflict | The recipient has already confirmed receipt; revocation is not possible. | | `delivery_expired` | 409 | conflict | The delivery has already expired. | | `delivery_link_expired` | 401 | authentication_error | This delivery link has expired. | | `delivery_link_invalid` | 404 | not_found | This delivery link is not valid. | | `delivery_link_wrong_host` | 409 | conflict | This delivery opens on its own address; the page moves there. | | `delivery_not_found` | 404 | not_found | No delivery with that ID that you may see, in the mode of your key. | | `delivery_revoked` | 409 | conflict | The delivery is already revoked. | | `delivery_state_invalid` | 409 | conflict | This delivery has been confirmed or has ended; its follow-ups no longer change. | | `delivery_too_large` | 400 | invalid_request | The files of one delivery add up to more than a delivery may hold. | | `delivery_visibility_insufficient` | 403 | permission_error | Webhook endpoints get events for every delivery in the organisation, so managing them needs a key or member that sees all deliveries. | | `document_not_found` | 404 | not_found | No shared document with that ID. | | `document_personal_code_missing` | 403 | permission_error | This document does not show a kennitala. Use electronic ID, or an Icelandic passport or ID card. | | `domain_exists` | 409 | conflict | Your organisation already has this domain. | | `domain_not_found` | 404 | not_found | No such domain. | | `domain_not_verified` | 409 | conflict | Verify at least one e-mail domain first: discovery works only on verified domains. | | `domain_public_mail` | 400 | invalid_request | This is a public mail service: anyone can have an address there, so it cannot be verified for an organisation. | | `eid_sign_in_required` | 403 | permission_error | You are listed for this company: confirm with your electronic ID to open its account. | | `email_already_registered` | 409 | conflict | This address already has a Parasend account. Sign in instead. | | `email_disposable` | 400 | invalid_request | Disposable addresses cannot be used. Use your own address. | | `email_in_use` | 409 | conflict | This address belongs to another Parasend account. Sign in to that one, or use another address. | | `email_not_confirmed` | 409 | conflict | Confirm this address first (POST /v1/portal/emails). | | `email_required` | 409 | conflict | Confirm an e-mail address for the account first (POST /v1/portal/emails). | | `email_sender_confirmation_not_found` | 404 | not_found | This link is not valid any more. Send a new test e-mail from Parasend. | | `email_sender_domain_claimed` | 409 | conflict | Another organisation on Parasend uses this domain. | | `email_sender_exists` | 409 | conflict | Your organisation already has this e-mail sender. | | `email_sender_in_use` | 409 | conflict | A brand profile uses this e-mail sender. Choose another sender there, or archive the profile, first. | | `email_sender_mail_from_invalid` | 400 | invalid_request | `mail_from_domain` must be a subdomain of the sending domain that nobody sends or receives mail at. | | `email_sender_not_found` | 404 | not_found | No e-mail sender with that ID. | | `email_sender_not_smtp` | 400 | invalid_request | This sender goes through Parasend; only an SMTP sender has a server. | | `email_sender_public_domain` | 400 | invalid_request | Notices can go only from your organisation's own domain, not from a public mail provider. | | `email_sender_removed` | 409 | conflict | This sender was removed; add it again to use it. | | `email_sender_reserved` | 400 | invalid_request | Parasend's own domains cannot be a company's sender. | | `email_sender_secret_not_deleted` | 500 | api_error | The sender's password could not be deleted just now; nothing was removed. Try again. | | `event_not_found` | 404 | not_found | No event with that ID, in the mode of your key. | | `expiry_extension_invalid` | 400 | invalid_request | expires_at must be in the future. | | `file_deleted` | 404 | not_found | The sender has deleted their account; its documents no longer exist. | | `file_not_found` | 404 | not_found | No such file in this delivery. | | `file_reference_invalid` | 400 | invalid_request | A file the request names does not exist, or is not yours. | | `file_too_large` | 413 | payload_too_large | The file is larger than a direct upload allows (32 MB). | | `free_daily_cap_reached` | 429 | rate_limited | Free transactions have a daily limit, and it is reached. Choose a plan to send more today. | | `free_sms_destination_not_covered` | 400 | invalid_request | Free transactions include SMS to Iceland and some countries only. Choose a plan to send SMS to this country. | | `gateway_return_unknown` | 404 | not_found | No sign-in is waiting for this return, or its time is over. | | `gender_wording_unavailable` | 403 | permission_error | Your organisation may not use the gender word in its wordings. `message` says why. | | `governance_company_only` | 403 | permission_error | Shared documents are for company accounts. | | `governance_document_shared` | 409 | conflict | This shared document has been shared and cannot be changed this way. | | `governance_file_blocked` | 409 | conflict | The file did not pass the malware check and cannot be downloaded. | | `governance_file_too_large` | 413 | invalid_request | The shared document is larger than allowed. | | `governance_not_connected` | 503 | api_error | Shared documents are not connected yet. | | `governance_not_your_upload` | 403 | permission_error | Only the person who uploaded this file can do this. | | `governance_quota_exceeded` | 409 | conflict | The company's shared documents have used their space. Remove files you no longer need. | | `governance_restore_unavailable` | 404 | not_found | Nothing to restore: the version is not removed, or more than 30 days have passed. | | `governance_scan_pending` | 409 | conflict | The file is still being checked for malware. Try again in a minute. | | `icon_not_square` | 400 | invalid_request | The icon is not square. | | `icon_svg_unsafe` | 400 | invalid_request | The SVG holds something an icon may not, such as a script or an external link. Export it as a plain SVG, or upload a PNG. | | `icon_too_large` | 413 | invalid_request | The icon is larger than 1 MB. | | `icon_too_small` | 400 | invalid_request | The icon is smaller than 180 × 180 pixels. | | `icon_type_unsupported` | 415 | invalid_request | Send the icon as the request body, as image/png or image/svg+xml. | | `idempotency_key_invalid` | 400 | invalid_request | Idempotency-Key must be 1-255 characters of [A-Za-z0-9_-]. | | `idempotency_key_missing` | 400 | invalid_request | POST requests require an Idempotency-Key header. | | `idempotency_key_reused` | 409 | conflict | This Idempotency-Key was already used with a different request body. | | `identifier_invalid` | 400 | invalid_request | The test sign-in takes a personal code, not a phone number. | | `identity_already_verified` | 409 | conflict | Your identity is already verified with eID. | | `identity_document_not_verified` | 403 | permission_error | The passport or ID-card check did not pass. Try again, or choose another way. | | `identity_mismatch` | 403 | permission_error | The verified identity does not satisfy the authentication condition set by the sender for this delivery. | | `identity_proofing_coming_soon` | 503 | api_error | The passport check is coming soon. | | `identity_proofing_not_connected` | 503 | api_error | Identity proofing with an ID document is not connected yet. | | `identity_proofing_not_found` | 404 | not_found | No such identity proofing. | | `identity_proofing_unavailable` | 503 | api_error | Identity proofing did not answer. Try again in a moment. | | `identity_verification_conflict` | 409 | conflict | This personal code belongs to another member of a team you are in. Contact support@parasend.com. | | `identity_verification_expired` | 409 | conflict | This verification has ended. Start a new one. | | `identity_verification_failed` | 409 | conflict | The verification was cancelled. | | `identity_verification_mismatch` | 409 | conflict | This electronic ID belongs to someone else. Use your own. | | `identity_verification_not_found` | 404 | not_found | No such identity verification. | | `identity_verification_pending` | 409 | conflict | Confirm on your phone first. | | `identity_verification_required` | 403 | permission_error | Verify your identity with an electronic ID or a passport check before choosing a service plan. | | `image_too_detailed` | 400 | invalid_request | The photo cannot be made small enough for the page. Choose a calmer photo or crop it. | | `image_too_large` | 413 | invalid_request | The photo can be at most 5 MB. | | `image_too_small` | 400 | invalid_request | The photo is smaller than 640 × 320 pixels. | | `image_type_unsupported` | 415 | invalid_request | The photo must be a JPEG (image/jpeg), PNG (image/png) or WebP (image/webp). | | `internal_error` | 500 | api_error | Something went wrong on our side. Try again; if it keeps happening, contact us with the `request_id`. | | `invalid_parameter_value` | 400 | invalid_request | A parameter has a value the API does not accept. `param` names it, and `message` says what is allowed. | | `invitation_cancelled` | 409 | conflict | This invitation has been cancelled. | | `invitation_expired` | 409 | conflict | This invitation has expired. Ask for a new one. | | `invitation_not_found` | 404 | not_found | No such invitation. | | `invitation_not_pending` | 409 | conflict | The invitation is no longer open and can no longer be cancelled. | | `invitation_pending` | 409 | conflict | This address already has an open invitation: resend it instead. | | `invitation_used` | 409 | conflict | This invitation has already been used. | | `invitation_wrong_person` | 403 | permission_error | This invitation is for someone else: only the person it names can accept it. | | `invoice_not_found` | 404 | not_found | No such invoice. | | `invoice_not_payable` | 409 | conflict | Only an unpaid card invoice whose charge was refused is paid here. | | `invoice_pdf_not_available` | 404 | not_found | This invoice has no PDF: it is test data, or Payday has not booked it yet. | | `join_request_cooldown` | 409 | conflict | Your last request to join was declined. You can ask again later. | | `join_request_not_found` | 404 | not_found | No such join request. | | `join_request_not_pending` | 409 | conflict | This request has expired. | | `join_request_pending` | 409 | conflict | You have already asked to join; the request is waiting for an answer. | | `key_decisions_required` | 400 | invalid_request | The member holds API keys: decide for each whether to roll, keep or delete it. | | `key_delete_holder_only` | 403 | permission_error | Only the person the key is bound to, or the account owner, can delete it. | | `key_holder_only` | 403 | permission_error | Only the person this key is bound to can change who sees the deliveries it sends. | | `key_holder_unavailable` | 409 | conflict | No member can take over these keys. | | `key_holder_unidentified` | 409 | conflict | A key can be handed only to a member who has signed in to this organisation with eID. Choose another member. | | `key_recent_use_unconfirmed` | 409 | conflict | The key was used recently. Confirm that you want to delete it: whatever uses it will stop working. | | `key_reveal_issuer_only` | 403 | permission_error | Only the person this key is bound to can reveal it. | | `key_reveal_not_available` | 409 | conflict | A live key is shown only once, when it is created or rolled. Roll it to get a new one. | | `key_secret_unavailable` | 409 | conflict | This key's secret is not stored. Roll it to get a new one you can reveal later. | | `limit_invalid` | 400 | invalid_request | `limit` must be a whole number within the range the endpoint allows. | | `link_domain_unavailable` | 409 | conflict | Your brand profile's link domain is not working, so this delivery's links would be on Parasend's own address. Send with `options.allow_parasend_links: true`, or fix the domain. `error.link_domain` names it. | | `livemode_not_available` | 409 | conflict | This organisation has no access to live data yet. | | `logo_svg_unsafe` | 400 | invalid_request | The SVG holds something a logo may not, such as a script or an external link. Export it as a plain SVG, or upload a PNG. | | `logo_too_large` | 413 | invalid_request | The logo can be at most 1 MB. | | `logo_type_unsupported` | 415 | invalid_request | Send the logo as the request body, as image/png or image/svg+xml. | | `malformed_json` | 400 | invalid_request | The request body is not valid JSON, or not the JSON object the endpoint expects. | | `member_already_active` | 409 | conflict | This person is already a member of the organisation. | | `member_limit_reached` | 409 | conflict | This team is full: every seat on the organisation's plan is taken. | | `member_not_found` | 404 | not_found | No member with that ID in your organisation. | | `member_not_manageable` | 403 | permission_error | This member holds rights you do not hold, so you cannot change or remove them. | | `member_role_from_groups` | 409 | conflict | This member's role comes from their groups in your identity provider: change it there. Their permissions and the deliveries they see can still be changed here. | | `member_self_edit` | 403 | permission_error | You cannot change your own role, permissions or visibility; another member who manages the team can. | | `membership_required` | 403 | permission_error | You are no longer a member of this organisation. | | `metadata_too_large` | 400 | invalid_request | `metadata` has more keys or longer values than allowed. | | `minimum_loa_unsatisfiable` | 400 | invalid_request | conditional_authentication requires verification_level high or substantial. | | `missing_required_parameter` | 400 | invalid_request | A required parameter is missing. `param` names it. | | `monthly_delivery_cap_exceeded` | 429 | rate_limited | This API key has reached its monthly cap of deliveries. The count starts again at the start of next month. | | `nickname_invalid` | 400 | invalid_request | A card's name is up to 40 characters, on one line. | | `not_found` | 404 | not_found | No such route. | | `organization_already_verified` | 409 | conflict | This account is verified already. | | `organization_closed` | 409 | conflict | This account is being deleted. | | `organization_deletion_not_scheduled` | 409 | conflict | No deletion is under way. | | `organization_deletion_scheduled` | 403 | permission_error | This company account is being deleted. Cancel the deletion to use it again, or switch to another account. | | `organization_not_found` | 404 | not_found | No such account. | | `organization_not_selected` | 409 | conflict | Choose an organisation first (PATCH /v1/portal/session). | | `organization_verification_pending` | 403 | permission_error | A verification of this account is already waiting. Open it to follow it. | | `owner_cannot_leave` | 409 | conflict | The owner cannot leave the organisation: transfer ownership to another member first. | | `owner_only` | 403 | permission_error | Only the owner can transfer ownership. | | `owner_or_admin_required` | 403 | permission_error | Only the account owner or an admin can verify the company. | | `owner_protected` | 403 | permission_error | Nobody is invited as owner: ownership changes only by a transfer. | | `owner_required` | 403 | permission_error | Only the account owner can delete the account. | | `ownership_transfer_expired` | 409 | conflict | This transfer has expired. Ask the owner to start it again. | | `ownership_transfer_not_found` | 404 | not_found | No such ownership transfer. | | `ownership_transfer_not_pending` | 409 | conflict | This transfer of ownership is no longer waiting for an answer. | | `ownership_transfer_pending` | 409 | conflict | A transfer of ownership is already waiting for approval. Cancel it first. | | `ownership_transfer_target_only` | 403 | permission_error | Only the member ownership is being transferred to can approve it. | | `parameter_invalid` | 400 | invalid_request | A parameter has a value the API does not accept. `param` names it. | | `parameter_missing` | 400 | invalid_request | A required parameter is missing. `param` names it. | | `passkey_already_registered` | 409 | conflict | This passkey is already registered. | | `passkey_attestation_invalid` | 400 | invalid_request | attestation must be the WebAuthn registration response (PublicKeyCredential.toJSON()). | | `passkey_below_level` | 403 | permission_error | This passkey was set up with an e-mail address. Confirm with your eID, or a passkey set up after an eID sign-in. | | `passkey_challenge_invalid` | 400 | invalid_request | challenge_id is the one /v1/portal/passkeys/options returned. | | `passkey_enrolment_not_allowed` | 409 | conflict | A passkey can be added here only right after confirming receipt with your eID (within 10 minutes). | | `passkey_last_one` | 409 | conflict | This is your only passkey and your only way to sign in. Add another before removing it. | | `passkey_limit_reached` | 409 | conflict | You have as many passkeys as allowed. Remove one before adding another. | | `passkey_not_available` | 409 | conflict | No passkey is available for this confirmation. Confirm with your electronic ID instead. | | `passkey_not_found` | 404 | not_found | No such passkey. | | `passkey_not_recognised` | 401 | authentication_error | This passkey is not recognised here. Sign in with your eID instead. | | `passkey_rate_limited` | 429 | rate_limited | Too many failed passkey sign-ins. Wait a while or sign in with your eID. | | `passkey_sign_in_expired` | 409 | conflict | This sign-in has expired or was already used. Start a new one. | | `passkey_user_verification_required` | 400 | invalid_request | The device did not verify you (Face ID, fingerprint or PIN). Try again. | | `passport_check_coming_soon` | 409 | conflict | The passport check is coming soon; until then a delivery can be bound to a kennitala (recipient.personal_code) only. | | `payment_method_is_primary` | 409 | conflict | This card is the primary card. Make another card the primary first. | | `payment_method_merchant_only` | 409 | conflict | An Apple Pay card is charged automatically each month and cannot pay an invoice here. Use a card, or add one. | | `payment_method_not_found` | 404 | not_found | No such card. | | `payment_method_required` | 409 | conflict | Add a card before choosing to pay by card. | | `payment_overdue` | 402 | permission_error | Sending is paused: this account has unpaid invoices over its limit. Pay them in the portal (Service plan and billing) and sending opens again at once. | | `permission_denied` | 403 | permission_error | The API key or member does not have the permission this needs. `param` names it, as resource.action. | | `permission_exceeds_issuer` | 403 | permission_error | You cannot grant webhooks: webhook endpoints receive events for every delivery, and you see only your own. | | `personal_account` | 409 | conflict | A personal account has no team: this is for company accounts. | | `personal_account_exists` | 409 | conflict | You have started a new personal account since; this one cannot be opened again. | | `photo_consent_required` | 422 | invalid_request | Agree that a photo of your passport's photo page and your selfie are kept with this delivery's evidence and shown to its sender (photo_consent: true) before the passport check starts. | | `picture_too_large` | 413 | invalid_request | The picture can be at most 5 MB. | | `picture_type_unsupported` | 415 | invalid_request | The picture must be a JPEG, PNG or WebP image. | | `pin_invalid_for_scheme` | 400 | invalid_request | `personal_code` must be a 10-digit kennitala, and `id_document_number` a passport or ID-card number. | | `pin_type_incompatible_method` | 400 | invalid_request | None of the requested methods can assert the declared identity condition. | | `pin_type_required` | 400 | invalid_request | pin_type is required whenever pin is set. | | `pipedrive_deal_missing` | 409 | conflict | This account has no Pipedrive deal yet. | | `plan_change_by_agreement` | 409 | conflict | Plan 1–3 are by written agreement: starting on one, or moving between Pay-as-you-go and Plan 1–3. Contact sales@parasend.com. | | `plan_change_by_contract` | 409 | conflict | Enterprise is agreed by contract. Contact sales@parasend.com to change it. | | `plan_change_by_parasend` | 409 | conflict | This account's plan is set by Parasend. Contact sales@parasend.com to change it. | | `plan_required` | 403 | permission_error | The Trust center is included in Plan 1–3 and Enterprise. | | `policy_archived` | 409 | conflict | This follow-up policy is archived; duplicate it or create a new one instead. | | `policy_in_use` | 409 | conflict | The follow-up policy is still used by open batches or pending deliveries. Move them to another policy first. | | `policy_not_found` | 404 | not_found | No follow-up policy in use with that ID. | | `policy_not_resettable` | 400 | invalid_request | Only Parasend's own policies can be reset; this one is your organisation's. | | `policy_override_choice_required` | 409 | conflict | Some pending deliveries follow their own policy. Say whether to keep them (`own_policy_deliveries: keep`) or switch them too. | | `policy_override_not_allowed` | 400 | invalid_request | This batch uses one follow-up policy for every delivery; leave options.reminder_policy out, or ask the batch's owner to allow overrides. | | `policy_sms_too_long` | 400 | invalid_request | The reminder text is too long: with the sender's name and the link, it must fit in one SMS. | | `policy_step_invalid` | 400 | invalid_request | A step of the policy is not valid: its channel, its timing or its wording. | | `policy_step_out_of_range` | 400 | invalid_request | The step number is outside the policy's steps. | | `portal_beta_invite_only` | 403 | permission_error | Parasend is in a closed beta: sign-in is by invitation only. | | `portal_session_expired` | 401 | authentication_error | The portal session has ended. Sign in again. | | `portal_session_missing` | 401 | authentication_error | Sign in to the portal first. | | `portal_session_required` | 403 | permission_error | Only a person signed in to the portal can do this; an API key cannot. | | `portal_sign_in_blocked` | 429 | rate_limited | Too many failed sign-ins. Try again after 24 hours. | | `portal_sign_in_cancelled` | 409 | conflict | This sign-in was cancelled. Start a new one. | | `portal_sign_in_expired` | 409 | conflict | This sign-in has already been used. Start a new one. | | `portal_sign_in_failed` | 401 | authentication_error | This sign-in failed. Start a new one. | | `portal_sign_in_not_found` | 404 | not_found | No such sign-in. | | `portal_sign_in_pending` | 409 | conflict | Confirm the sign-in on your phone first. | | `price_list_not_found` | 404 | not_found | There is no price list in effect for that plan and date. | | `rail_not_available` | 400 | invalid_request | That plan cannot be paid that way. | | `rate_limit_exceeded` | 429 | rate_limited | Too many requests of this kind. Wait the number of seconds in the `Retry-After` header, then try again. | | `rate_limited` | 429 | rate_limited | Too many requests. Try again shortly. | | `reauthentication_cancelled` | 409 | conflict | The eID confirmation was cancelled. | | `reauthentication_expired` | 409 | conflict | This confirmation expired. Start a new one. | | `reauthentication_failed` | 401 | authentication_error | The eID confirmation did not succeed. | | `reauthentication_not_found` | 404 | not_found | No such eID confirmation. | | `reauthentication_pending` | 409 | conflict | Confirm on your phone first. | | `reauthentication_required` | 403 | permission_error | This needs a fresh sign-in. Confirm with your electronic ID or passkey, then try again. | | `reauthentication_wrong_person` | 403 | permission_error | This passkey is not the person signed in. Confirm with your own. | | `recipient_domain_apex` | 400 | invalid_request | A link domain is a subdomain, such as afhending.fyrirtaeki.example: the domain itself cannot point at Parasend with a CNAME. | | `recipient_domain_claimed` | 409 | conflict | Another organisation on Parasend uses this domain. | | `recipient_domain_exists` | 409 | conflict | Your organisation already has this link domain. | | `recipient_domain_in_use` | 409 | conflict | A brand profile uses this link domain. Choose another domain there, or archive the profile, first. | | `recipient_domain_not_found` | 404 | not_found | No link domain with that ID. | | `recipient_domain_removed` | 409 | conflict | This domain was removed; add it again to use it. | | `recipient_domain_reserved` | 400 | invalid_request | Parasend's own domains cannot be a link domain. | | `recipient_domain_too_long` | 400 | invalid_request | The host name is too long for a link domain: a text message must keep the sender's name readable. | | `recipient_email_invalid` | 400 | invalid_request | recipient.email is not a valid e-mail address. | | `recipient_missing_contact` | 400 | invalid_request | recipient needs at least one contact channel: email or phone. | | `recipient_phone_invalid` | 400 | invalid_request | recipient.phone must be in E.164 format (e.g. +3546661234). | | `recovery_link_invalid` | 400 | invalid_request | This sign-in link is not valid. Ask for a new one. | | `recovery_session_restricted` | 403 | permission_error | Add a passkey first: until then this session can only add one and remove old ones. | | `registration_closed` | 403 | permission_error | Sign-up is not open. | | `registration_code_invalid` | 400 | invalid_request | This link is not valid. Use the code in the e-mail. | | `registration_expired` | 409 | conflict | This sign-up has expired or was already completed. Start again. | | `registration_not_confirmed` | 409 | conflict | Confirm the e-mail address first. | | `registration_not_found` | 404 | not_found | No such sign-up. | | `registration_required` | 409 | conflict | Finish registering first; then accept the invitation. | | `registry_not_connected` | 500 | api_error | The National Registry is not connected yet; type the personal code instead. | | `registry_query_invalid` | 400 | invalid_request | Search by a full 10-digit personal code, or by a full name (first name and surname). | | `registry_rate_limited` | 429 | rate_limited | Too many registry searches this hour. Type the personal code instead. | | `registry_unavailable` | 500 | api_error | The National Registry is not answering right now; type the personal code instead. | | `remind_rate_limited` | 429 | rate_limited | Manual reminders to this recipient have reached their daily cap. Try again tomorrow. | | `remind_state_invalid` | 409 | conflict | The first notification has not been sent yet. | | `remind_too_soon` | 409 | conflict | A reminder can be sent a day after the request. | | `request_too_large` | 413 | payload_too_large | The request body is too large. | | `resource_missing` | 404 | invalid_request | No such object, or none that this key can see. `param` names the ID when there is one. | | `route_not_found` | 404 | not_found | No endpoint matches this method and path. Check the address against the API reference. | | `scim_not_in_plan` | 403 | permission_error | Provisioning (SCIM) comes with Plan 1–3 and Enterprise. | | `scim_token_limit_reached` | 409 | conflict | Two tokens are live already. Revoke one first (two let you rotate without a gap). | | `scim_token_not_found` | 404 | not_found | No such live SCIM token. | | `secret_and_files_mutually_exclusive` | 400 | invalid_request | files and secret are mutually exclusive; secrets are their own delivery kind (POST /v1/secrets). | | `send_confirmation_invalid` | 400 | invalid_request | This confirmation cannot be used: it was used already, it has expired (30 minutes), or it belongs to another sign-in. Confirm the send again. | | `send_confirmation_mismatch` | 400 | invalid_request | The files, recipient or message differ from what was confirmed with the passkey. Confirm the send again. | | `sender_authentication_insufficient` | 403 | permission_error | The person behind this session or key has not been identified strongly enough for this action. Sign in with your electronic ID. | | `sender_representation_lapsed` | 403 | permission_error | The company registry no longer confirms that the person behind this key represents the company. Renew the confirmation in the portal. | | `sender_representation_unverifiable` | 503 | api_error | The Company Registry could not be reached to confirm representation; try again later. | | `service_unavailable` | 503 | api_error | The service is briefly unavailable. Try again in a moment. | | `session_absolute_timeout` | 401 | authentication_error | The recipient's session has reached its longest allowed time. Open the delivery link again. | | `session_expired` | 401 | authentication_error | The recipient's session has ended. Open the delivery link again. | | `session_host_mismatch` | 401 | authentication_error | A recipient session was used on another address than the one it started on. Open the link again. | | `session_state_invalid` | 409 | conflict | This delivery could not be completed at the level it requires. | | `short_code_unavailable` | 500 | api_error | Could not allocate a short link for this delivery. | | `sign_in_policy_self_lockout` | 409 | conflict | Sign in with an electronic ID first: requiring it would end your own access. | | `sign_in_policy_unmet` | 403 | permission_error | This organisation requires sign-in with an electronic ID. Sign in again with eID or a passkey set up after an eID sign-in. | | `sign_in_required` | 403 | permission_error | You signed in with your company's single sign-on, which opens only that account. Sign in with eID or a passkey to open another. | | `sms_destination_not_available` | 400 | invalid_request | Parasend does not send SMS to this country yet. Notify by e-mail instead; see parasend.com/pricing/sms. | | `sms_sender_name_decided` | 409 | conflict | Parasend has already answered this sender name request. | | `sms_sender_name_invalid` | 400 | invalid_request | sms_sender_name must be 3–11 letters (a–z, A–Z) or digits with at least one letter: no spaces, hyphens or Icelandic letters. | | `sms_sender_name_not_found` | 404 | not_found | No SMS sender name with that ID. | | `sms_sender_name_reserved` | 400 | invalid_request | An SMS sender name cannot name Parasend. | | `sms_sender_name_taken` | 409 | conflict | Another company has this SMS sender name. One company per name. | | `sms_sender_name_unverified_company` | 403 | permission_error | Only a verified company can send SMS under its own name. Verify the company under Company settings first. | | `smtp_not_configured` | 409 | invalid_request | Save the mail server first. | | `smtp_senders_off` | 409 | invalid_request | Sending through your own mail server is not switched on here yet. | | `spending_cap_reached` | 429 | rate_limited | This delivery would take the month's estimate over your spending cap. Raise the cap in the portal, or wait for next month. | | `sso_access_turned_off` | 403 | permission_error | Your organisation has turned off your access to Parasend. | | `sso_address_in_use` | 409 | conflict | Another person in this organisation already has this address. If that is you, sign in with the electronic ID you use as that member, then try again. | | `sso_assertion_invalid` | 401 | authentication_error | Single sign-on did not succeed. | | `sso_certificate_expired` | 409 | conflict | The identity provider's certificate has expired. Ask your administrator to update the connection. | | `sso_company_only` | 403 | permission_error | Provisioning is for company accounts. | | `sso_connection_disabled` | 409 | conflict | Turn the connection on again before testing it. | | `sso_connection_exists` | 409 | conflict | This organisation already has an SSO connection. | | `sso_connection_not_enabled` | 409 | conflict | Turn single sign-on on first; then it can be required. | | `sso_connection_not_tested` | 409 | conflict | Test the connection first: sign in once through it with Test. | | `sso_domain_not_verified` | 409 | conflict | Verify your organisation's e-mail domain first (Team → Domains). | | `sso_enrolment_needs_proof` | 403 | permission_error | Sign in with your electronic ID, or verify your passport, to confirm who you are. | | `sso_enrolment_required` | 409 | conflict | Confirm who you are with your electronic ID before your first sign-in with single sign-on. | | `sso_identity_linked_elsewhere` | 409 | conflict | This identity is already linked to another person. | | `sso_join_request_needs_email` | 409 | conflict | Your identity provider sent no e-mail address. Ask your administrator to invite you. | | `sso_link_needs_eid` | 403 | permission_error | Sign in with your electronic ID first: the link is confirmed by it. | | `sso_membership_suspended` | 403 | permission_error | Your access to this organisation is suspended. Ask your administrator. | | `sso_metadata_pasted` | 409 | conflict | This connection's metadata was pasted; paste the new metadata instead. | | `sso_not_available` | 503 | api_error | Provisioning is not set up in this environment yet. | | `sso_not_configured` | 404 | not_found | This organisation has no SSO connection. | | `sso_not_in_plan` | 403 | permission_error | Single sign-on comes with Plan 1–3 and Enterprise. | | `sso_required` | 403 | permission_error | This organisation requires sign-in with its single sign-on. | | `sso_self_lockout` | 409 | conflict | Sign in through your single sign-on first (or be the owner or a named admin signed in with eID), so you are not locked out. | | `sso_sign_in_expired` | 410 | conflict | This sign-in has expired or was already used. Start again. | | `sso_test_expired` | 409 | conflict | Run the test again: a test can be linked only in the session that ran it, within ten minutes. | | `staff_admin_only` | 403 | permission_error | Only an admin on the staff list changes plans. | | `staff_eid_required` | 403 | permission_error | Sign in with your electronic ID, or a passkey made with it, to use the staff admin. | | `staff_member_unknown` | 400 | invalid_request | Pick someone on the staff list. | | `staff_only` | 403 | permission_error | This sign-in is not on Parasend's staff list. | | `system_policy_immutable` | 403 | permission_error | Parasend's own policies cannot be renamed or deleted, and `rpol_none` takes no steps. | | `test_email_address_not_confirmed` | 400 | invalid_request | to must be one of your own confirmed e-mail addresses. | | `test_email_limit_reached` | 429 | rate_limited | At most 5 test e-mails an hour. Try again later. | | `theme_knob_plan_gated` | 403 | permission_error | This part of a brand profile comes with Plan 1 and up. | | `too_many_approval_requests` | 409 | conflict | Too many requests are waiting. Cancel one first. | | `too_many_checks` | 429 | rate_limited | Too many checks this hour. Senders are checked every 15 minutes anyway. | | `too_many_company_applications` | 409 | conflict | Too many company applications are waiting. Cancel one first. | | `too_many_identity_proofings` | 429 | rate_limited | Too many identity checks today. Try again tomorrow. | | `too_many_join_requests` | 409 | conflict | Three requests to join by company code are already waiting for an answer. | | `too_many_lookups` | 429 | rate_limited | Too many company lookups. Try again in an hour. | | `too_many_signups` | 429 | rate_limited | Too many sign-ups from here, or for this address. Try again in an hour. | | `too_many_tests` | 429 | rate_limited | Too many test e-mails for this sender this hour. Try again later. | | `unknown_parameter` | 400 | invalid_request | The request has a parameter the endpoint does not know, often a typo. `param` names it. | | `unsupported_media_type` | 415 | invalid_request | The request body's Content-Type is not accepted here. Send JSON (application/json) unless the endpoint says otherwise. | | `upload_incomplete` | 400 | invalid_request | The upload is not complete. | | `upload_link_invalid` | 401 | authentication_error | This upload link is not valid or has expired. | | `upload_session_invalid` | 409 | conflict | This upload was cancelled or has expired; start it again. | | `upload_session_not_found` | 404 | not_found | No such part of an open upload. | | `view_link_invalid` | 401 | authentication_error | This preview link has expired; open it again from the delivery page. | | `webhook_auth_config_invalid` | 400 | invalid_request | The endpoint's `auth` settings are missing or malformed for its mode, for example OAuth 2.0 without a `token_url`. | | `webhook_delivery_sources_invalid` | 400 | invalid_request | `delivery_sources` is not valid: it names no key and not the portal, too many keys, or a key that is not your organisation's, not in the endpoint's mode, or revoked. | | `webhook_endpoint_limit_reached` | 400 | invalid_request | You have as many webhook endpoints as one mode may have. Delete one first. | | `webhook_endpoint_not_found` | 404 | not_found | No webhook endpoint with that ID, in the mode of your key. | | `webhook_event_type_unknown` | 400 | invalid_request | An event type the endpoint should receive is not one Parasend sends. | | `webhook_signature_invalid` | 401 | authentication_error | The webhook signature does not match. | | `webhook_url_invalid` | 400 | invalid_request | url must be an https URL. | | `webhook_url_not_public` | 400 | invalid_request | The host is an IP address or a reserved name, or resolves to a private, loopback or other non-public address. | | `webhook_url_port_not_allowed` | 400 | invalid_request | The URL uses another port than 443. `param` names the URL. | | `webhook_url_unresolvable` | 400 | invalid_request | The host has no A or AAAA record. Check the name for a typo. | | `wording_archived` | 409 | conflict | This saved wording is archived; save a new one instead. | | `wording_not_found` | 404 | not_found | No saved wording with that ID. | | `wording_version_not_found` | 404 | not_found | The saved wording has no version with that number. | | `zip_too_large` | 400 | invalid_request | Download the files one at a time. | --- # Event types Source: https://parasend.com/docs/events Sent to your webhook endpoints and listed by GET https://api.parasend.com/v1/events. An event names its `type` and its `resource`; fetch the resource for its full state. Ignore types you do not know. - `delivery.created`: A delivery was created. Its notice goes out next. - `delivery.notified`: The notice went out: the e-mail, the SMS or both. In test mode nothing is sent, but the event still comes. - `delivery.email_opened`: The notice e-mail was opened: the e-mail client loaded its image. A hint, never proof of receipt. - `delivery.opened`: The recipient opened the delivery page through the link. - `delivery.auth_failed`: The recipient tried to verify and failed. `reason` says why, for example `personal_code_mismatch`, `id_document_mismatch`, `timeout` or `cancelled`. - `delivery.confirmed`: The recipient confirmed receipt. This is the event that proves the delivery. - `delivery.downloaded`: The recipient downloaded the documents for the first time. - `delivery.delivered`: The confirmation is sealed into the evidence record: the delivery has reached its end. - `delivery.evidence_sealed`: The evidence record's envelope was sealed, after a request with `sealed=true`. - `delivery.reminded`: A reminder went out, from the follow-up policy or by hand. - `delivery.bounced`: The notice could not be delivered: the e-mail bounced or the SMS failed. - `delivery.expired`: The delivery's time ran out before the recipient confirmed receipt. - `delivery.revoked`: You revoked the delivery before the recipient confirmed receipt. - `delivery.rejected`: A delivery was accepted and then refused before it was sent, by a check that runs after the request. It will not be sent. - `delivery.policy_changed`: The delivery's follow-up policy was changed on its own, not as part of a batch. - `deletion.scheduled`: DELETE /deliveries/{id} scheduled the delivery's data for removal. - `document.previewed`: The recipient previewed a document. - `link.opened`: A link in the notice was opened. - `otp.sent`: A one-time code was sent to the recipient by SMS. - `otp.verified`: The recipient entered the right one-time code. - `otp.locked`: Too many wrong codes: one-time codes are locked for the delivery for 24 hours. - `batch.finalized`: A batch was sent out, or a rolling batch was closed. - `batch.completed`: Every delivery in the batch has reached its end. - `batch.cancelled`: A batch was cancelled: its deliveries that were not yet confirmed are revoked. - `batch.policy_changed`: A batch's follow-up policy was changed. - `recipient_domain.verified`: A link domain's DNS records are right, and links can use it. Live mode only. - `recipient_domain.degraded`: A link domain's DNS records are no longer right. Reminders wait for it to work again. Live mode only. - `recipient_domain.failed`: A link domain stayed broken, so links use Parasend's own address. Live mode only. - `member.suspended`: A member of your organisation was suspended, from the portal or by SCIM. Live mode only. - `member.restored`: A suspended member was restored. Live mode only. - `member.removed`: A member left your organisation: removed in the portal, by SCIM, or their account was deleted. Live mode only. - `email_sender.verified`: Your e-mail sender's checks passed: notices go from your own address. Live mode only. - `email_sender.degraded`: Your e-mail sender's checks stopped passing for now. Live mode only. - `email_sender.failed`: Your e-mail sender failed: notices go from Parasend's address. Live mode only. - `email_sender.restored`: Your e-mail sender's checks pass again. Live mode only.