Skip to content

REST API

Use the MailJawn REST API to manage subscribers, track events, and work with assets programmatically. Every feature available in the dashboard is accessible through the API.

Base URL

All API endpoints are relative to:

https://api.mailjawn.com/api/v1/projects/{project_id}/

Replace {project_id} with your app's UUID from the MailJawn dashboard.

Authentication

Authenticate every request with a Bearer token in the Authorization header:

Authorization: Bearer mj_your_api_key_here

API keys start with mj_ and are created in Dashboard > API Keys. See Account > API Keys for details.

Warning

API keys are shown only once at creation. Store them securely — MailJawn cannot recover a lost key.

Scopes

Each API key has a set of scopes that control what it can access. Choose the narrowest scope bundle that fits your use case.

Scope Permits
subscribers:read List and view subscribers
subscribers:write Create, update, delete, identify, and bulk import subscribers
events:write Track subscriber events
assets:read List and view assets
assets:write Upload, update, and delete assets
campaigns:read Read campaign data
campaigns:write Create and manage campaigns
templates:read Read email templates
templates:write Create and manage templates
sequences:read Read automation sequences
sequences:write Manage automation sequences
stats:read Read analytics and statistics
email:send Send emails

Scope Bundles

Bundle Includes Best For
SDK subscribers:write, events:write Mobile app integration
MCP All read scopes + subscribers:write, events:write, campaigns:write, assets:write, email:send AI agent integration
Full Access All scopes Admin scripts

Rate Limits

Requests are rate-limited per API key:

Operation Limit Window
Read (GET, HEAD, OPTIONS) 100 requests 60 seconds
Write (POST, PATCH, DELETE) 30 requests 60 seconds
MCP 60 requests 60 seconds
Email send 10 requests 60 seconds

Rate Limit Headers

Every response includes rate limit information:

Header Description
RateLimit-Limit Maximum requests allowed in the current window
RateLimit-Remaining Requests remaining in the current window
RateLimit-Reset Unix timestamp when the window resets
Retry-After Seconds to wait (only on 429 responses)

Rate Limit Exceeded

When you hit the limit, you'll receive:

HTTP/1.1 429 Too Many Requests
Retry-After: 45
RateLimit-Limit: 30
RateLimit-Remaining: 0
RateLimit-Reset: 1740200000
{
  "detail": "Request was throttled. Expected available in 45 seconds."
}

Error Responses

All errors return JSON with a consistent format:

{
  "error": "Description of what went wrong"
}
Status Code Meaning
400 Bad request — invalid parameters or validation error
401 Unauthorized — missing or invalid API key
403 Forbidden — valid key, but missing required scope
404 Not found — project, subscriber, or resource doesn't exist
429 Too many requests — rate limit exceeded
500 Server error — something went wrong on our end

Endpoints

Identify Subscriber

Upsert a subscriber by email. Creates the subscriber if they don't exist, updates them if they do. This is the primary endpoint used by the Swift SDK.

POST /projects/{project_id}/subscribers/identify/

Scope: subscribers:write

Request:

curl -X POST "https://api.mailjawn.com/api/v1/projects/{project_id}/subscribers/identify/" \
  -H "Authorization: Bearer mj_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "user@example.com",
    "name": "Jane Doe",
    "timezone": "America/New_York",
    "custom_fields": {
      "plan": "pro",
      "signup_date": "2026-02-21"
    },
    "tags": ["vip", "early-adopter"],
    "source": "api",
    "trigger_automations": true
  }'
Field Type Required Description
email string Yes Subscriber's email address
name string No Display name
timezone string No IANA timezone (e.g., "America/New_York")
custom_fields object No Arbitrary key-value pairs (merged with existing)
tags array No Tag slugs to apply
source string No How the subscriber was added (e.g., "api", "sdk")
trigger_automations boolean No Whether to trigger welcome automations (default: true)
device_type string No Device type (auto-captured by SDK)
os_version string No OS version (auto-captured by SDK)
app_version string No App version (auto-captured by SDK)
locale string No Locale code (auto-captured by SDK)
sdk_version string No SDK version (auto-captured by SDK)

Response (200):

{
  "subscriber_id": "550e8400-e29b-41d4-a716-446655440000",
  "created": true
}
Field Type Description
subscriber_id UUID The subscriber's unique identifier
created boolean true if new subscriber, false if updated

List Subscribers

GET /projects/{project_id}/subscribers/

Scope: subscribers:read

Query Parameters:

Parameter Type Default Description
status string Filter by status: active, unsubscribed, bounced, complained
tag string Filter by tag slug
search string Search email and name
limit integer 50 Results per page (1–100)
offset integer 0 Pagination offset

Request:

curl "https://api.mailjawn.com/api/v1/projects/{project_id}/subscribers/?status=active&limit=20" \
  -H "Authorization: Bearer mj_your_key"

Response (200):

{
  "subscribers": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "email": "user@example.com",
      "name": "Jane Doe",
      "status": "active",
      "timezone": "America/New_York",
      "effective_timezone": "America/New_York",
      "first_seen": "2026-02-01T10:00:00Z",
      "last_seen": "2026-02-21T14:30:00Z",
      "subscribed_at": "2026-02-01T10:00:00Z",
      "device_type": "iPhone",
      "os_version": "iOS 17.2",
      "app_version": "2.3.1",
      "locale": "en_US",
      "sdk_version": "1.0.0",
      "custom_fields": {"plan": "pro"},
      "source": "api",
      "tags": [
        {"id": "uuid", "name": "VIP", "slug": "vip"}
      ],
      "created": "2026-02-01T10:00:00Z",
      "modified": "2026-02-21T14:30:00Z"
    }
  ],
  "total": 150,
  "limit": 20,
  "offset": 0
}

Bulk Import Subscribers

Import up to 1,000 subscribers in a single request.

POST /projects/{project_id}/subscribers/

Scope: subscribers:write

Request:

curl -X POST "https://api.mailjawn.com/api/v1/projects/{project_id}/subscribers/" \
  -H "Authorization: Bearer mj_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "subscribers": [
      {
        "email": "user1@example.com",
        "name": "User One",
        "tags": ["imported"],
        "trigger_automations": false
      },
      {
        "email": "user2@example.com",
        "name": "User Two"
      }
    ]
  }'
Field Type Required Description
subscribers array Yes Array of subscriber objects (1–1,000)

Each subscriber object supports the same fields as the identify endpoint.

Response (200):

{
  "imported": 1000,
  "skipped": 0,
  "failed": 0,
  "errors": []
}

Get Subscriber

GET /projects/{project_id}/subscribers/{subscriber_id}/

Scope: subscribers:read

Request:

curl "https://api.mailjawn.com/api/v1/projects/{project_id}/subscribers/{subscriber_id}/" \
  -H "Authorization: Bearer mj_your_key"

Response (200): Same subscriber object as the list endpoint.


Update Subscriber

PATCH /projects/{project_id}/subscribers/{subscriber_id}/

Scope: subscribers:write

Request:

curl -X PATCH "https://api.mailjawn.com/api/v1/projects/{project_id}/subscribers/{subscriber_id}/" \
  -H "Authorization: Bearer mj_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Updated Name",
    "timezone": "Europe/London",
    "custom_fields": {"plan": "enterprise"},
    "status": "unsubscribed"
  }'

All fields are optional. Only included fields are updated.

Field Type Description
name string Display name
timezone string IANA timezone
custom_fields object Merged with existing custom fields
status string active, unsubscribed, bounced, complained

Response (200): Updated subscriber object.


Delete Subscriber

DELETE /projects/{project_id}/subscribers/{subscriber_id}/

Scope: subscribers:write

Request:

curl -X DELETE "https://api.mailjawn.com/api/v1/projects/{project_id}/subscribers/{subscriber_id}/" \
  -H "Authorization: Bearer mj_your_key"

Response: 204 No Content


Subscribe (Public)

Public endpoint for signup forms. No API key required.

POST /projects/{project_id}/subscribe/

Scope: None

Rate limit: 10 requests per minute per IP address

See Subscribers > Subscribe Forms for full details, HTML form examples, and bot protection.

Request:

curl -X POST "https://api.mailjawn.com/api/v1/projects/{project_id}/subscribe/" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "visitor@example.com",
    "name": "Site Visitor",
    "tag": "newsletter"
  }'

Response (200):

{
  "status": "subscribed",
  "email": "visitor@example.com"
}

Track Event

Record a subscriber event for automation triggers and analytics.

POST /projects/{project_id}/events/track/

Scope: events:write

Request:

curl -X POST "https://api.mailjawn.com/api/v1/projects/{project_id}/events/track/" \
  -H "Authorization: Bearer mj_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "subscriber_id": "550e8400-e29b-41d4-a716-446655440000",
    "event_name": "app_opened",
    "properties": {
      "screen": "home",
      "duration_seconds": 120
    },
    "occurred_at": "2026-02-21T14:30:00Z",
    "client_event_id": "550e8400-e29b-41d4-a716-446655440001"
  }'
Field Type Required Description
subscriber_id UUID Yes The subscriber who triggered the event
event_name string Yes Event name (max 100 characters)
properties object No Custom event data
occurred_at ISO 8601 No When the event happened (defaults to server time)
client_event_id UUID No Client-generated ID for idempotency/deduplication

Response (201 Created — new event):

{
  "event_id": "550e8400-e29b-41d4-a716-446655440002",
  "subscriber_id": "550e8400-e29b-41d4-a716-446655440000",
  "event_name": "app_opened",
  "created": true,
  "properties": {"screen": "home", "duration_seconds": 120},
  "occurred_at": "2026-02-21T14:30:00Z"
}

Response (200 OK — duplicate): Same body with "created": false.

Tip

The app_opened event automatically updates the subscriber's last_seen timestamp. Other custom events do not.


List Assets

GET /projects/{project_id}/assets/

Scope: assets:read

Query Parameters:

Parameter Type Default Description
search string Search by filename
limit integer 50 Results per page (1–100)
offset integer 0 Pagination offset

Request:

curl "https://api.mailjawn.com/api/v1/projects/{project_id}/assets/?limit=10" \
  -H "Authorization: Bearer mj_your_key"

Response (200):

{
  "assets": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440003",
      "filename": "logo.png",
      "description": "Company logo",
      "url": "https://cdn.example.com/assets/logo.png",
      "width": 200,
      "height": 100,
      "size_kb": 45,
      "format": "PNG",
      "created": "2026-02-01T10:00:00Z"
    }
  ],
  "total": 25,
  "limit": 10,
  "offset": 0
}

Upload Asset

Upload an image file. Accepted formats: JPEG, PNG, GIF, WebP. Maximum file size: 10 MB.

POST /projects/{project_id}/assets/

Scope: assets:write

Content-Type: multipart/form-data

Request:

curl -X POST "https://api.mailjawn.com/api/v1/projects/{project_id}/assets/" \
  -H "Authorization: Bearer mj_your_key" \
  -F "file=@hero.jpg" \
  -F "description=Hero image for welcome email"
Field Type Required Description
file file Yes Image file (max 10 MB, JPEG/PNG/GIF/WebP)
description string No Asset description (max 1,000 characters)

Response (201 Created):

{
  "id": "550e8400-e29b-41d4-a716-446655440004",
  "filename": "hero.jpg",
  "description": "Hero image for welcome email",
  "url": "https://cdn.example.com/assets/hero.jpg",
  "width": 1920,
  "height": 1080,
  "size_kb": 350,
  "format": "JPEG",
  "created": "2026-02-21T14:30:00Z"
}

Get Asset

GET /projects/{project_id}/assets/{asset_id}/

Scope: assets:read

Request:

curl "https://api.mailjawn.com/api/v1/projects/{project_id}/assets/{asset_id}/" \
  -H "Authorization: Bearer mj_your_key"

Response (200): Same asset object as the upload response.


Update Asset

Update asset metadata.

PATCH /projects/{project_id}/assets/{asset_id}/

Scope: assets:write

Request:

curl -X PATCH "https://api.mailjawn.com/api/v1/projects/{project_id}/assets/{asset_id}/" \
  -H "Authorization: Bearer mj_your_key" \
  -H "Content-Type: application/json" \
  -d '{"description": "Updated description"}'

Response (200): Updated asset object.


Delete Asset

DELETE /projects/{project_id}/assets/{asset_id}/

Scope: assets:write

Request:

curl -X DELETE "https://api.mailjawn.com/api/v1/projects/{project_id}/assets/{asset_id}/" \
  -H "Authorization: Bearer mj_your_key"

Response: 204 No Content


Endpoint Summary

Method Endpoint Scope Description
POST /subscribers/identify/ subscribers:write Upsert subscriber
GET /subscribers/ subscribers:read List subscribers
POST /subscribers/ subscribers:write Bulk import (up to 1,000)
GET /subscribers/{id}/ subscribers:read Get subscriber
PATCH /subscribers/{id}/ subscribers:write Update subscriber
DELETE /subscribers/{id}/ subscribers:write Delete subscriber
POST /subscribe/ None Public form subscribe
POST /events/track/ events:write Track event
GET /assets/ assets:read List assets
POST /assets/ assets:write Upload asset
GET /assets/{id}/ assets:read Get asset
PATCH /assets/{id}/ assets:write Update asset
DELETE /assets/{id}/ assets:write Delete asset

All endpoints are relative to /api/v1/projects/{project_id}/.


See also: Account > API Keys | Swift SDK Reference | MCP Integration