Skip to content
Early access: send from your own mailbox today. What works

API reference

Manage leads, campaigns, suppressions and replies from your own code, Zapier, Make or n8n.

Basics

Base URL https://app.inboxed.si/api/v1. Send a workspace API key as Authorization: Bearer ibx_…. Keys are created by owners and admins in Settings → API, can expire, and can be rotated (the old key keeps working for 24 hours).

Rate limits. 600 requests a minute per key. Every answer has RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset (seconds); a 429 adds Retry-After. Lead writes are also limited to 120 requests per 10 minutes per workspace, because new leads are verified.

Errors are JSON with a readable error and a stable code: {"error": "Campaign not found.", "code": "not_found"}. Validation errors add details.

Pagination. List endpoints take limit (default 50, at most 100) and return {"data": [...], "next_cursor": "…"}. Pass cursor=next_cursor for the next page; it’s null on the last one.

Idempotency. Send Idempotency-Key: <any unique string> on a POST. Retrying with the same key within 24 hours returns the first answer (with Idempotent-Replayed: true) instead of doing it twice. Reusing a key for a different request is a 422.

IP allowlist. If the workspace owner set one, requests from other addresses get 403 ip_not_allowed.

Scopes

read
Campaigns, stats, lists, leads, mailboxes, conversations, suppressions, wallet, webhook subscriptions.
leads:write
Create and update lists and leads (verified on add).
campaigns:write
Pause campaigns and remove leads from them.
campaigns:launch
Add leads to running campaigns and resume them. These send (and charge for) more emails. Manager role.
inbox:write
Write replies in the team inbox. They wait for approval unless you ask to send and the key’s creator can approve.
suppressions:write
Add and remove addresses and domains on the suppress list.
admin
Everything above, plus erasing a person’s data.

Every write scope includes read. Spending scopes (campaigns:launch, admin) also check that the person who created the key still has that role. Keys made before scopes existed have read, campaigns:write, campaigns:launch, inbox:write: exactly what they could do before.

Campaigns

GET/campaignsList campaignsread

Parameters

limit query
integer · Items per page, 1 to 100.
cursor query
string · The next_cursor from the previous page.
status query
"draft" | "setting_up" | "active" | "paused" | "completed" | "stopped"
curl -X GET "https://app.inboxed.si/api/v1/campaigns" \
  -H "Authorization: Bearer $INBOXED_API_KEY"
GET/campaigns/{id}Get a campaignread

Parameters

id path
string
curl -X GET "https://app.inboxed.si/api/v1/campaigns/00000000-0000-0000-0000-000000000000" \
  -H "Authorization: Bearer $INBOXED_API_KEY"
GET/campaigns/{id}/statsCampaign statsread

Results and how many enrolled leads are in each state. Rates are fractions (0.05 = 5%); opens are estimates.

Parameters

id path
string
curl -X GET "https://app.inboxed.si/api/v1/campaigns/00000000-0000-0000-0000-000000000000/stats" \
  -H "Authorization: Bearer $INBOXED_API_KEY"
POST/campaigns/{id}/pausePause a campaigncampaigns:write

Free. Unsent emails aren’t charged.

Parameters

id path
string
curl -X POST "https://app.inboxed.si/api/v1/campaigns/00000000-0000-0000-0000-000000000000/pause" \
  -H "Authorization: Bearer $INBOXED_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
POST/campaigns/{id}/resumeResume a campaigncampaigns:launch

Starts sending (and charging) again. Needs wallet balance, the campaigns:launch scope, and a key creator who is still a manager.

Parameters

id path
string
curl -X POST "https://app.inboxed.si/api/v1/campaigns/00000000-0000-0000-0000-000000000000/resume" \
  -H "Authorization: Bearer $INBOXED_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
POST/campaigns/{id}/leadsAdd leads to a running campaigncampaigns:launch

Copies existing leads (leadIds) or creates new ones (leads, verified on add) in the campaign’s list and enrols them. Up to 1000 per request. This sends more emails, so it needs campaigns:launch.

Parameters

id path
string

Body

leadIds
string[] · Existing leads (any list) to copy into the campaign's list.
leads
object[] · New leads to add to the campaign's list (verified on add).
curl -X POST "https://app.inboxed.si/api/v1/campaigns/00000000-0000-0000-0000-000000000000/leads" \
  -H "Authorization: Bearer $INBOXED_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"leads":[{"email":"ana@example.com","firstName":"Ana"}],"leadIds":["00000000-0000-0000-0000-000000000000"]}'
POST/campaigns/{id}/leads/removeStop leads in a campaigncampaigns:write

Stops leads for good (by leadIds or emails). They get no more emails and are never charged. Leads that replied or bounced are left alone.

Parameters

id path
string

Body

leadIds
string[]
emails
string[]
curl -X POST "https://app.inboxed.si/api/v1/campaigns/00000000-0000-0000-0000-000000000000/leads/remove" \
  -H "Authorization: Bearer $INBOXED_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"emails":["ana@example.com"]}'

Leads

GET/listsList lead listsread

Parameters

limit query
integer · Items per page, 1 to 100.
cursor query
string · The next_cursor from the previous page.
curl -X GET "https://app.inboxed.si/api/v1/lists" \
  -H "Authorization: Bearer $INBOXED_API_KEY"
POST/listsCreate a listleads:write

Body

name *
string
curl -X POST "https://app.inboxed.si/api/v1/lists" \
  -H "Authorization: Bearer $INBOXED_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"name":"Webinar sign-ups"}'
GET/lists/{id}Get a listread

Parameters

id path
string
curl -X GET "https://app.inboxed.si/api/v1/lists/00000000-0000-0000-0000-000000000000" \
  -H "Authorization: Bearer $INBOXED_API_KEY"
PATCH/lists/{id}Rename a listleads:write

Parameters

id path
string

Body

name *
string
curl -X PATCH "https://app.inboxed.si/api/v1/lists/00000000-0000-0000-0000-000000000000" \
  -H "Authorization: Bearer $INBOXED_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Webinar sign-ups (March)"}'
DELETE/lists/{id}Delete a list and its leadsleads:write

Refused (409) while a running campaign uses the list.

Parameters

id path
string
curl -X DELETE "https://app.inboxed.si/api/v1/lists/00000000-0000-0000-0000-000000000000" \
  -H "Authorization: Bearer $INBOXED_API_KEY"
GET/lists/{id}/leadsList leads in a listread

Parameters

id path
string
limit query
integer · Items per page, 1 to 100.
cursor query
string · The next_cursor from the previous page.
curl -X GET "https://app.inboxed.si/api/v1/lists/00000000-0000-0000-0000-000000000000/leads" \
  -H "Authorization: Bearer $INBOXED_API_KEY"
POST/lists/{id}/leadsCreate or update leadsleads:write

One lead object, or {"leads": [...]} with up to 1000. Addresses already in the list are updated with the fields you send; repeats in one request count once. New addresses are verified before they’re stored. A single invalid lead returns 422; in bulk, each item reports its own result.

Parameters

id path
string

Body

Either

email *
string
firstName
string or null
lastName
string or null
company
string or null
title
string or null
timezone
string or null · IANA time zone, e.g. Europe/Ljubljana.
country
string or null · ISO 3166 alpha-2.
region
string or null
city
string or null
companySize
integer or null
language
string or null
custom
object · Custom fields by snake_case key, usable as {{key}} in emails.

or

leads *
object[]
curl -X POST "https://app.inboxed.si/api/v1/lists/00000000-0000-0000-0000-000000000000/leads" \
  -H "Authorization: Bearer $INBOXED_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"leads":[{"email":"ana@example.com","firstName":"Ana","company":"Example Co","country":"SI","custom":{"webinar":"March"}}]}'
GET/leads/{id}Get a leadread

Parameters

id path
string
curl -X GET "https://app.inboxed.si/api/v1/leads/00000000-0000-0000-0000-000000000000" \
  -H "Authorization: Bearer $INBOXED_API_KEY"
PATCH/leads/{id}Update a leadleads:write

Send only the fields to change. custom keys are merged.

Parameters

id path
string

Body

firstName
string or null
lastName
string or null
company
string or null
title
string or null
timezone
string or null · IANA time zone, e.g. Europe/Ljubljana.
country
string or null · ISO 3166 alpha-2.
region
string or null
city
string or null
companySize
integer or null
language
string or null
custom
object · Custom fields by snake_case key, usable as {{key}} in emails.
curl -X PATCH "https://app.inboxed.si/api/v1/leads/00000000-0000-0000-0000-000000000000" \
  -H "Authorization: Bearer $INBOXED_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title":"Head of Growth","custom":{"plan":"pro"}}'

Suppressions

GET/suppressionsList the suppress listread

Parameters

limit query
integer
cursor query
string
reason query
"unsubscribe" | "bounce" | "complaint" | "manual" | "crm"
curl -X GET "https://app.inboxed.si/api/v1/suppressions" \
  -H "Authorization: Bearer $INBOXED_API_KEY"
POST/suppressionsAdd emails or domainssuppressions:write

Live sequences to these addresses (or domains) stop straight away.

Body

entries *
string[] · Emails, or domains (acme.com or @acme.com) to block a whole company.
curl -X POST "https://app.inboxed.si/api/v1/suppressions" \
  -H "Authorization: Bearer $INBOXED_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"entries":["someone@example.com","competitor.com"]}'
DELETE/suppressions/{entry}Remove an entrysuppressions:write

URL-encode the email or @domain. Unsubscribes and spam complaints can’t be removed (409).

Parameters

entry path
string
curl -X DELETE "https://app.inboxed.si/api/v1/suppressions/someone%40example.com" \
  -H "Authorization: Bearer $INBOXED_API_KEY"
POST/erasureErase a person (GDPR)admin

Deletes everything the workspace holds about this address and keeps a hash so they’re never emailed again. Can’t be undone. Admin scope.

Body

email *
string
curl -X POST "https://app.inboxed.si/api/v1/erasure" \
  -H "Authorization: Bearer $INBOXED_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"email":"ana@example.com"}'

Mailboxes

GET/mailboxesList mailboxesread
curl -X GET "https://app.inboxed.si/api/v1/mailboxes" \
  -H "Authorization: Bearer $INBOXED_API_KEY"
GET/mailboxes/{id}Mailbox statusread

Connection status, today’s sends, last reply sync and the last DNS check.

Parameters

id path
string
curl -X GET "https://app.inboxed.si/api/v1/mailboxes/00000000-0000-0000-0000-000000000000" \
  -H "Authorization: Bearer $INBOXED_API_KEY"

Inbox

GET/conversationsList conversationsread

Newest first. Filter by status, intent or campaign.

Parameters

status query
"open" | "waiting" | "snoozed" | "closed"
intent query
string
campaignId query
string
limit query
integer
cursor query
string
curl -X GET "https://app.inboxed.si/api/v1/conversations" \
  -H "Authorization: Bearer $INBOXED_API_KEY"
GET/conversations/{id}Get a conversation with its messagesread

Parameters

id path
string
curl -X GET "https://app.inboxed.si/api/v1/conversations/00000000-0000-0000-0000-000000000000" \
  -H "Authorization: Bearer $INBOXED_API_KEY"
GET/conversations/{id}/messagesList messagesread

Oldest first; next_cursor is the last message id.

Parameters

id path
string
limit query
integer · Items per page, 1 to 100.
cursor query
string · The next_cursor from the previous page.
curl -X GET "https://app.inboxed.si/api/v1/conversations/00000000-0000-0000-0000-000000000000/messages" \
  -H "Authorization: Bearer $INBOXED_API_KEY"
POST/conversations/{id}/repliesReply to a conversationinbox:write

Saves a reply in the team inbox. It waits for a manager’s approval, unless you send "send": true and the key’s creator can approve replies (manager or above): then it goes out from the conversation’s mailbox within a minute. Replies are free.

Parameters

id path
string

Body

body *
string
send
boolean
curl -X POST "https://app.inboxed.si/api/v1/conversations/00000000-0000-0000-0000-000000000000/replies" \
  -H "Authorization: Bearer $INBOXED_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"body":"Thanks Ana, Tuesday at 10 works. I’ll send an invite.","send":false}'

Wallet

GET/walletWallet balanceread
curl -X GET "https://app.inboxed.si/api/v1/wallet" \
  -H "Authorization: Bearer $INBOXED_API_KEY"

Webhooks

GET/hooksList hook subscriptionsread
curl -X GET "https://app.inboxed.si/api/v1/hooks" \
  -H "Authorization: Bearer $INBOXED_API_KEY"
POST/hooksSubscribe a URL to an eventread

target_url must be https and resolve to a public address. Subscribing the same URL to the same event again returns the existing subscription.

Body

event *
"campaign.completed" | "campaign.paused" | "email.bounced" | "email.clicked" | "email.opened" | "email.sent" | "lead.added" | "lead.hot" | "lead.unsubscribed" | "mailbox.disconnected" | "reply.interested" | "reply.received"
target_url *
string
source
"zapier" | "make" | "n8n" | "api"
curl -X POST "https://app.inboxed.si/api/v1/hooks" \
  -H "Authorization: Bearer $INBOXED_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"event":"reply.interested","target_url":"https://hooks.example.com/inboxed","source":"api"}'
DELETE/hooks/{id}Delete a hook subscriptionread

Parameters

id path
string
curl -X DELETE "https://app.inboxed.si/api/v1/hooks/00000000-0000-0000-0000-000000000000" \
  -H "Authorization: Bearer $INBOXED_API_KEY"
GET/hooks/sampleSample eventread

An array with one object shaped exactly like a delivery: recent real data, or a made-up one marked "example": true.

Parameters

event query
"campaign.completed" | "campaign.paused" | "email.bounced" | "email.clicked" | "email.opened" | "email.sent" | "lead.added" | "lead.hot" | "lead.unsubscribed" | "mailbox.disconnected" | "reply.interested" | "reply.received"
curl -X GET "https://app.inboxed.si/api/v1/hooks/sample" \
  -H "Authorization: Bearer $INBOXED_API_KEY"

Webhook events

Subscribe a URL with POST /hooks, or set one workspace webhook URL in Settings → Notifications for every event. Each delivery is a POST of {"event", "sentAt", "data"} with headers X-Inboxed-Event, X-Inboxed-Delivery (unique; ignore repeats) and X-Inboxed-Signature: sha256=<hex HMAC-SHA256 of the raw body>, keyed with the workspace webhook secret.

A 2xx within 10 seconds counts as delivered. Anything else is retried after 1 min, 5 min, 30 min, 2 h and 6 h. Answering 410 Gone removes a subscription.

Verify the signature on the raw body before parsing it:

import { createHmac, timingSafeEqual } from "node:crypto";

// rawBody: the request body exactly as received (a Buffer or string), before JSON parsing.
export function verifyInboxed(rawBody, signatureHeader, secret) {
  const expected = "sha256=" + createHmac("sha256", secret).update(rawBody).digest("hex");
  const a = Buffer.from(expected);
  const b = Buffer.from(signatureHeader ?? "");
  return a.length === b.length && timingSafeEqual(a, b);
}
campaign.completedCampaign finished

A campaign has emailed every lead it will email.

data.id
string · Campaign id
data.name
string
data.status
string · completed
data.sent
integer
data.replies
integer
data.positive
integer
data.meetings
integer
data.bounced
integer
data.launchedAt
string
data.url
string · Campaign in the app
{
  "event": "campaign.completed",
  "sentAt": "2026-01-01T09:00:00.000Z",
  "data": {
    "id": "00000000-0000-0000-0000-000000000000",
    "name": "Example campaign",
    "status": "completed",
    "sent": 480,
    "replies": 21,
    "positive": 6,
    "meetings": 2,
    "bounced": 4,
    "launchedAt": "2026-01-01T09:00:00.000Z",
    "url": "https://app.example/app/campaigns/00000000-0000-0000-0000-000000000000"
  }
}
campaign.pausedCampaign paused

A teammate, the API or Inboxed (empty wallet, bounce guard, budget) paused a campaign.

data.id
string · Campaign id
data.name
string
data.status
string · paused
data.reason
string · Why it paused
data.by
string · user, api or system
data.sent
integer
data.url
string
{
  "event": "campaign.paused",
  "sentAt": "2026-01-01T09:00:00.000Z",
  "data": {
    "id": "00000000-0000-0000-0000-000000000000",
    "name": "Example campaign",
    "status": "paused",
    "sent": 120,
    "replies": 4,
    "positive": 1,
    "meetings": 0,
    "bounced": 2,
    "launchedAt": "2026-01-01T09:00:00.000Z",
    "url": "https://app.example/app/campaigns/00000000-0000-0000-0000-000000000000",
    "reason": "Paused through the API.",
    "by": "api"
  }
}
email.bouncedEmail bounced

A send hard-bounced or a bounce report arrived.

data.email
string
data.campaignId
string
data.reason
string · The server’s answer
{
  "event": "email.bounced",
  "sentAt": "2026-01-01T09:00:00.000Z",
  "data": {
    "email": "lead@example.com",
    "campaignId": "00000000-0000-0000-0000-000000000000",
    "reason": "550 5.1.1 User unknown"
  }
}
email.clickedLink clicked

A lead clicked a tracked link (bot clicks excluded).

data.email
string
data.campaignId
string
data.messageId
string
data.subject
string
data.engagement
object · kind, at, step, url, client, device
{
  "event": "email.clicked",
  "sentAt": "2026-01-01T09:00:00.000Z",
  "data": {
    "email": "lead@example.com",
    "campaignId": "00000000-0000-0000-0000-000000000000",
    "messageId": "00000000-0000-0000-0000-000000000000",
    "subject": "Quick question",
    "engagement": {
      "kind": "click",
      "at": "2026-01-01T09:00:00.000Z",
      "step": 1,
      "url": "https://example.com/pricing"
    }
  }
}
email.openedEmail opened (estimated)

A tracked email was opened by a person (machine opens excluded). Opens are estimates.

data.email
string
data.campaignId
string
data.messageId
string
data.subject
string
data.engagement
object · kind, at, step, client, device, estimated
{
  "event": "email.opened",
  "sentAt": "2026-01-01T09:00:00.000Z",
  "data": {
    "email": "lead@example.com",
    "campaignId": "00000000-0000-0000-0000-000000000000",
    "messageId": "00000000-0000-0000-0000-000000000000",
    "subject": "Quick question",
    "engagement": {
      "kind": "open",
      "at": "2026-01-01T09:00:00.000Z",
      "step": 1,
      "estimated": true
    }
  }
}
email.sentEmail sent

A campaign email was accepted by the mail server (and charged).

data.messageId
string · Inboxed id of the sent email
data.campaignId
string
data.email
string · Recipient
data.step
integer · Sequence step, from 1
data.subject
string
data.mailbox
string · Sending address
data.variant
string · A/Z version letter
data.sentAt
string
{
  "event": "email.sent",
  "sentAt": "2026-01-01T09:00:00.000Z",
  "data": {
    "messageId": "00000000-0000-0000-0000-000000000000",
    "campaignId": "00000000-0000-0000-0000-000000000000",
    "email": "lead@example.com",
    "step": 1,
    "subject": "Quick question",
    "mailbox": "you@yourcompany.com",
    "variant": "A",
    "sentAt": "2026-01-01T09:00:00.000Z"
  }
}
lead.addedLead added

A lead was created through the API or added to a running campaign.

data.leadId
string
data.listId
string
data.email
string
data.firstName
string
data.lastName
string
data.company
string
data.verifyStatus
string · valid, invalid, risky, catch_all, disposable, role_based or unverified
data.source
string · api or campaign
data.url
string
{
  "event": "lead.added",
  "sentAt": "2026-01-01T09:00:00.000Z",
  "data": {
    "leadId": "00000000-0000-0000-0000-000000000000",
    "listId": "00000000-0000-0000-0000-000000000000",
    "email": "lead@example.com",
    "firstName": "Example",
    "lastName": "Lead",
    "company": "Example Co",
    "verifyStatus": "valid",
    "source": "api",
    "url": "https://app.example/app/leads/00000000-0000-0000-0000-000000000000"
  }
}
lead.hotHot lead

A lead opened, clicked or replied enough to count as hot.

data.email
string
data.leadId
string
data.campaignId
string
data.engagement
object
data.detail
string
{
  "event": "lead.hot",
  "sentAt": "2026-01-01T09:00:00.000Z",
  "data": {
    "email": "lead@example.com",
    "leadId": "00000000-0000-0000-0000-000000000000",
    "campaignId": "00000000-0000-0000-0000-000000000000",
    "detail": "Clicked https://example.com/pricing"
  }
}
lead.unsubscribedLead unsubscribed

A lead unsubscribed by link or by replying.

data.email
string
data.campaignId
string
data.via
string · link, reply or crm
{
  "event": "lead.unsubscribed",
  "sentAt": "2026-01-01T09:00:00.000Z",
  "data": {
    "email": "lead@example.com",
    "campaignId": "00000000-0000-0000-0000-000000000000",
    "via": "link"
  }
}
mailbox.disconnectedMailbox disconnected

A mailbox can’t send: its sign-in failed or it was disconnected. Sent once per incident.

data.mailboxId
string
data.email
string
data.provider
string · google, microsoft or smtp
data.reason
string · The provider’s error
data.reconnectUrl
string · Where to reconnect it
{
  "event": "mailbox.disconnected",
  "sentAt": "2026-01-01T09:00:00.000Z",
  "data": {
    "mailboxId": "00000000-0000-0000-0000-000000000000",
    "email": "you@yourcompany.com",
    "provider": "google",
    "reason": "535 5.7.8 Username and Password not accepted",
    "reconnectUrl": "https://app.example/app/mailboxes/00000000-0000-0000-0000-000000000000"
  }
}
reply.interestedInterested reply

A reply is tagged Interested or Meeting request.

data.conversationId
string
data.campaignId
string
data.lead
string
data.email
string
data.company
string
data.intent
string
data.excerpt
string
data.url
string
{
  "event": "reply.interested",
  "sentAt": "2026-01-01T09:00:00.000Z",
  "data": {
    "conversationId": "00000000-0000-0000-0000-000000000000",
    "campaignId": "00000000-0000-0000-0000-000000000000",
    "lead": "Example Lead",
    "email": "lead@example.com",
    "company": "Example Co",
    "intent": "Interested",
    "excerpt": "Sounds good. Can we talk next week?",
    "url": "https://app.example/app/inbox?c=00000000-0000-0000-0000-000000000000"
  }
}
reply.receivedReply received

A lead replied (auto-replies such as out-of-office aren’t sent).

data.conversationId
string
data.campaignId
string
data.lead
string
data.email
string
data.company
string
data.intent
string
data.excerpt
string
data.url
string
{
  "event": "reply.received",
  "sentAt": "2026-01-01T09:00:00.000Z",
  "data": {
    "conversationId": "00000000-0000-0000-0000-000000000000",
    "campaignId": "00000000-0000-0000-0000-000000000000",
    "lead": "Example Lead",
    "email": "lead@example.com",
    "company": "Example Co",
    "intent": "Question",
    "excerpt": "How does pricing work?",
    "url": "https://app.example/app/inbox?c=00000000-0000-0000-0000-000000000000"
  }
}