API Reference

Complete documentation for the MailerBot REST API.

Getting Started

MailerBot provides a RESTful API for automating direct mail. All requests and responses use JSON with camelCase field names.

To get started, create an account via POST /auth/register, then authenticate using either JWT tokens or API keys.

Authentication

Two authentication methods are supported:

1. JWT Bearer Token

Obtain via POST /auth/login with form-encoded body (username + password). Pass as Authorization: Bearer {token}. Access tokens expire in 30 minutes. Refresh via POST /auth/refresh with { "refreshToken": "..." }. Refresh tokens last 7 days.

2. API Key

Create in the dashboard under Settings → API Keys. Pass as X-API-Key: mb_live_... header. Keys are shown only once on creation.

Live vs Test Keys

API keys come in two modes, determined by their prefix:

  • mb_live_...Live keys process payments and fulfill orders normally.
  • mb_test_...Test keys skip payment entirely and mark orders as test so they are never printed or mailed. Use these when building and testing integrations.

Test keys share the same data (contacts, documents, templates) as live keys — the only difference is that mailings created with a test key bypass payment and fulfillment. The mailing response will include isTest: true and paymentStatus: "test_skipped".

Resolution order: JWT checked first, then API Key.

Base URL

https://api.mailerbot.com/api/v1

Error Handling

Standard error format:

{
  "detail": "Error description"
}
Status CodeDescription
400Bad Request
401Unauthorized
404Not Found
422Validation Error (includes loc, msg, type fields in detail array)

Authentication

POST /auth/register Public

Register a new user account.

Request Body

FieldTypeRequiredDescription
emailstringRequiredValid email address
passwordstringRequiredMinimum 8 characters
fullNamestring|nullOptionalUser's full name

Response 201

FieldType
idstring (UUID)
emailstring
fullNamestring|null
isActiveboolean
createdAtdatetime
cURL
curl -X POST https://api.mailerbot.com/api/v1/auth/register \
  -H "Content-Type: application/json" \
  -d '{
    "email": "user@example.com",
    "password": "securepass123",
    "fullName": "Jane Doe"
  }'
JavaScript
const res = await fetch("https://api.mailerbot.com/api/v1/auth/register", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    email: "user@example.com",
    password: "securepass123",
    fullName: "Jane Doe"
  })
});
const user = await res.json();
Python
import requests

res = requests.post(
    "https://api.mailerbot.com/api/v1/auth/register",
    json={
        "email": "user@example.com",
        "password": "securepass123",
        "fullName": "Jane Doe"
    }
)
user = res.json()
POST /auth/login Public

Authenticate with email and password. Uses application/x-www-form-urlencoded content type.

Request Body (form-encoded)

FieldTypeRequiredDescription
usernamestringRequiredThe user's email address
passwordstringRequiredAccount password

Response 200 — Standard

FieldType
accessTokenstring
refreshTokenstring
tokenTypestring ("bearer")
expiresIninteger (1800)

MFA Challenge Response 200

When MFA is enabled on the account, the login response returns an MFA challenge instead of tokens directly. Complete authentication by calling POST /auth/mfa/verify with the returned mfa_token.

FieldTypeDescription
mfa_tokenstringTemporary MFA session token
mfa_requiredboolean (true)Indicates MFA challenge is pending
cURL
curl -X POST https://api.mailerbot.com/api/v1/auth/login \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "username=user@example.com&password=securepass123"
JavaScript
const res = await fetch("https://api.mailerbot.com/api/v1/auth/login", {
  method: "POST",
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  body: new URLSearchParams({
    username: "user@example.com",
    password: "securepass123"
  })
});
const { accessToken, refreshToken } = await res.json();
Python
import requests

res = requests.post(
    "https://api.mailerbot.com/api/v1/auth/login",
    data={
        "username": "user@example.com",
        "password": "securepass123"
    }
)
tokens = res.json()
POST /auth/refresh Public

Refresh an expired access token using a valid refresh token.

Request Body

FieldTypeRequired
refreshTokenstringRequired

Response 200

Same schema as login response (accessToken, refreshToken, tokenType, expiresIn).

cURL
curl -X POST https://api.mailerbot.com/api/v1/auth/refresh \
  -H "Content-Type: application/json" \
  -d '{ "refreshToken": "eyJhbGci..." }'
POST /auth/forgot-password Public Rate limited: 3/min

Request a password reset email. Always returns success to prevent email enumeration.

Request Body

FieldTypeRequired
emailstringRequired

Response 200

FieldType
messagestring
cURL
curl -X POST https://api.mailerbot.com/api/v1/auth/forgot-password \
  -H "Content-Type: application/json" \
  -d '{ "email": "user@example.com" }'
POST /auth/reset-password Public Rate limited: 5/min

Reset a user's password using the token from the forgot-password email.

Request Body

FieldTypeRequired
tokenstringRequired
new_passwordstringRequired

Response 200

FieldType
messagestring
cURL
curl -X POST https://api.mailerbot.com/api/v1/auth/reset-password \
  -H "Content-Type: application/json" \
  -d '{ "token": "reset_abc123...", "new_password": "newsecurepass123" }'
POST /auth/verify-email Public Rate limited: 10/min

Verify a user's email address using the token from the verification email.

Request Body

FieldTypeRequired
tokenstringRequired

Response 200

FieldType
messagestring
cURL
curl -X POST https://api.mailerbot.com/api/v1/auth/verify-email \
  -H "Content-Type: application/json" \
  -d '{ "token": "verify_abc123..." }'
POST /auth/resend-verification Public Rate limited: 3/min

Resend the email verification link to the given address.

Request Body

FieldTypeRequired
emailstringRequired

Response 200

FieldType
messagestring
cURL
curl -X POST https://api.mailerbot.com/api/v1/auth/resend-verification \
  -H "Content-Type: application/json" \
  -d '{ "email": "user@example.com" }'
POST /auth/check-passkey Public

Check whether the given email address has a passkey registered, to determine if passwordless login is available.

Request Body

FieldTypeRequired
emailstringRequired

Response 200

FieldType
has_passkeyboolean
cURL
curl -X POST https://api.mailerbot.com/api/v1/auth/check-passkey \
  -H "Content-Type: application/json" \
  -d '{ "email": "user@example.com" }'
POST /auth/passkey-login Public

Start a passwordless passkey login flow. Returns WebAuthn authentication options that should be passed to the browser's WebAuthn API.

Request Body

FieldTypeRequired
emailstringRequired

Response 200

WebAuthn authentication options object (challenge, allowCredentials, timeout, etc.).

cURL
curl -X POST https://api.mailerbot.com/api/v1/auth/passkey-login \
  -H "Content-Type: application/json" \
  -d '{ "email": "user@example.com" }'
GET /auth/me JWT Only

Get the current authenticated user's profile.

Response 200

Returns UserResponse (id, email, fullName, isActive, createdAt).

cURL
curl https://api.mailerbot.com/api/v1/auth/me \
  -H "Authorization: Bearer eyJhbGci..."
PUT /auth/me JWT Only

Update current user profile.

Request Body

FieldTypeRequiredDescription
fullNamestring|nullOptionalUser's full name
returnAddressobject|nullOptionalReturn address (name, company, addressLine1, addressLine2, city, state, zip)
cURL
curl -X PUT https://api.mailerbot.com/api/v1/auth/me \
  -H "Authorization: Bearer eyJhbGci..." \
  -H "Content-Type: application/json" \
  -d '{
    "fullName": "Jane Smith",
    "returnAddress": {
      "name": "Jane Smith",
      "company": "Acme Corp",
      "addressLine1": "123 Main St",
      "city": "Austin",
      "state": "TX",
      "zip": "78701"
    }
  }'
POST /auth/api-keys JWT Only

Create a new API key. The full key value is returned only once in the response.

Request Body

FieldTypeRequiredDescription
namestringRequiredDescriptive name for the key
scopesstring[]OptionalPermission scopes
prefixstringOptional"mb_live" (default) or "mb_test". Test keys skip payment and mark mailings as test — they won't be fulfilled.

Response 201

FieldType
idstring (UUID)
namestring
prefixstring
lastFourstring
scopesstring[]
isActiveboolean
createdAtdatetime
lastUsedAtdatetime|null
keystring
cURL
# Create a live key
curl -X POST https://api.mailerbot.com/api/v1/auth/api-keys \
  -H "Authorization: Bearer eyJhbGci..." \
  -H "Content-Type: application/json" \
  -d '{ "name": "Production Key", "prefix": "mb_live" }'

# Create a test key (for integration testing)
curl -X POST https://api.mailerbot.com/api/v1/auth/api-keys \
  -H "Authorization: Bearer eyJhbGci..." \
  -H "Content-Type: application/json" \
  -d '{ "name": "Test Key", "prefix": "mb_test" }'
GET /auth/api-keys JWT Only

List all API keys for the current user. The full key value is not returned.

Response 200

Array of API key objects (id, name, prefix, lastFour, scopes, isActive, createdAt, lastUsedAt).

cURL
curl https://api.mailerbot.com/api/v1/auth/api-keys \
  -H "Authorization: Bearer eyJhbGci..."
DELETE /auth/api-keys/{key_id} JWT Only

Revoke an API key. This action cannot be undone.

Path Parameters

ParameterTypeDescription
key_idstring (UUID)The API key ID to revoke

Response 204

No content.

cURL
curl -X DELETE https://api.mailerbot.com/api/v1/auth/api-keys/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer eyJhbGci..."

Documents

GET /documents JWT / API Key

List documents with optional filtering.

Query Parameters

ParameterTypeDefaultDescription
pageinteger1Page number
pageSizeinteger20Items per page
typestring--Filter by type

Response 200

PaginatedResponse of DocumentResponse.

cURL
curl https://api.mailerbot.com/api/v1/documents?page=1&pageSize=20 \
  -H "Authorization: Bearer eyJhbGci..."
POST /documents JWT / API Key

Create a new document.

Request Body

FieldTypeDefaultDescription
titlestring"Untitled Document"Document title
contentstring""HTML content

Response 201

FieldType
idstring (UUID)
titlestring
contentstring
createdAtdatetime
updatedAtdatetime
typestring
pdfUrlstring|null
thumbnailUrlstring|null
pageCountinteger|null
fileSizeinteger|null
cURL
curl -X POST https://api.mailerbot.com/api/v1/documents \
  -H "Authorization: Bearer eyJhbGci..." \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Q1 Welcome Letter",
    "content": "<p>Dear {{firstName}},</p><p>Welcome!</p>"
  }'
JavaScript
const res = await fetch("https://api.mailerbot.com/api/v1/documents", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${accessToken}`,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    title: "Q1 Welcome Letter",
    content: "<p>Dear {{firstName}},</p>"
  })
});
const doc = await res.json();
Python
import requests

res = requests.post(
    "https://api.mailerbot.com/api/v1/documents",
    headers={"Authorization": f"Bearer {access_token}"},
    json={
        "title": "Q1 Welcome Letter",
        "content": "<p>Dear {{firstName}},</p>"
    }
)
doc = res.json()
GET /documents/{document_id} JWT / API Key

Get a single document by ID.

Path Parameters

ParameterType
document_idstring (UUID)

Response 200

DocumentResponse object.

cURL
curl https://api.mailerbot.com/api/v1/documents/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer eyJhbGci..."
PUT /documents/{document_id} JWT / API Key

Update a document.

Request Body

FieldTypeRequired
titlestring|nullOptional
contentstring|nullOptional
cURL
curl -X PUT https://api.mailerbot.com/api/v1/documents/550e8400-... \
  -H "Authorization: Bearer eyJhbGci..." \
  -H "Content-Type: application/json" \
  -d '{ "title": "Updated Title" }'
DELETE /documents/{document_id} JWT / API Key

Delete a document.

Response 204

No content.

cURL
curl -X DELETE https://api.mailerbot.com/api/v1/documents/550e8400-... \
  -H "Authorization: Bearer eyJhbGci..."
POST /documents/upload JWT / API Key

Upload a PDF document. Max file size: 25 MB. Content type must be multipart/form-data.

Request Body (multipart/form-data)

FieldTypeRequiredDescription
filefile (PDF)RequiredPDF file to upload

Response 201

DocumentResponse object.

cURL
curl -X POST https://api.mailerbot.com/api/v1/documents/upload \
  -H "Authorization: Bearer eyJhbGci..." \
  -F "file=@/path/to/document.pdf"

Contacts

GET /contacts JWT / API Key

List contacts with pagination.

Query Parameters

ParameterTypeDefault
pageinteger1
pageSizeinteger20
searchstring--
cURL
curl https://api.mailerbot.com/api/v1/contacts?page=1&pageSize=20 \
  -H "Authorization: Bearer eyJhbGci..."
POST /contacts JWT / API Key

Create a new contact.

Request Body

FieldTypeRequired
firstNamestringRequired
lastNamestringRequired
companystring|nullOptional
addressLine1stringRequired
addressLine2string|nullOptional
citystringRequired
statestringRequired
zipstringRequired
countryCodestringOptional
emailstring|nullOptional
phonestring|nullOptional

Country code: ISO 3166-1 alpha-2 code (e.g. "US", "CA", "GB"). Defaults to "US" if omitted. Use List Countries to get valid codes.

Response 201

FieldType
idstring (UUID)
firstNamestring
lastNamestring
companystring|null
addressLine1string
addressLine2string|null
citystring
statestring
zipstring
countryCodestring
countryNamestring
emailstring|null
phonestring|null
validationStatusstring
correctedAddressobject|null
cURL
curl -X POST https://api.mailerbot.com/api/v1/contacts \
  -H "Authorization: Bearer eyJhbGci..." \
  -H "Content-Type: application/json" \
  -d '{
    "firstName": "John",
    "lastName": "Smith",
    "addressLine1": "456 Oak Ave",
    "city": "Portland",
    "state": "OR",
    "zip": "97201",
    "countryCode": "US"
  }'
GET /contacts/{contact_id} JWT / API Key

Get a single contact by ID.

cURL
curl https://api.mailerbot.com/api/v1/contacts/550e8400-... \
  -H "Authorization: Bearer eyJhbGci..."
PUT /contacts/{contact_id} JWT / API Key

Update a contact. All fields are optional.

cURL
curl -X PUT https://api.mailerbot.com/api/v1/contacts/550e8400-... \
  -H "Authorization: Bearer eyJhbGci..." \
  -H "Content-Type: application/json" \
  -d '{ "company": "Acme Corp", "phone": "555-0123" }'
DELETE /contacts/{contact_id} JWT / API Key

Delete a contact.

Response 204

No content.

cURL
curl -X DELETE https://api.mailerbot.com/api/v1/contacts/550e8400-... \
  -H "Authorization: Bearer eyJhbGci..."
POST /contacts/import JWT / API Key

Bulk import contacts. Optionally creates a contact list.

Request Body

FieldTypeRequiredDescription
contactsContactCreate[]RequiredArray of contact objects
listNamestring|nullOptionalCreates a contact list with this name

Response 201

FieldType
importedCountinteger
listIdstring|null
cURL
curl -X POST https://api.mailerbot.com/api/v1/contacts/import \
  -H "Authorization: Bearer eyJhbGci..." \
  -H "Content-Type: application/json" \
  -d '{
    "contacts": [
      {
        "firstName": "Alice",
        "lastName": "Johnson",
        "addressLine1": "789 Elm St",
        "city": "Denver",
        "state": "CO",
        "zip": "80201",
        "countryCode": "US"
      }
    ],
    "listName": "Q1 Prospects"
  }'
JavaScript
const res = await fetch("https://api.mailerbot.com/api/v1/contacts/import", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${accessToken}`,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    contacts: [
      { firstName: "Alice", lastName: "Johnson",
        addressLine1: "789 Elm St", city: "Denver",
        state: "CO", zip: "80201" }
    ],
    listName: "Q1 Prospects"
  })
});
const result = await res.json();
Python
import requests

res = requests.post(
    "https://api.mailerbot.com/api/v1/contacts/import",
    headers={"Authorization": f"Bearer {access_token}"},
    json={
        "contacts": [{
            "firstName": "Alice",
            "lastName": "Johnson",
            "addressLine1": "789 Elm St",
            "city": "Denver",
            "state": "CO",
            "zip": "80201"
        }],
        "listName": "Q1 Prospects"
    }
)
POST /contacts/validate JWT / API Key

Validate addresses for a set of contacts.

Request Body

FieldTypeRequired
contactIdsstring[]Required

Response 200

FieldType
contactIdstring
statusstring ("valid"|"invalid"|"corrected")
originalobject
correctedobject|null
cURL
curl -X POST https://api.mailerbot.com/api/v1/contacts/validate \
  -H "Authorization: Bearer eyJhbGci..." \
  -H "Content-Type: application/json" \
  -d '{ "contactIds": ["uuid-1", "uuid-2"] }'

Contact Lists

GET /contact-lists JWT / API Key

List all contact lists.

Response 200

FieldType
idstring (UUID)
namestring
contactIdsstring[]
createdAtdatetime
updatedAtdatetime
cURL
curl https://api.mailerbot.com/api/v1/contact-lists \
  -H "Authorization: Bearer eyJhbGci..."
POST /contact-lists JWT / API Key

Create a new contact list.

Request Body

FieldTypeRequired
namestringRequired
contactIdsstring[]Optional
cURL
curl -X POST https://api.mailerbot.com/api/v1/contact-lists \
  -H "Authorization: Bearer eyJhbGci..." \
  -H "Content-Type: application/json" \
  -d '{ "name": "VIP Customers", "contactIds": ["uuid-1", "uuid-2"] }'
GET /contact-lists/{list_id} JWT / API Key

Get a contact list by ID.

cURL
curl https://api.mailerbot.com/api/v1/contact-lists/550e8400-... \
  -H "Authorization: Bearer eyJhbGci..."
PUT /contact-lists/{list_id} JWT / API Key

Update a contact list name.

Request Body

FieldTypeRequired
namestring|nullOptional
cURL
curl -X PUT https://api.mailerbot.com/api/v1/contact-lists/550e8400-... \
  -H "Authorization: Bearer eyJhbGci..." \
  -H "Content-Type: application/json" \
  -d '{ "name": "Premium Customers" }'
DELETE /contact-lists/{list_id} JWT / API Key

Delete a contact list.

Response 204

No content.

cURL
curl -X DELETE https://api.mailerbot.com/api/v1/contact-lists/550e8400-... \
  -H "Authorization: Bearer eyJhbGci..."
POST /contact-lists/{list_id}/contacts JWT / API Key

Add contacts to an existing list.

Request Body

FieldTypeRequired
contactIdsstring[]Required

Response 204

No content.

cURL
curl -X POST https://api.mailerbot.com/api/v1/contact-lists/550e8400-.../contacts \
  -H "Authorization: Bearer eyJhbGci..." \
  -H "Content-Type: application/json" \
  -d '{ "contactIds": ["uuid-1", "uuid-2"] }'
DELETE /contact-lists/{list_id}/contacts JWT / API Key

Remove contacts from a list.

Request Body

FieldTypeRequired
contactIdsstring[]Required

Response 204

No content.

cURL
curl -X DELETE https://api.mailerbot.com/api/v1/contact-lists/550e8400-.../contacts \
  -H "Authorization: Bearer eyJhbGci..." \
  -H "Content-Type: application/json" \
  -d '{ "contactIds": ["uuid-1"] }'

Postcards

GET /postcards/templates Public

List all available postcard templates.

Response 200

FieldType
idstring
namestring
descriptionstring
categorystring
thumbnailUrlstring
frontobject
backobject
cURL
curl https://api.mailerbot.com/api/v1/postcards/templates
GET /postcards/templates/{template_id} Public

Get a single postcard template by ID.

cURL
curl https://api.mailerbot.com/api/v1/postcards/templates/tpl-001
GET /postcards JWT / API Key

List user postcards with pagination.

Query Parameters

ParameterTypeDefault
pageinteger1
pageSizeinteger20

Response 200

FieldType
idstring (UUID)
titlestring
frontPostcardSide
backPostcardSide
widthinteger
heightinteger
createdAtdatetime
updatedAtdatetime
templateIdstring|null
cURL
curl https://api.mailerbot.com/api/v1/postcards?page=1&pageSize=10 \
  -H "Authorization: Bearer eyJhbGci..."
POST /postcards JWT / API Key

Create a new postcard.

Request Body

FieldTypeDefaultDescription
titlestring--Postcard title
frontPostcardSide|nullnullFront side design
backPostcardSide|nullnullBack side design
widthinteger1800Width in pixels (300 DPI)
heightinteger1275Height in pixels (300 DPI)

PostcardSide: { elements: CanvasElement[], backgroundColor: string, backgroundImage: string|null }

cURL
curl -X POST https://api.mailerbot.com/api/v1/postcards \
  -H "Authorization: Bearer eyJhbGci..." \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Summer Sale Postcard",
    "width": 1800,
    "height": 1275,
    "front": {
      "elements": [],
      "backgroundColor": "#FFFFFF",
      "backgroundImage": null
    }
  }'
GET /postcards/{postcard_id} JWT / API Key

Get a single postcard by ID.

cURL
curl https://api.mailerbot.com/api/v1/postcards/550e8400-... \
  -H "Authorization: Bearer eyJhbGci..."
PUT /postcards/{postcard_id} JWT / API Key

Update a postcard. All fields are optional.

Request Body

FieldTypeRequired
titlestring|nullOptional
frontPostcardSide|nullOptional
backPostcardSide|nullOptional
cURL
curl -X PUT https://api.mailerbot.com/api/v1/postcards/550e8400-... \
  -H "Authorization: Bearer eyJhbGci..." \
  -H "Content-Type: application/json" \
  -d '{ "title": "Updated Summer Sale" }'
DELETE /postcards/{postcard_id} JWT / API Key

Delete a postcard.

Response 204

No content.

cURL
curl -X DELETE https://api.mailerbot.com/api/v1/postcards/550e8400-... \
  -H "Authorization: Bearer eyJhbGci..."

Mailings

GET /mailings JWT / API Key

List mailings with pagination.

Response 200

FieldType
idstring (UUID)
namestring
typestring ("letter"|"postcard")
productTypeIdstring|null
documentIdstring|null
postcardIdstring|null
contactListIdstring
couponListIdstring|null
statusstring
recipientCountinteger
createdAtdatetime
scheduledDatedatetime|null
printColorboolean
productCostPerPiecenumber
totalCostnumber
taxAmountnumber
taxRatenumber
paymentStatusstring
stripePaymentIntentIdstring|null
isTestboolean
postageMailingPostage[]

MailingPostage Object

FieldType
postageZoneIdstring
postageZoneNamestring
postageZoneSlugstring
postageRateIdstring
postageRateLabelstring
postageRateSlugstring
recipientCountinteger
costPerPiecenumber
subtotalnumber
cURL
curl https://api.mailerbot.com/api/v1/mailings?page=1&pageSize=20 \
  -H "Authorization: Bearer eyJhbGci..."
POST /mailings JWT / API Key

Create a new mailing.

Request Body

FieldTypeRequiredDescription
namestringRequiredMailing name
typestringRequired"letter" or "postcard"
documentIdstring|nullOptionalDocument ID (for letters)
postcardIdstring|nullOptionalPostcard ID (for postcards)
contactListIdstringRequiredTarget contact list
couponListIdstring|nullOptionalCoupon list to attach
scheduledDatedatetime|nullOptionalWhen to send
printColorbooleanOptionalColor printing for letters (default false)
postageSelectionsPostageSelection[]OptionalOne entry per postage zone. Zones left blank default to cheapest rate.

Postage selections: Each entry in postageSelections specifies a postageZoneSlug and postageRateSlug. If omitted or empty, the cheapest available rate is automatically selected for each zone that has recipients. You can provide entries for specific zones to override the default, and omit the rest. Extra zones (with no recipients) are silently ignored. Use List Postage Zones and Get Pricing Catalog to discover available zones and rates.

PostageSelection Object

FieldTypeDescription
postageZoneSlugstringZone slug (e.g. "domestic", "canada", "international")
postageRateSlugstringRate slug (e.g. "standard-postage")
cURL
curl -X POST https://api.mailerbot.com/api/v1/mailings \
  -H "Authorization: Bearer eyJhbGci..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Q1 Postcard Campaign",
    "type": "postcard",
    "postcardId": "pc_8f3a...",
    "contactListId": "cl_2b7c...",
    "scheduledDate": "2026-04-01T00:00:00Z",
    "postageSelections": [
      { "postageZoneSlug": "domestic", "postageRateSlug": "standard-postage" },
      { "postageZoneSlug": "canada", "postageRateSlug": "intl-standard" }
    ]
  }'
JavaScript
const res = await fetch("https://api.mailerbot.com/api/v1/mailings", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${accessToken}`,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    name: "Q1 Postcard Campaign",
    type: "postcard",
    postcardId: "pc_8f3a...",
    contactListId: "cl_2b7c...",
    scheduledDate: "2026-04-01T00:00:00Z",
    postageSelections: [
      { postageZoneSlug: "domestic", postageRateSlug: "standard-postage" },
      { postageZoneSlug: "canada", postageRateSlug: "intl-standard" }
    ]
  })
});
const mailing = await res.json();
Python
import requests

res = requests.post(
    "https://api.mailerbot.com/api/v1/mailings",
    headers={"Authorization": f"Bearer {access_token}"},
    json={
        "name": "Q1 Postcard Campaign",
        "type": "postcard",
        "postcardId": "pc_8f3a...",
        "contactListId": "cl_2b7c...",
        "scheduledDate": "2026-04-01T00:00:00Z",
        "postageSelections": [
            {"postageZoneSlug": "domestic", "postageRateSlug": "standard-postage"},
            {"postageZoneSlug": "canada", "postageRateSlug": "intl-standard"}
        ]
    }
)
mailing = res.json()
GET /mailings/{mailing_id} JWT / API Key

Get a single mailing by ID.

cURL
curl https://api.mailerbot.com/api/v1/mailings/550e8400-... \
  -H "Authorization: Bearer eyJhbGci..."
PUT /mailings/{mailing_id} JWT / API Key

Update a mailing.

Request Body

FieldTypeRequired
namestring|nullOptional
scheduledDatedatetime|nullOptional
cURL
curl -X PUT https://api.mailerbot.com/api/v1/mailings/550e8400-... \
  -H "Authorization: Bearer eyJhbGci..." \
  -H "Content-Type: application/json" \
  -d '{ "name": "Updated Campaign Name" }'
DELETE /mailings/{mailing_id} JWT / API Key

Delete a mailing. Only draft mailings can be deleted.

Response 204

No content.

cURL
curl -X DELETE https://api.mailerbot.com/api/v1/mailings/550e8400-... \
  -H "Authorization: Bearer eyJhbGci..."
POST /mailings/{mailing_id}/validate-addresses JWT / API Key

Validate all recipient addresses for a mailing.

Response 200

Array of AddressValidation objects.

cURL
curl -X POST https://api.mailerbot.com/api/v1/mailings/550e8400-.../validate-addresses \
  -H "Authorization: Bearer eyJhbGci..."
POST /mailings/estimate-cost JWT / API Key

Estimate cost before creating a mailing. Returns per-zone recipient counts so you know which postage zones to select. Use this to preview pricing and zone breakdown before committing to a mailing.

Request Body

FieldTypeRequiredDescription
typestringRequired"letter" or "postcard"
contactListIdstring|nullOptionalExisting contact list ID
contactIdsstring[]|nullOptionalSpecific contact IDs (alternative to contactListId)
documentIdstring|nullOptionalDocument ID (required for letters, used for page count)
printColorbooleanOptionalColor printing (default false)

Response 200

Same CostCalculation response as Calculate Cost. The totalCost will be null since no postage selections have been made yet — use zoneCounts to determine which zones need postage selections.

cURL
curl -X POST https://api.mailerbot.com/api/v1/mailings/estimate-cost \
  -H "Authorization: Bearer eyJhbGci..." \
  -H "Content-Type: application/json" \
  -d '{
    "type": "postcard",
    "contactListId": "cl_2b7c..."
  }'
POST /mailings/{mailing_id}/calculate-cost JWT / API Key

Calculate the cost of a mailing before sending. Returns per-zone recipient counts and pricing breakdown.

Response 200

FieldTypeDescription
typestring"letter" or "postcard"
productTypeCodestringProduct type code
recipientCountintegerTotal across all zones
productCostPerPiecenumberPer-piece product cost (not including postage)
totalProductCostnumberrecipientCount x productCostPerPiece
zoneCountsZoneRecipientCount[]Per-zone recipient breakdown
unavailableUnavailableRecipients[]Recipients in disabled countries/zones
totalCostnumber|nullTotal including postage (only when postage_selections provided on the mailing)
pageCountinteger|nullPage count (letters only)
printColorbooleanColor printing enabled
basePricenumber|nullBase price (letters only)
perPagePricenumber|nullPer-page price (letters only)
colorSurchargePerPagenumber|nullColor surcharge per page (letters only)

ZoneRecipientCount Object

FieldType
postageZoneIdstring
postageZoneNamestring
postageZoneSlugstring
recipientCountinteger
cURL
curl -X POST https://api.mailerbot.com/api/v1/mailings/550e8400-.../calculate-cost \
  -H "Authorization: Bearer eyJhbGci..."
POST /mailings/{mailing_id}/send JWT / API Key

Submit a mailing for processing and delivery. If the mailing is unpaid, charges the specified card (or the user's default card) off-session. If already paid, transitions directly to processing.

API & Integration usage: Pass paymentMethodId to charge a specific card, or omit it to use your default card. Cards that require 3D Secure authentication will be rejected — use a card that does not require 3DS for headless API calls. Add cards via the Settings page in the web app, then use List Cards to get IDs and Set Default Card to choose one for automatic billing.

Test mode: When authenticated with a mb_test_ API key, payment is skipped entirely and the mailing transitions straight to processing with paymentStatus: "test_skipped". No card is required. The order will not be printed or mailed — use this for integration testing.

Request Body (optional)

FieldTypeRequiredDescription
paymentMethodIdstring|nullOptionalStripe payment method ID to charge. Falls back to default card if omitted.

Response 200

MailingResponse with status: "processing".

Error Responses 400

ConditionError Message
No card provided and no default setNo payment method provided and no default card on file.
Card requires 3D SecureThis payment requires 3D Secure authentication and cannot be completed without a browser.
Charge failsPayment was not successful (status: ...).
cURL
# Charge default card
curl -X POST https://api.mailerbot.com/api/v1/mailings/550e8400-.../send \
  -H "Authorization: Bearer eyJhbGci..."

# Charge a specific card
curl -X POST https://api.mailerbot.com/api/v1/mailings/550e8400-.../send \
  -H "Authorization: Bearer eyJhbGci..." \
  -H "Content-Type: application/json" \
  -d '{ "paymentMethodId": "pm_1Abc..." }'

# Test mode — no payment required
curl -X POST https://api.mailerbot.com/api/v1/mailings/550e8400-.../send \
  -H "X-API-Key: mb_test_..."
JavaScript
// Charge default card
const res = await fetch(
  `https://api.mailerbot.com/api/v1/mailings/${mailingId}/send`,
  {
    method: "POST",
    headers: { "Authorization": `Bearer ${accessToken}` }
  }
);

// Charge a specific card
const res = await fetch(
  `https://api.mailerbot.com/api/v1/mailings/${mailingId}/send`,
  {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${accessToken}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({ paymentMethodId: "pm_1Abc..." })
  }
);
const result = await res.json();
// result.status === "processing"
Python
import requests

# Charge default card
res = requests.post(
    f"https://api.mailerbot.com/api/v1/mailings/{mailing_id}/send",
    headers={"Authorization": f"Bearer {access_token}"}
)

# Charge a specific card
res = requests.post(
    f"https://api.mailerbot.com/api/v1/mailings/{mailing_id}/send",
    headers={"Authorization": f"Bearer {access_token}"},
    json={"paymentMethodId": "pm_1Abc..."}
)
result = res.json()
# result["status"] == "processing"

Campaigns

GET /campaigns JWT / API Key

List campaigns with pagination.

Response 200

FieldType
idstring (UUID)
namestring
mailingIdsstring[]
statusstring
createdAtdatetime
updatedAtdatetime
cURL
curl https://api.mailerbot.com/api/v1/campaigns?page=1&pageSize=20 \
  -H "Authorization: Bearer eyJhbGci..."
POST /campaigns JWT / API Key

Create a new campaign.

Request Body

FieldTypeRequired
namestringRequired
mailingIdsstring[]Optional
cURL
curl -X POST https://api.mailerbot.com/api/v1/campaigns \
  -H "Authorization: Bearer eyJhbGci..." \
  -H "Content-Type: application/json" \
  -d '{ "name": "Q1 2026 Outreach" }'
GET /campaigns/{campaign_id} JWT / API Key

Get a single campaign by ID.

cURL
curl https://api.mailerbot.com/api/v1/campaigns/550e8400-... \
  -H "Authorization: Bearer eyJhbGci..."
PUT /campaigns/{campaign_id} JWT / API Key

Update a campaign.

Request Body

FieldTypeRequired
namestring|nullOptional
statusstring|nullOptional
cURL
curl -X PUT https://api.mailerbot.com/api/v1/campaigns/550e8400-... \
  -H "Authorization: Bearer eyJhbGci..." \
  -H "Content-Type: application/json" \
  -d '{ "name": "Updated Campaign", "status": "active" }'
DELETE /campaigns/{campaign_id} JWT / API Key

Delete a campaign.

Response 204

No content.

cURL
curl -X DELETE https://api.mailerbot.com/api/v1/campaigns/550e8400-... \
  -H "Authorization: Bearer eyJhbGci..."
POST /campaigns/{campaign_id}/mailings JWT / API Key

Add a mailing to a campaign.

Request Body

FieldTypeRequired
mailingIdstring (UUID)Required

Response 200

CampaignResponse.

cURL
curl -X POST https://api.mailerbot.com/api/v1/campaigns/550e8400-.../mailings \
  -H "Authorization: Bearer eyJhbGci..." \
  -H "Content-Type: application/json" \
  -d '{ "mailingId": "ml_9d4e..." }'
DELETE /campaigns/{campaign_id}/mailings/{mailing_id} JWT / API Key

Remove a mailing from a campaign.

Response 200

CampaignResponse.

cURL
curl -X DELETE https://api.mailerbot.com/api/v1/campaigns/550e8400-.../mailings/ml_9d4e... \
  -H "Authorization: Bearer eyJhbGci..."

Dashboard

GET /dashboard/stats JWT / API Key

Get aggregate dashboard statistics for the current user.

Response 200

FieldType
totalDocumentsinteger
totalMailingsinteger
lettersSentinteger
postcardsSentinteger
totalContactsinteger
totalSpentnumber
totalQrScansinteger
cURL
curl https://api.mailerbot.com/api/v1/dashboard/stats \
  -H "Authorization: Bearer eyJhbGci..."
GET /dashboard/reporting JWT / API Key

Get reporting data with optional date range filtering. Returns aggregate mailing stats and geographic distribution.

Query Parameters

ParameterTypeRequiredDescription
startDatestringOptionalStart date (YYYY-MM-DD)
endDatestringOptionalEnd date (YYYY-MM-DD)

Response 200

FieldTypeDescription
totalPiecesintegerTotal mail pieces sent
totalSpentnumberTotal amount spent
lettersCountintegerNumber of letter pieces
postcardsCountintegerNumber of postcard pieces
destinationsByStatearrayArray of { state, count } objects
cURL
curl https://api.mailerbot.com/api/v1/dashboard/reporting?startDate=2026-01-01&endDate=2026-03-31 \
  -H "Authorization: Bearer eyJhbGci..."

Merge Tags

GET /merge-tags Public

List all available merge tags for use in documents and postcards.

Response 200

FieldTypeDescription
keystringMerge tag key (e.g. "firstName")
labelstringDisplay label
categorystring"Contact", "Address", "Company", or "Custom"
sampleValuestringExample value for preview
cURL
curl https://api.mailerbot.com/api/v1/merge-tags

Multi-Factor Authentication

POST /auth/mfa/totp/setup JWT

Generate a TOTP secret and provisioning QR code URI. The user must confirm with a valid code via the enable endpoint to activate TOTP.

Response 200

FieldTypeDescription
secretstringBase32-encoded TOTP secret
otpauthUrlstringOTP auth URI for QR code generation
cURL
curl -X POST https://api.mailerbot.com/api/v1/auth/mfa/totp/setup \
  -H "Authorization: Bearer eyJhbGci..."
POST /auth/mfa/totp/enable JWT

Verify a TOTP code against the secret from setup and permanently enable TOTP for the account.

Request Body

FieldTypeRequiredDescription
secretstringRequiredThe TOTP secret from setup
codestringRequired6-digit TOTP code from authenticator app

Response 200

FieldType
enabledboolean (true)
DELETE /auth/mfa/totp JWT

Disable TOTP multi-factor authentication for the current user.

Response 200

FieldType
enabledboolean (false)
GET /auth/mfa/passkeys JWT

List all registered passkeys (WebAuthn credentials) for the current user.

Response 200 (array)

FieldTypeDescription
idstringPasskey identifier
namestringUser-assigned name for the passkey
createdAtdatetimeWhen the passkey was registered
DELETE /auth/mfa/passkeys/{passkey_id} JWT

Remove a registered passkey. Returns 204 No Content on success.

Path Parameters

ParameterTypeDescription
passkey_idstringThe passkey identifier to delete
POST /auth/mfa/passkeys/register/options JWT

Get WebAuthn credential creation options to begin registering a new passkey. Pass the returned options to the browser's WebAuthn API (navigator.credentials.create()).

Response 200

WebAuthn credential creation options object (challenge, rp, user, pubKeyCredParams, timeout, etc.).

cURL
curl -X POST https://api.mailerbot.com/api/v1/auth/mfa/passkeys/register/options \
  -H "Authorization: Bearer eyJhbGci..."
POST /auth/mfa/passkeys/register/verify JWT

Verify and store a new passkey after the user has completed the WebAuthn registration ceremony.

Request Body

FieldTypeRequiredDescription
credentialobjectRequiredWebAuthn credential from navigator.credentials.create()
namestringRequiredUser-assigned name for this passkey

Response 200

FieldType
idstring
namestring
created_atstring (datetime)
cURL
curl -X POST https://api.mailerbot.com/api/v1/auth/mfa/passkeys/register/verify \
  -H "Authorization: Bearer eyJhbGci..." \
  -H "Content-Type: application/json" \
  -d '{ "credential": { ... }, "name": "My MacBook" }'
POST /auth/mfa/passkeys/auth/options Public

Get WebAuthn authentication options during the MFA login flow. Called after receiving an MFA challenge from POST /auth/login.

Request Body

FieldTypeRequiredDescription
mfa_tokenstringRequiredMFA token from login response

Response 200

WebAuthn authentication options object (challenge, allowCredentials, timeout, etc.).

cURL
curl -X POST https://api.mailerbot.com/api/v1/auth/mfa/passkeys/auth/options \
  -H "Content-Type: application/json" \
  -d '{ "mfa_token": "mfa_abc123..." }'
POST /auth/mfa/verify Public

Complete MFA verification during login. Submit the MFA token (received from POST /auth/login when MFA is enabled) along with a TOTP code or passkey response.

Request Body

FieldTypeRequiredDescription
mfaTokenstringRequiredMFA token from login response
codestring|nullOptional6-digit TOTP code (provide this or passkeyResponse)
passkeyResponseobject|nullOptionalWebAuthn assertion response

Response 200

FieldType
accessTokenstring
refreshTokenstring
tokenTypestring ("bearer")
expiresIninteger (1800)
cURL
curl -X POST https://api.mailerbot.com/api/v1/auth/mfa/verify \
  -H "Content-Type: application/json" \
  -d '{
    "mfaToken": "mfa_abc123...",
    "code": "123456"
  }'

OAuth 2.1

GET /.well-known/oauth-authorization-server Public

OAuth 2.0 Authorization Server Metadata (RFC 8414). Root-level endpoint — not under /api/v1. Returns standard OAuth server discovery metadata including supported grant types, scopes, and endpoint URLs.

Response 200

OAuth authorization server metadata JSON object.

cURL
curl https://api.mailerbot.com/.well-known/oauth-authorization-server
GET /.well-known/oauth-protected-resource Public

Protected Resource Metadata (RFC 9728). Root-level endpoint — not under /api/v1. Returns metadata about the protected resource including supported token types and authorization server location.

Response 200

Protected resource metadata JSON object.

cURL
curl https://api.mailerbot.com/.well-known/oauth-protected-resource
GET /oauth/authorize Public

OAuth 2.1 Authorization Endpoint. Root-level endpoint — not under /api/v1. Redirects the user to the consent page.

Query Parameters

ParameterTypeRequiredDescription
client_idstringRequiredOAuth client ID
redirect_uristringRequiredCallback URL
response_typestringRequired"code"
scopestringOptionalSpace-separated scopes
statestringOptionalCSRF state parameter
code_challengestringRequiredPKCE code challenge
code_challenge_methodstringRequired"S256"

Response

Redirect to consent page.

GET /oauth/authorize-info Public

Get information about an OAuth client for display on the consent page.

Query Parameters

ParameterTypeRequired
client_idstringRequired
scopestringOptional

Response 200

FieldType
client_namestring
scopesstring[]
POST /oauth/authorize JWT

Approve an OAuth authorization request from the consent page. Called by the frontend after the user approves the consent.

Request Body

FieldTypeRequired
client_idstringRequired
redirect_uristringRequired
scopestringOptional
response_typestringRequired
code_challengestringRequired
code_challenge_methodstringRequired
statestringOptional

Response 200

FieldType
redirect_urlstring
cURL
curl -X POST https://api.mailerbot.com/api/v1/oauth/authorize \
  -H "Authorization: Bearer eyJhbGci..." \
  -H "Content-Type: application/json" \
  -d '{
    "client_id": "CLIENT_ID",
    "redirect_uri": "https://myapp.com/callback",
    "response_type": "code",
    "scope": "contacts:read mailings:read",
    "code_challenge": "CHALLENGE",
    "code_challenge_method": "S256",
    "state": "random_state"
  }'
POST /oauth/clients JWT / API Key

Register a new OAuth 2.1 client application. The clientSecret is returned only once in the creation response.

Request Body

FieldTypeRequiredDescription
clientNamestringRequiredDisplay name for the application
redirectUrisstring[]RequiredAllowed redirect URIs
allowedScopesstring[]OptionalRequested scopes (e.g. "contacts:read", "mailings:write")
isConfidentialbooleanOptionalConfidential client (default: true)

Available Scopes

ScopeDescription
contacts:readView contacts
contacts:writeCreate and update contacts
documents:readView documents
documents:writeCreate and update documents
postcards:readView postcards
postcards:writeCreate and update postcards
mailings:readView mailings
mailings:writeCreate and send mailings
campaigns:readView campaigns
campaigns:writeCreate and update campaigns
account:readView account information

Response 200

FieldType
idstring
clientNamestring
clientIdstring
clientSecretstring (shown once)
redirectUrisstring[]
allowedScopesstring[]
grantTypesstring[]
isConfidentialboolean
isActiveboolean
createdAtdatetime
cURL
curl -X POST https://api.mailerbot.com/api/v1/oauth/clients \
  -H "Authorization: Bearer eyJhbGci..." \
  -H "Content-Type: application/json" \
  -d '{
    "clientName": "My Integration",
    "redirectUris": ["https://myapp.com/callback"],
    "allowedScopes": ["contacts:read", "mailings:write"]
  }'
GET /oauth/clients JWT / API Key

List all OAuth clients created by the current user.

Response 200 (array)

FieldType
idstring
clientNamestring
clientIdstring
clientSecretLastFourstring|null
redirectUrisstring[]
allowedScopesstring[]
grantTypesstring[]
isActiveboolean
createdAtdatetime
DELETE /oauth/clients/{client_id} JWT / API Key

Deactivate an OAuth client application. Returns 204 No Content on success.

Path Parameters

ParameterTypeDescription
client_idstringThe client database ID
POST /oauth/token Public

OAuth 2.1 Token Endpoint. Exchange an authorization code for tokens or refresh an existing token. Uses application/x-www-form-urlencoded content type. Note: This endpoint is at /oauth/token (not under /api/v1).

Request Body (form-encoded)

FieldTypeRequiredDescription
grant_typestringRequired"authorization_code" or "refresh_token"
codestringConditionalAuthorization code (required for authorization_code grant)
redirect_uristringConditionalMust match the redirect_uri used during authorization
code_verifierstringConditionalPKCE code verifier (required for authorization_code grant)
client_idstringConditionalOAuth client ID
client_secretstringOptionalRequired for confidential clients
refresh_tokenstringConditionalRequired for refresh_token grant

Response 200

FieldType
access_tokenstring
token_typestring ("Bearer")
expires_ininteger
refresh_tokenstring|null
scopestring
cURL
curl -X POST https://api.mailerbot.com/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code&code=AUTH_CODE&redirect_uri=https://myapp.com/callback&code_verifier=VERIFIER&client_id=CLIENT_ID"
POST /oauth/revoke Public

Revoke an OAuth token (RFC 7009). Always returns 200 per the spec. Note: This endpoint is at /oauth/revoke (not under /api/v1).

Request Body (form-encoded)

FieldTypeRequiredDescription
tokenstringRequiredThe token to revoke
token_type_hintstringOptional"access_token" or "refresh_token"

Payments

POST /payments/create-payment-intent JWT / API Key

Create a Stripe PaymentIntent for a mailing. Optionally pass a saved payment method ID to charge immediately.

Request Body

FieldTypeRequiredDescription
mailingIdstringRequiredThe mailing to pay for
paymentMethodIdstring|nullOptionalSaved card payment method ID for immediate charge

Response 200

FieldTypeDescription
clientSecretstringStripe client secret for frontend confirmation
paymentIntentIdstringStripe PaymentIntent ID
amountintegerTotal amount in cents (subtotal + tax)
subtotalintegerSubtotal in cents
taxAmountintegerTax amount in cents
taxRatenumberTax rate percentage (e.g. 8.25)
currencystringCurrency code (e.g. "usd")
cURL
curl -X POST https://api.mailerbot.com/api/v1/payments/create-payment-intent \
  -H "Authorization: Bearer eyJhbGci..." \
  -H "Content-Type: application/json" \
  -d '{ "mailingId": "ml_9d4e..." }'
GET /payments/config JWT / API Key

Get the Stripe publishable key for frontend payment initialization.

Response 200

FieldType
publishableKeystring
GET /payments/cards JWT / API Key

List saved payment methods (cards) for the current user. Each card includes an isDefault flag indicating the customer's default payment method.

Response 200 (array)

FieldTypeDescription
idstringStripe payment method ID
brandstringCard brand (e.g. "visa", "mastercard")
last4stringLast 4 digits of card number
expMonthintegerExpiration month
expYearintegerExpiration year
isDefaultbooleanWhether this is the default payment method
POST /payments/cards/setup JWT / API Key

Create a Stripe SetupIntent for saving a new payment card. Returns a client secret for use with Stripe Elements on the frontend.

Note: This endpoint is designed for use with the MailerBot web app's Stripe Elements integration. The returned client secret requires our Stripe publishable key and properly configured CORS origins to complete the card setup flow.

Response 200

FieldTypeDescription
clientSecretstringStripe SetupIntent client secret
cURL
curl -X POST https://api.mailerbot.com/api/v1/payments/cards/setup \
  -H "Authorization: Bearer eyJhbGci..."
POST /payments/cards/default JWT / API Key

Set a saved card as the default payment method. The default card is used automatically when sending mailings via API without specifying a payment method.

Request Body

FieldTypeRequiredDescription
paymentMethodIdstringRequiredStripe payment method ID to set as default

Response 204

No content on success.

cURL
curl -X POST https://api.mailerbot.com/api/v1/payments/cards/default \
  -H "Authorization: Bearer eyJhbGci..." \
  -H "Content-Type: application/json" \
  -d '{ "paymentMethodId": "pm_1Abc..." }'
DELETE /payments/cards/{payment_method_id} JWT / API Key

Remove a saved payment card. If the deleted card was the default, the default is cleared. Returns 204 No Content on success.

Path Parameters

ParameterTypeDescription
payment_method_idstringThe Stripe payment method ID

QR Tracking

GET /r/{slug} Public

Redirect a QR short URL to its destination. This endpoint is at /r/{slug} (not under /api/v1). Records a scan event before redirecting.

Path Parameters

ParameterTypeDescription
slugstringThe QR link slug

Response 302

HTTP 302 redirect to the destination URL.

cURL
curl -L https://api.mailerbot.com/r/abc123
POST /qr/links JWT / API Key

Create a trackable QR link. The generated short URL can be embedded as a QR code on postcards and letters to measure engagement.

Request Body

FieldTypeRequiredDescription
destinationUrlstringRequiredURL to redirect to when scanned
postcardIdstring|nullOptionalAssociated postcard ID
mailingIdstring|nullOptionalAssociated mailing ID
recipientIdstring|nullOptionalAssociated recipient/contact ID
qrElementIdstring|nullOptionalCanvas element ID for the QR code

Response 201

FieldTypeDescription
idstringLink ID
slugstringShort URL slug
shortUrlstringFull short URL for QR code
destinationUrlstringDestination URL
scanCountintegerNumber of times scanned
createdAtdatetimeCreation timestamp
cURL
curl -X POST https://api.mailerbot.com/api/v1/qr/links \
  -H "Authorization: Bearer eyJhbGci..." \
  -H "Content-Type: application/json" \
  -d '{
    "destinationUrl": "https://mysite.com/promo",
    "mailingId": "ml_9d4e..."
  }'
GET /qr/analytics JWT / API Key

Get QR scan analytics including daily scan counts, top-performing links, and recent scans.

Query Parameters

ParameterTypeDefaultDescription
daysinteger30Number of days to look back (1-365)

Response 200

FieldTypeDescription
totalScansintegerTotal scans in the period
uniqueLinksintegerNumber of unique links scanned
scansByDayarrayArray of { date, count } objects
topLinksarrayTop links by scan count with { slug, destinationUrl, scanCount, shortUrl }
recentScansarrayRecent scan events with geo/device info
cURL
curl https://api.mailerbot.com/api/v1/qr/analytics?days=30 \
  -H "Authorization: Bearer eyJhbGci..."

Pricing

GET /pricing Public

Get the active pricing catalog. Returns all active pricing items including per-piece costs for letters and postcards.

Response 200

Array of pricing item objects.

cURL
curl https://api.mailerbot.com/api/v1/pricing
GET /pricing/countries Public

List all supported countries. Use the code field as the countryCode when creating contacts.

Response 200

FieldTypeDescription
codestringISO 3166-1 alpha-2 code (e.g. "US", "CA")
namestringCountry name
isActivebooleanWhether mailing to this country is enabled
cURL
curl https://api.mailerbot.com/api/v1/pricing/countries
GET /pricing/postage-zones Public

List active postage zones. Each zone groups countries by shipping rate. Use the slug value as postageZoneSlug in postage selections when creating mailings.

Response 200

FieldTypeDescription
idstring (UUID)Zone ID
namestringZone display name
slugstringZone slug for API use
descriptionstring|nullZone description
sortOrderintegerDisplay order
isActivebooleanWhether zone is active
cURL
curl https://api.mailerbot.com/api/v1/pricing/postage-zones

Coupons

GET /coupons JWT / API Key

List all coupon lists for the current user.

Response 200

Array of CouponList objects.

cURL
curl https://api.mailerbot.com/api/v1/coupons \
  -H "Authorization: Bearer eyJhbGci..."
POST /coupons JWT / API Key

Create a new coupon list.

Request Body

FieldTypeRequired
namestringRequired
descriptionstringOptional

Response 201

CouponList object.

cURL
curl -X POST https://api.mailerbot.com/api/v1/coupons \
  -H "Authorization: Bearer eyJhbGci..." \
  -H "Content-Type: application/json" \
  -d '{ "name": "Summer Promo 2026", "description": "Summer discount codes" }'
GET /coupons/{list_id} JWT / API Key

Get a coupon list by ID.

cURL
curl https://api.mailerbot.com/api/v1/coupons/550e8400-... \
  -H "Authorization: Bearer eyJhbGci..."
PUT /coupons/{list_id} JWT / API Key

Update a coupon list.

Request Body

FieldTypeRequired
namestringOptional
descriptionstringOptional
cURL
curl -X PUT https://api.mailerbot.com/api/v1/coupons/550e8400-... \
  -H "Authorization: Bearer eyJhbGci..." \
  -H "Content-Type: application/json" \
  -d '{ "name": "Updated Promo Name" }'
DELETE /coupons/{list_id} JWT / API Key

Delete a coupon list and all its codes.

Response 204

No content.

cURL
curl -X DELETE https://api.mailerbot.com/api/v1/coupons/550e8400-... \
  -H "Authorization: Bearer eyJhbGci..."
GET /coupons/{list_id}/codes JWT / API Key

List coupon codes in a list with optional filtering.

Query Parameters

ParameterTypeDefaultDescription
pageinteger1Page number
page_sizeinteger20Items per page
usedboolean--Filter by used/unused status
cURL
curl https://api.mailerbot.com/api/v1/coupons/550e8400-.../codes?used=false \
  -H "Authorization: Bearer eyJhbGci..."
POST /coupons/{list_id}/codes/import JWT / API Key

Import an array of coupon codes into a list. Duplicate codes within the list are skipped.

Request Body

FieldTypeRequired
codesstring[]Required

Response 200

FieldType
importednumber
duplicatesnumber
cURL
curl -X POST https://api.mailerbot.com/api/v1/coupons/550e8400-.../codes/import \
  -H "Authorization: Bearer eyJhbGci..." \
  -H "Content-Type: application/json" \
  -d '{ "codes": ["SAVE10", "SAVE20", "PROMO25"] }'
DELETE /coupons/{list_id}/codes/{code_id} JWT / API Key

Delete a specific coupon code from a list.

Response 204

No content.

cURL
curl -X DELETE https://api.mailerbot.com/api/v1/coupons/550e8400-.../codes/code-uuid \
  -H "Authorization: Bearer eyJhbGci..."
GET /coupons/{list_id}/availability JWT / API Key

Check whether a coupon list has enough unused codes for a mailing.

Query Parameters

ParameterTypeRequiredDescription
countintegerRequiredNumber of codes needed

Response 200

FieldType
availablenumber
sufficientboolean
cURL
curl https://api.mailerbot.com/api/v1/coupons/550e8400-.../availability?count=500 \
  -H "Authorization: Bearer eyJhbGci..."

Assets

GET /assets JWT / API Key

List uploaded image assets for the current user.

Query Parameters

ParameterTypeDefault
pageinteger1
page_sizeinteger20

Response 200

PaginatedResponse of asset objects.

cURL
curl https://api.mailerbot.com/api/v1/assets?page=1&page_size=20 \
  -H "Authorization: Bearer eyJhbGci..."
POST /assets JWT / API Key

Upload an image asset. Accepted formats: JPEG, PNG, GIF, WebP, SVG. Maximum file size: 10 MB. Content type must be multipart/form-data.

Request Body (multipart/form-data)

FieldTypeRequiredDescription
filefile (image)RequiredJPEG, PNG, GIF, WebP, or SVG — max 10 MB

Response 201

FieldType
idstring (UUID)
urlstring
filenamestring
sizenumber (bytes)
created_atstring (datetime)
cURL
curl -X POST https://api.mailerbot.com/api/v1/assets \
  -H "Authorization: Bearer eyJhbGci..." \
  -F "file=@/path/to/image.png"
DELETE /assets/{asset_id} JWT / API Key

Delete an uploaded asset.

Response 204

No content.

cURL
curl -X DELETE https://api.mailerbot.com/api/v1/assets/550e8400-... \
  -H "Authorization: Bearer eyJhbGci..."