All integrations

EmailOctopus

MARKETING · MARKETING

Contact lists, contacts, campaigns, and their reports in the account they connected.

Acts as the person, not as itself

Each user connects their own account. Every call carries both identities — the agent and the person it is acting for — so the agent can never reach past what that individual can already do.

Credentials never touch the agent

Tokens live in the vault and attach server-side at call time. The agent holds a session, not a secret, and revoking access does not mean rotating a key.

Every call on the record

Who asked, which agent acted, which action ran, and the verdict that let it through — one audit trail across every integration, not one per vendor.

What an agent can do

Each action is granted on its own. An agent allowed to read is not thereby allowed to write, and the scope beside each row is what the acting user must have connected for it to run at all.

emailoctopus_delete_lists_by_list_idWRITE

Delete a contact list via DELETE /lists/{list_id}. Deletes the list AND, with it, the contacts inside it -- this is the widest-blast-radius call in the integration. DESTRUCTIVE AND IRREVERSIBLE: the sealed contract documents no recycle bin, no restore endpoint and no soft-delete state for this resource, and the call answers 204 with no body, so there is nothing to read back. Confirm the id before calling. An id this key cannot see answers 404 `not-found` -- "typically caused by using an invalid or incorrect identifier, such as a list ID or contact ID that doesn't exist in your account". Measured 2026-09-22: an UNROUTED path answers the same 404 shape, so read the `type` field rather than the status to tell a missing record from a missing route. A 403 here is one of two things and neither is a bad key: `access-denied` means this key belongs to a different EmailOctopus account than the list or campaign named (check the last four characters of the key on the account's API keys screen), and `out-of-limits` means the operation would exceed the account's PLAN limits. Only a 401 is a credential problem. EmailOctopus rate-limits with a token bucket: 100 tokens, one per request, refilled at 10 per second, so a burst of 100 or a steady 10 per second. The remainder is in the X-RateLimiting-Remaining response header and a 429 carries X-RateLimit-Retry-After; a 429 is the budget, never the credential.

api
emailoctopus_delete_lists_by_list_id_contacts_by_contact_idWRITE

Delete a contact via DELETE /lists/{list_id}/contacts/{contact_id}. Removes the contact from the list entirely. This is NOT an unsubscribe: a deleted contact leaves no record, so the list loses the evidence that the address ever opted out -- to suppress someone while keeping that record, update the contact with `status: unsubscribed` instead. DESTRUCTIVE AND IRREVERSIBLE: the sealed contract documents no recycle bin, no restore endpoint and no soft-delete state for this resource, and the call answers 204 with no body, so there is nothing to read back. Confirm the id before calling. An id this key cannot see answers 404 `not-found` -- "typically caused by using an invalid or incorrect identifier, such as a list ID or contact ID that doesn't exist in your account". Measured 2026-09-22: an UNROUTED path answers the same 404 shape, so read the `type` field rather than the status to tell a missing record from a missing route. A 403 here is one of two things and neither is a bad key: `access-denied` means this key belongs to a different EmailOctopus account than the list or campaign named (check the last four characters of the key on the account's API keys screen), and `out-of-limits` means the operation would exceed the account's PLAN limits. Only a 401 is a credential problem. EmailOctopus rate-limits with a token bucket: 100 tokens, one per request, refilled at 10 per second, so a burst of 100 or a steady 10 per second. The remainder is in the X-RateLimiting-Remaining response header and a 429 carries X-RateLimit-Retry-After; a 429 is the budget, never the credential.

api
emailoctopus_delete_lists_by_list_id_fields_by_tagWRITE

Delete a custom field from a list via DELETE /lists/{list_id}/fields/{tag}. Deletes the custom field definition from the list and, with it, the value every contact in that list held for it. DESTRUCTIVE AND IRREVERSIBLE: the sealed contract documents no recycle bin, no restore endpoint and no soft-delete state for this resource, and the call answers 204 with no body, so there is nothing to read back. Confirm the id before calling. An id this key cannot see answers 404 `not-found` -- "typically caused by using an invalid or incorrect identifier, such as a list ID or contact ID that doesn't exist in your account". Measured 2026-09-22: an UNROUTED path answers the same 404 shape, so read the `type` field rather than the status to tell a missing record from a missing route. A 403 here is one of two things and neither is a bad key: `access-denied` means this key belongs to a different EmailOctopus account than the list or campaign named (check the last four characters of the key on the account's API keys screen), and `out-of-limits` means the operation would exceed the account's PLAN limits. Only a 401 is a credential problem. EmailOctopus rate-limits with a token bucket: 100 tokens, one per request, refilled at 10 per second, so a burst of 100 or a steady 10 per second. The remainder is in the X-RateLimiting-Remaining response header and a 429 carries X-RateLimit-Retry-After; a 429 is the budget, never the credential.

api
emailoctopus_delete_lists_by_list_id_tags_by_tagWRITE

Delete a tag from a list via DELETE /lists/{list_id}/tags/{tag}. Deletes the tag from the list and removes it from every contact that carried it. The contacts themselves are untouched. DESTRUCTIVE AND IRREVERSIBLE: the sealed contract documents no recycle bin, no restore endpoint and no soft-delete state for this resource, and the call answers 204 with no body, so there is nothing to read back. Confirm the id before calling. An id this key cannot see answers 404 `not-found` -- "typically caused by using an invalid or incorrect identifier, such as a list ID or contact ID that doesn't exist in your account". Measured 2026-09-22: an UNROUTED path answers the same 404 shape, so read the `type` field rather than the status to tell a missing record from a missing route. A 403 here is one of two things and neither is a bad key: `access-denied` means this key belongs to a different EmailOctopus account than the list or campaign named (check the last four characters of the key on the account's API keys screen), and `out-of-limits` means the operation would exceed the account's PLAN limits. Only a 401 is a credential problem. EmailOctopus rate-limits with a token bucket: 100 tokens, one per request, refilled at 10 per second, so a burst of 100 or a steady 10 per second. The remainder is in the X-RateLimiting-Remaining response header and a 429 carries X-RateLimit-Retry-After; a 429 is the budget, never the credential.

api
emailoctopus_get_campaignsREAD

List campaigns via GET /campaigns. Returns every campaign on the account with its id, name, status, subject, from address and send time. This is where a `campaign_id` comes from for the three report tools. Paginated: at most 100 results per page in `data`, with the next page's cursor in `paging.next.starting_after`. Pass that value back verbatim as `starting_after`; the cursor is opaque and the vendor asks that nothing be read out of it. A 403 here is one of two things and neither is a bad key: `access-denied` means this key belongs to a different EmailOctopus account than the list or campaign named (check the last four characters of the key on the account's API keys screen), and `out-of-limits` means the operation would exceed the account's PLAN limits. Only a 401 is a credential problem. EmailOctopus rate-limits with a token bucket: 100 tokens, one per request, refilled at 10 per second, so a burst of 100 or a steady 10 per second. The remainder is in the X-RateLimiting-Remaining response header and a 429 carries X-RateLimit-Retry-After; a 429 is the budget, never the credential.

api
emailoctopus_get_campaigns_by_campaign_idREAD

Get a campaign via GET /campaigns/{campaign_id}. One campaign by id, with its content and the lists and segments it was sent to. An id this key cannot see answers 404 `not-found` -- "typically caused by using an invalid or incorrect identifier, such as a list ID or contact ID that doesn't exist in your account". Measured 2026-09-22: an UNROUTED path answers the same 404 shape, so read the `type` field rather than the status to tell a missing record from a missing route. A 403 here is one of two things and neither is a bad key: `access-denied` means this key belongs to a different EmailOctopus account than the list or campaign named (check the last four characters of the key on the account's API keys screen), and `out-of-limits` means the operation would exceed the account's PLAN limits. Only a 401 is a credential problem. EmailOctopus rate-limits with a token bucket: 100 tokens, one per request, refilled at 10 per second, so a burst of 100 or a steady 10 per second. The remainder is in the X-RateLimiting-Remaining response header and a 429 carries X-RateLimit-Retry-After; a 429 is the budget, never the credential.

api
emailoctopus_get_campaigns_by_campaign_id_reportsREAD

List a campaign's contacts by report status via GET /campaigns/{campaign_id}/reports. `status` is REQUIRED and is the whole point of the tool: it selects which slice of the campaign's recipients to list -- `sent`, `opened`, `not-opened`, `clicked`, `not-clicked`, `bounced`, `complained` or `unsubscribed`. Each row is a contact, so this is the tool that answers "who did not open it". Omitting `status` is a 422, not a default. Paginated: at most 100 results per page in `data`, with the next page's cursor in `paging.next.starting_after`. Pass that value back verbatim as `starting_after`; the cursor is opaque and the vendor asks that nothing be read out of it. An id this key cannot see answers 404 `not-found` -- "typically caused by using an invalid or incorrect identifier, such as a list ID or contact ID that doesn't exist in your account". Measured 2026-09-22: an UNROUTED path answers the same 404 shape, so read the `type` field rather than the status to tell a missing record from a missing route. A 403 here is one of two things and neither is a bad key: `access-denied` means this key belongs to a different EmailOctopus account than the list or campaign named (check the last four characters of the key on the account's API keys screen), and `out-of-limits` means the operation would exceed the account's PLAN limits. Only a 401 is a credential problem. EmailOctopus rate-limits with a token bucket: 100 tokens, one per request, refilled at 10 per second, so a burst of 100 or a steady 10 per second. The remainder is in the X-RateLimiting-Remaining response header and a 429 carries X-RateLimit-Retry-After; a 429 is the budget, never the credential.

api
emailoctopus_get_campaigns_by_campaign_id_reports_linksREAD

Get a campaign's link click report via GET /campaigns/{campaign_id}/reports/links. Per-LINK click totals for one campaign: every URL in the email with its total and unique click counts. Not paginated. An id this key cannot see answers 404 `not-found` -- "typically caused by using an invalid or incorrect identifier, such as a list ID or contact ID that doesn't exist in your account". Measured 2026-09-22: an UNROUTED path answers the same 404 shape, so read the `type` field rather than the status to tell a missing record from a missing route. A 403 here is one of two things and neither is a bad key: `access-denied` means this key belongs to a different EmailOctopus account than the list or campaign named (check the last four characters of the key on the account's API keys screen), and `out-of-limits` means the operation would exceed the account's PLAN limits. Only a 401 is a credential problem. EmailOctopus rate-limits with a token bucket: 100 tokens, one per request, refilled at 10 per second, so a burst of 100 or a steady 10 per second. The remainder is in the X-RateLimiting-Remaining response header and a 429 carries X-RateLimit-Retry-After; a 429 is the budget, never the credential.

api
emailoctopus_get_campaigns_by_campaign_id_reports_summaryREAD

Get a campaign's summary report via GET /campaigns/{campaign_id}/reports/summary. The campaign's headline counters in one document -- sent, bounced (hard and soft), opened, clicked, unsubscribed, complained -- each as a total and a unique count. Read this before pulling the per-contact reports, which are paginated a hundred at a time. An id this key cannot see answers 404 `not-found` -- "typically caused by using an invalid or incorrect identifier, such as a list ID or contact ID that doesn't exist in your account". Measured 2026-09-22: an UNROUTED path answers the same 404 shape, so read the `type` field rather than the status to tell a missing record from a missing route. A 403 here is one of two things and neither is a bad key: `access-denied` means this key belongs to a different EmailOctopus account than the list or campaign named (check the last four characters of the key on the account's API keys screen), and `out-of-limits` means the operation would exceed the account's PLAN limits. Only a 401 is a credential problem. EmailOctopus rate-limits with a token bucket: 100 tokens, one per request, refilled at 10 per second, so a burst of 100 or a steady 10 per second. The remainder is in the X-RateLimiting-Remaining response header and a 429 carries X-RateLimit-Retry-After; a 429 is the budget, never the credential.

api
emailoctopus_get_listsREAD

List contact lists via GET /lists. Returns every contact list on the account with its id, name, double-opt-in setting, custom field definitions and per-status contact counts. This is where a `list_id` comes from for every other list, contact, field and tag tool. Paginated: at most 100 results per page in `data`, with the next page's cursor in `paging.next.starting_after`. Pass that value back verbatim as `starting_after`; the cursor is opaque and the vendor asks that nothing be read out of it. A 403 here is one of two things and neither is a bad key: `access-denied` means this key belongs to a different EmailOctopus account than the list or campaign named (check the last four characters of the key on the account's API keys screen), and `out-of-limits` means the operation would exceed the account's PLAN limits. Only a 401 is a credential problem. EmailOctopus rate-limits with a token bucket: 100 tokens, one per request, refilled at 10 per second, so a burst of 100 or a steady 10 per second. The remainder is in the X-RateLimiting-Remaining response header and a 429 carries X-RateLimit-Retry-After; a 429 is the budget, never the credential.

api
emailoctopus_get_lists_by_list_idREAD

Get a contact list via GET /lists/{list_id}. One list by id, with its custom field definitions (each field's `tag` is the key to use in a contact's `fields` object) and its contact counts by status. An id this key cannot see answers 404 `not-found` -- "typically caused by using an invalid or incorrect identifier, such as a list ID or contact ID that doesn't exist in your account". Measured 2026-09-22: an UNROUTED path answers the same 404 shape, so read the `type` field rather than the status to tell a missing record from a missing route. A 403 here is one of two things and neither is a bad key: `access-denied` means this key belongs to a different EmailOctopus account than the list or campaign named (check the last four characters of the key on the account's API keys screen), and `out-of-limits` means the operation would exceed the account's PLAN limits. Only a 401 is a credential problem. EmailOctopus rate-limits with a token bucket: 100 tokens, one per request, refilled at 10 per second, so a burst of 100 or a steady 10 per second. The remainder is in the X-RateLimiting-Remaining response header and a 429 carries X-RateLimit-Retry-After; a 429 is the budget, never the credential.

api
emailoctopus_get_lists_by_list_id_contactsREAD

List contacts in a list via GET /lists/{list_id}/contacts. The contacts in one list, filterable by `status` (defaults to `subscribed`, so unsubscribed and pending contacts are NOT returned unless asked for), by `tag`, and by creation or update time through the four ISO 8601 bounds `created_at.gte`, `created_at.lte`, `last_updated_at.gte` and `last_updated_at.lte`. Paginated: at most 100 results per page in `data`, with the next page's cursor in `paging.next.starting_after`. Pass that value back verbatim as `starting_after`; the cursor is opaque and the vendor asks that nothing be read out of it. An id this key cannot see answers 404 `not-found` -- "typically caused by using an invalid or incorrect identifier, such as a list ID or contact ID that doesn't exist in your account". Measured 2026-09-22: an UNROUTED path answers the same 404 shape, so read the `type` field rather than the status to tell a missing record from a missing route. A 403 here is one of two things and neither is a bad key: `access-denied` means this key belongs to a different EmailOctopus account than the list or campaign named (check the last four characters of the key on the account's API keys screen), and `out-of-limits` means the operation would exceed the account's PLAN limits. Only a 401 is a credential problem. EmailOctopus rate-limits with a token bucket: 100 tokens, one per request, refilled at 10 per second, so a burst of 100 or a steady 10 per second. The remainder is in the X-RateLimiting-Remaining response header and a 429 carries X-RateLimit-Retry-After; a 429 is the budget, never the credential.

api
emailoctopus_get_lists_by_list_id_contacts_by_contact_idREAD

Get a contact via GET /lists/{list_id}/contacts/{contact_id}. One contact in one list. `contact_id` accepts either the contact's id or an MD5 hash of the lowercase form of its email address, which is how a caller holding only an address reads a contact without listing the whole list. An id this key cannot see answers 404 `not-found` -- "typically caused by using an invalid or incorrect identifier, such as a list ID or contact ID that doesn't exist in your account". Measured 2026-09-22: an UNROUTED path answers the same 404 shape, so read the `type` field rather than the status to tell a missing record from a missing route. A 403 here is one of two things and neither is a bad key: `access-denied` means this key belongs to a different EmailOctopus account than the list or campaign named (check the last four characters of the key on the account's API keys screen), and `out-of-limits` means the operation would exceed the account's PLAN limits. Only a 401 is a credential problem. EmailOctopus rate-limits with a token bucket: 100 tokens, one per request, refilled at 10 per second, so a burst of 100 or a steady 10 per second. The remainder is in the X-RateLimiting-Remaining response header and a 429 carries X-RateLimit-Retry-After; a 429 is the budget, never the credential.

api
emailoctopus_get_lists_by_list_id_tagsREAD

List a list's tags via GET /lists/{list_id}/tags. Every tag defined on one list. Tags are per list, like fields. Paginated: at most 100 results per page in `data`, with the next page's cursor in `paging.next.starting_after`. Pass that value back verbatim as `starting_after`; the cursor is opaque and the vendor asks that nothing be read out of it. An id this key cannot see answers 404 `not-found` -- "typically caused by using an invalid or incorrect identifier, such as a list ID or contact ID that doesn't exist in your account". Measured 2026-09-22: an UNROUTED path answers the same 404 shape, so read the `type` field rather than the status to tell a missing record from a missing route. A 403 here is one of two things and neither is a bad key: `access-denied` means this key belongs to a different EmailOctopus account than the list or campaign named (check the last four characters of the key on the account's API keys screen), and `out-of-limits` means the operation would exceed the account's PLAN limits. Only a 401 is a credential problem. EmailOctopus rate-limits with a token bucket: 100 tokens, one per request, refilled at 10 per second, so a burst of 100 or a steady 10 per second. The remainder is in the X-RateLimiting-Remaining response header and a 429 carries X-RateLimit-Retry-After; a 429 is the budget, never the credential.

api
emailoctopus_post_automations_by_automation_id_queueWRITE

Start an automation for a contact via POST /automations/{automation_id}/queue. Start an automation for a specific contact. The automation must have the **Started via API** trigger type. A contact can only trigger an automation once, unless you've enabled **Allow contacts to repeat** in the automation's settings. `automation_id` names the automation and the required body field `contact_id` names who enters it -- either the contact's id or an MD5 hash of the lowercase form of their email address, which is how this API lets a caller address a contact it has never listed. Answers 204 with no body on success. A 409 `conflict` is the documented signal that the contact has already been through this automation once. An id this key cannot see answers 404 `not-found` -- "typically caused by using an invalid or incorrect identifier, such as a list ID or contact ID that doesn't exist in your account". Measured 2026-09-22: an UNROUTED path answers the same 404 shape, so read the `type` field rather than the status to tell a missing record from a missing route. EmailOctopus validates the body before acting and answers 422 with an `errors` array of RFC 9457 entries, each carrying a JSON Pointer to the offending attribute (`{"pointer": "/email_address", "detail": "This value should not be blank."}`), so a 422 creates and changes nothing. The body must be sent with `Content-Type: application/json` or the API answers 415 `unsupported-media-type`. A 403 here is one of two things and neither is a bad key: `access-denied` means this key belongs to a different EmailOctopus account than the list or campaign named (check the last four characters of the key on the account's API keys screen), and `out-of-limits` means the operation would exceed the account's PLAN limits. Only a 401 is a credential problem. EmailOctopus rate-limits with a token bucket: 100 tokens, one per request, refilled at 10 per second, so a burst of 100 or a steady 10 per second. The remainder is in the X-RateLimiting-Remaining response header and a 429 carries X-RateLimit-Retry-After; a 429 is the budget, never the credential.

api
emailoctopus_post_listsWRITE

Create a contact list via POST /lists. `name` is the only field, and it is required. The new list is returned with the `id` every other list tool needs. A list starts with no custom fields beyond the built-in ones and no tags. EmailOctopus validates the body before acting and answers 422 with an `errors` array of RFC 9457 entries, each carrying a JSON Pointer to the offending attribute (`{"pointer": "/email_address", "detail": "This value should not be blank."}`), so a 422 creates and changes nothing. The body must be sent with `Content-Type: application/json` or the API answers 415 `unsupported-media-type`. A 403 here is one of two things and neither is a bad key: `access-denied` means this key belongs to a different EmailOctopus account than the list or campaign named (check the last four characters of the key on the account's API keys screen), and `out-of-limits` means the operation would exceed the account's PLAN limits. Only a 401 is a credential problem. EmailOctopus rate-limits with a token bucket: 100 tokens, one per request, refilled at 10 per second, so a burst of 100 or a steady 10 per second. The remainder is in the X-RateLimiting-Remaining response header and a 429 carries X-RateLimit-Retry-After; a 429 is the budget, never the credential.

api
emailoctopus_post_lists_by_list_id_contactsWRITE

Create a contact via POST /lists/{list_id}/contacts. `email_address` is required; `fields` keys by each custom field's `tag`, `tags` is an object of tag-name to true, and `status` is `subscribed`, `unsubscribed` or `pending`. Creating a contact that is already in the list is a 409. Creating something that already exists answers 409 `already-exists` (the vendor's examples: a tag the list already has, a contact already in the list). Prefer the upsert -- Create or update a contact (emailoctopus_put_lists_by_list_id_contacts) -- when the record may exist. An id this key cannot see answers 404 `not-found` -- "typically caused by using an invalid or incorrect identifier, such as a list ID or contact ID that doesn't exist in your account". Measured 2026-09-22: an UNROUTED path answers the same 404 shape, so read the `type` field rather than the status to tell a missing record from a missing route. EmailOctopus validates the body before acting and answers 422 with an `errors` array of RFC 9457 entries, each carrying a JSON Pointer to the offending attribute (`{"pointer": "/email_address", "detail": "This value should not be blank."}`), so a 422 creates and changes nothing. The body must be sent with `Content-Type: application/json` or the API answers 415 `unsupported-media-type`. A 403 here is one of two things and neither is a bad key: `access-denied` means this key belongs to a different EmailOctopus account than the list or campaign named (check the last four characters of the key on the account's API keys screen), and `out-of-limits` means the operation would exceed the account's PLAN limits. Only a 401 is a credential problem. EmailOctopus rate-limits with a token bucket: 100 tokens, one per request, refilled at 10 per second, so a burst of 100 or a steady 10 per second. The remainder is in the X-RateLimiting-Remaining response header and a 429 carries X-RateLimit-Retry-After; a 429 is the budget, never the credential.

api
emailoctopus_post_lists_by_list_id_fieldsWRITE

Create a custom field on a list via POST /lists/{list_id}/fields. Custom fields are per LIST, not per account. The body is one of two shapes, which is why it is declared as a choice: a `text`, `number` or `date` field needs `label`, `tag` and `type`; a `choice_single` or `choice_multiple` field needs those three AND a `choices` array. `tag` is the key a contact's `fields` object uses and the key personalisation uses in a campaign, so it is the name that matters. Creating something that already exists answers 409 `already-exists` (the vendor's examples: a tag the list already has, a contact already in the list). Prefer the upsert -- Create or update a contact (emailoctopus_put_lists_by_list_id_contacts) -- when the record may exist. An id this key cannot see answers 404 `not-found` -- "typically caused by using an invalid or incorrect identifier, such as a list ID or contact ID that doesn't exist in your account". Measured 2026-09-22: an UNROUTED path answers the same 404 shape, so read the `type` field rather than the status to tell a missing record from a missing route. EmailOctopus validates the body before acting and answers 422 with an `errors` array of RFC 9457 entries, each carrying a JSON Pointer to the offending attribute (`{"pointer": "/email_address", "detail": "This value should not be blank."}`), so a 422 creates and changes nothing. The body must be sent with `Content-Type: application/json` or the API answers 415 `unsupported-media-type`. A 403 here is one of two things and neither is a bad key: `access-denied` means this key belongs to a different EmailOctopus account than the list or campaign named (check the last four characters of the key on the account's API keys screen), and `out-of-limits` means the operation would exceed the account's PLAN limits. Only a 401 is a credential problem. EmailOctopus rate-limits with a token bucket: 100 tokens, one per request, refilled at 10 per second, so a burst of 100 or a steady 10 per second. The remainder is in the X-RateLimiting-Remaining response header and a 429 carries X-RateLimit-Retry-After; a 429 is the budget, never the credential.

api
emailoctopus_post_lists_by_list_id_tagsWRITE

Create a tag on a list via POST /lists/{list_id}/tags. Creates a tag on the list so it can be applied to contacts. Creating a tag the list already carries is a 409. Creating something that already exists answers 409 `already-exists` (the vendor's examples: a tag the list already has, a contact already in the list). Prefer the upsert -- Create or update a contact (emailoctopus_put_lists_by_list_id_contacts) -- when the record may exist. An id this key cannot see answers 404 `not-found` -- "typically caused by using an invalid or incorrect identifier, such as a list ID or contact ID that doesn't exist in your account". Measured 2026-09-22: an UNROUTED path answers the same 404 shape, so read the `type` field rather than the status to tell a missing record from a missing route. EmailOctopus validates the body before acting and answers 422 with an `errors` array of RFC 9457 entries, each carrying a JSON Pointer to the offending attribute (`{"pointer": "/email_address", "detail": "This value should not be blank."}`), so a 422 creates and changes nothing. The body must be sent with `Content-Type: application/json` or the API answers 415 `unsupported-media-type`. A 403 here is one of two things and neither is a bad key: `access-denied` means this key belongs to a different EmailOctopus account than the list or campaign named (check the last four characters of the key on the account's API keys screen), and `out-of-limits` means the operation would exceed the account's PLAN limits. Only a 401 is a credential problem. EmailOctopus rate-limits with a token bucket: 100 tokens, one per request, refilled at 10 per second, so a burst of 100 or a steady 10 per second. The remainder is in the X-RateLimiting-Remaining response header and a 429 carries X-RateLimit-Retry-After; a 429 is the budget, never the credential.

api
emailoctopus_put_lists_by_list_idWRITE

Rename a contact list via PUT /lists/{list_id}. `name` is required and is the only settable attribute on this route: double opt-in, fields and tags each have their own tools. This REPLACES the name rather than patching it. An id this key cannot see answers 404 `not-found` -- "typically caused by using an invalid or incorrect identifier, such as a list ID or contact ID that doesn't exist in your account". Measured 2026-09-22: an UNROUTED path answers the same 404 shape, so read the `type` field rather than the status to tell a missing record from a missing route. EmailOctopus validates the body before acting and answers 422 with an `errors` array of RFC 9457 entries, each carrying a JSON Pointer to the offending attribute (`{"pointer": "/email_address", "detail": "This value should not be blank."}`), so a 422 creates and changes nothing. The body must be sent with `Content-Type: application/json` or the API answers 415 `unsupported-media-type`. A 403 here is one of two things and neither is a bad key: `access-denied` means this key belongs to a different EmailOctopus account than the list or campaign named (check the last four characters of the key on the account's API keys screen), and `out-of-limits` means the operation would exceed the account's PLAN limits. Only a 401 is a credential problem. EmailOctopus rate-limits with a token bucket: 100 tokens, one per request, refilled at 10 per second, so a burst of 100 or a steady 10 per second. The remainder is in the X-RateLimiting-Remaining response header and a 429 carries X-RateLimit-Retry-After; a 429 is the budget, never the credential.

api
emailoctopus_put_lists_by_list_id_contactsWRITE

Create or update a contact via PUT /lists/{list_id}/contacts. This is an *upsert* endpoint. If the contact does not exist, it will be created. If the contact already exists, it will be updated. UPSERT: this is the create-or-update route, addressed by `email_address` in the body rather than by contact id, so it is the right tool when the caller does not know whether the contact exists. A field set to `null` is REMOVED from the contact and a tag set to `false` is removed; tags the body does not mention are left alone. An id this key cannot see answers 404 `not-found` -- "typically caused by using an invalid or incorrect identifier, such as a list ID or contact ID that doesn't exist in your account". Measured 2026-09-22: an UNROUTED path answers the same 404 shape, so read the `type` field rather than the status to tell a missing record from a missing route. EmailOctopus validates the body before acting and answers 422 with an `errors` array of RFC 9457 entries, each carrying a JSON Pointer to the offending attribute (`{"pointer": "/email_address", "detail": "This value should not be blank."}`), so a 422 creates and changes nothing. The body must be sent with `Content-Type: application/json` or the API answers 415 `unsupported-media-type`. A 403 here is one of two things and neither is a bad key: `access-denied` means this key belongs to a different EmailOctopus account than the list or campaign named (check the last four characters of the key on the account's API keys screen), and `out-of-limits` means the operation would exceed the account's PLAN limits. Only a 401 is a credential problem. EmailOctopus rate-limits with a token bucket: 100 tokens, one per request, refilled at 10 per second, so a burst of 100 or a steady 10 per second. The remainder is in the X-RateLimiting-Remaining response header and a 429 carries X-RateLimit-Retry-After; a 429 is the budget, never the credential.

api
emailoctopus_put_lists_by_list_id_contacts_batchWRITE

Update contacts in a batch via PUT /lists/{list_id}/contacts/batch. Up to a page of contacts in one call, each addressed by `id` or by the MD5 hash of its lowercase email address. PARTIAL SUCCESS IS THE NORMAL OUTCOME and the status code does not report it: the 200 body carries a `success` array and an `errors` array, and each error names the contact's id with its own status and detail. Read `errors`, never the status, to find out what did not happen. An id this key cannot see answers 404 `not-found` -- "typically caused by using an invalid or incorrect identifier, such as a list ID or contact ID that doesn't exist in your account". Measured 2026-09-22: an UNROUTED path answers the same 404 shape, so read the `type` field rather than the status to tell a missing record from a missing route. EmailOctopus validates the body before acting and answers 422 with an `errors` array of RFC 9457 entries, each carrying a JSON Pointer to the offending attribute (`{"pointer": "/email_address", "detail": "This value should not be blank."}`), so a 422 creates and changes nothing. The body must be sent with `Content-Type: application/json` or the API answers 415 `unsupported-media-type`. A 403 here is one of two things and neither is a bad key: `access-denied` means this key belongs to a different EmailOctopus account than the list or campaign named (check the last four characters of the key on the account's API keys screen), and `out-of-limits` means the operation would exceed the account's PLAN limits. Only a 401 is a credential problem. EmailOctopus rate-limits with a token bucket: 100 tokens, one per request, refilled at 10 per second, so a burst of 100 or a steady 10 per second. The remainder is in the X-RateLimiting-Remaining response header and a 429 carries X-RateLimit-Retry-After; a 429 is the budget, never the credential.

api
emailoctopus_put_lists_by_list_id_contacts_by_contact_idWRITE

Update a contact via PUT /lists/{list_id}/contacts/{contact_id}. Updates a contact addressed by id (or by the MD5 hash of its lowercase email address). Every body field is optional here, unlike the upsert: a field set to `null` is removed from the contact and a tag set to `false` is removed, while tags the body does not mention are left alone. Changing `status` to `unsubscribed` is how a contact is unsubscribed through the API. An id this key cannot see answers 404 `not-found` -- "typically caused by using an invalid or incorrect identifier, such as a list ID or contact ID that doesn't exist in your account". Measured 2026-09-22: an UNROUTED path answers the same 404 shape, so read the `type` field rather than the status to tell a missing record from a missing route. EmailOctopus validates the body before acting and answers 422 with an `errors` array of RFC 9457 entries, each carrying a JSON Pointer to the offending attribute (`{"pointer": "/email_address", "detail": "This value should not be blank."}`), so a 422 creates and changes nothing. The body must be sent with `Content-Type: application/json` or the API answers 415 `unsupported-media-type`. A 403 here is one of two things and neither is a bad key: `access-denied` means this key belongs to a different EmailOctopus account than the list or campaign named (check the last four characters of the key on the account's API keys screen), and `out-of-limits` means the operation would exceed the account's PLAN limits. Only a 401 is a credential problem. EmailOctopus rate-limits with a token bucket: 100 tokens, one per request, refilled at 10 per second, so a burst of 100 or a steady 10 per second. The remainder is in the X-RateLimiting-Remaining response header and a 429 carries X-RateLimit-Retry-After; a 429 is the budget, never the credential.

api
emailoctopus_put_lists_by_list_id_fields_by_tagWRITE

Update a custom field on a list via PUT /lists/{list_id}/fields/{tag}. The `{tag}` in the path names the EXISTING field; `tag` in the body is what it becomes, so this route can rename the key itself -- and a rename changes the key every contact's `fields` object and every campaign's personalisation must use. `label`, `tag` and `type` are all required, so this replaces the field's definition rather than patching one attribute. An id this key cannot see answers 404 `not-found` -- "typically caused by using an invalid or incorrect identifier, such as a list ID or contact ID that doesn't exist in your account". Measured 2026-09-22: an UNROUTED path answers the same 404 shape, so read the `type` field rather than the status to tell a missing record from a missing route. EmailOctopus validates the body before acting and answers 422 with an `errors` array of RFC 9457 entries, each carrying a JSON Pointer to the offending attribute (`{"pointer": "/email_address", "detail": "This value should not be blank."}`), so a 422 creates and changes nothing. The body must be sent with `Content-Type: application/json` or the API answers 415 `unsupported-media-type`. A 403 here is one of two things and neither is a bad key: `access-denied` means this key belongs to a different EmailOctopus account than the list or campaign named (check the last four characters of the key on the account's API keys screen), and `out-of-limits` means the operation would exceed the account's PLAN limits. Only a 401 is a credential problem. EmailOctopus rate-limits with a token bucket: 100 tokens, one per request, refilled at 10 per second, so a burst of 100 or a steady 10 per second. The remainder is in the X-RateLimiting-Remaining response header and a 429 carries X-RateLimit-Retry-After; a 429 is the budget, never the credential.

api
emailoctopus_put_lists_by_list_id_tags_by_tagWRITE

Rename a tag on a list via PUT /lists/{list_id}/tags/{tag}. The `{tag}` in the path names the existing tag and `tag` in the body is its new name; the rename follows through to every contact carrying it. An id this key cannot see answers 404 `not-found` -- "typically caused by using an invalid or incorrect identifier, such as a list ID or contact ID that doesn't exist in your account". Measured 2026-09-22: an UNROUTED path answers the same 404 shape, so read the `type` field rather than the status to tell a missing record from a missing route. EmailOctopus validates the body before acting and answers 422 with an `errors` array of RFC 9457 entries, each carrying a JSON Pointer to the offending attribute (`{"pointer": "/email_address", "detail": "This value should not be blank."}`), so a 422 creates and changes nothing. The body must be sent with `Content-Type: application/json` or the API answers 415 `unsupported-media-type`. A 403 here is one of two things and neither is a bad key: `access-denied` means this key belongs to a different EmailOctopus account than the list or campaign named (check the last four characters of the key on the account's API keys screen), and `out-of-limits` means the operation would exceed the account's PLAN limits. Only a 401 is a credential problem. EmailOctopus rate-limits with a token bucket: 100 tokens, one per request, refilled at 10 per second, so a burst of 100 or a steady 10 per second. The remainder is in the X-RateLimiting-Remaining response header and a 429 carries X-RateLimit-Retry-After; a 429 is the budget, never the credential.

api

Put EmailOctopus behind one governed endpoint.

Same permissions, same audit trail, whatever else you connect next.