NetSendo

NetSendo REST API reference

Every endpoint of the public API, with authentication, scopes, rate limits, errors and examples.

Written for NetSendo 2.7.0 and checked against its source code (routes, controllers and the API key model).

Overview

The API is part of every NetSendo installation, SILVER and GOLD alike. It runs on your own server under https://your-domain.com/api/v1, speaks JSON, and acts on the account that owns the API key. The MCP server and the n8n node use the same endpoints. The few calls that drive GOLD features, such as A/B tests, answer 402 without an active GOLD licence.

Send Authorization: Bearer with your key on every request, Content-Type: application/json with a body, and Accept: application/json so that validation errors come back as JSON.

Check your key

GET /api/v1/account returns the account behind the key and the NetSendo version. Integrations use it to test a connection.

curl https://your-domain.com/api/v1/account \
  -H "Authorization: Bearer ns_live_YOUR_KEY" \
  -H "Accept: application/json"

# 200 OK
{
  "data": {
    "id": 1,
    "name": "Anna Admin",
    "email": "admin@example.com",
    "version": "2.7.0"
  }
}

Authentication and API keys

Create keys in the NetSendo panel under Settings → API Keys. Give each key a name, pick its scopes and, if you want, an expiry date. The key is shown once, when you create it; NetSendo stores only its SHA-256 hash and records when it was last used.

Keys look like ns_live_ followed by 32 random characters. Pass the key in the Authorization header as a Bearer token. Query-string keys are not accepted.

Authorization: Bearer ns_live_YOUR_KEY

Mark a key as MCP Key to use it with Claude, Cursor or VS Code; the panel's MCP connection test uses that key. See the MCP server documentation for connecting AI assistants.

When authentication fails

The API answers 401 with one of these messages:

  • Missing or invalid Authorization header. Use: Bearer <api_key>
  • Invalid API key format. Key should start with ns_live_
  • Invalid API key
  • API key has expired

Scopes

Each key carries a set of scopes. An endpoint that needs a scope the key lacks answers 403 Forbidden.

ScopeWhat it allows
subscribers:readRead the suppression list, export list members inline, read a subscriber's activity history
subscribers:writeCreate, update and delete subscribers, batch create, replace a subscriber's tags, add and remove suppressed addresses
lists:readList health reports and address verification, import preview, list activity and engagement, sending schedule, list defaults, forms, system emails and system pages
lists:writeCreate, change and delete lists; import; clean and deduplicate; bulk membership changes; tags and custom fields; sending schedule and list defaults; forms; system emails and pages
tags:readCan be selected; in 2.7.0 no endpoint requires it (reading tags needs only a valid key)
notifications:writeCreate notifications in the account owner's notification centre
webhooks:readCan be selected; in 2.7.0 no endpoint requires it (reading webhooks needs only a valid key)
webhooks:writeCreate, change, delete and test webhooks and regenerate their signing secret
email:readStatus of emails sent through the API, list of sending mailboxes
email:writeSend single and batch emails
sms:readStatus of SMS sent through the API, list of SMS providers
sms:writeSend single and batch SMS
messages:readCampaign preview and recipient count, email templates, template categories and saved blocks
messages:writeCreate, change, delete, schedule, send, test and duplicate campaigns; A/B tests; templates and saved blocks
funnels:readAutomation rules, their options and logs
funnels:writeCreate, change, delete, activate and pause funnels and their steps, enrol subscribers; create and change automation rules
  • A write scope includes the read scope of the same resource: a key with lists:write can also call endpoints that need lists:read.
  • A key created without any scope selected receives all 16 scopes.
  • Endpoints marked any valid key in the tables below check only that the key is valid and not expired. They include listing and reading subscribers, lists, tags, custom fields, campaigns, funnels, A/B tests and webhooks. Every endpoint returns only the data of the key's own account.

Rate limits

The API allows 3,000 requests per minute per API key. Routes without a key (the list subscription endpoints and the signed export download) are counted per IP address with the same limit.

Responses carry X-RateLimit-Limit and X-RateLimit-Remaining. Over the limit the API answers 429 Too Many Requests with a Retry-After header in seconds.

For large jobs use the batch endpoints instead of many single calls: up to 1,000 subscribers per POST /subscribers/batch, 5,000 rows per import and 5,000 contacts per membership operation.

Errors

Errors are JSON objects with a short error label and a readable message:

{
  "error": "Forbidden",
  "message": "API key does not have subscribers:write permission"
}

Validation errors use the standard Laravel format, a message plus an errors object with one array of messages per field. Send Accept: application/json; without it a failed validation may answer with a redirect instead of JSON.

{
  "message": "The email field must be a valid email address.",
  "errors": {
    "email": ["The email field must be a valid email address."]
  }
}

Since NetSendo 2.4.0 the calls that drive GOLD features answer 402 Payment Required without an active GOLD licence (no key, SILVER, or GOLD past its expiry). The body says which feature was refused; message is in the default language of the instance (APP_LOCALE):

{
  "error": "Payment Required",
  "code": "gold_required",
  "message": "“A/B tests” requires the GOLD licence. Upgrade on the Licence page to use it.",
  "feature": "ab_tests",
  "required_plan": "GOLD",
  "upgrade_url": "https://your-domain.com/license"
}
StatusMeaning
200Success
201Created
202Accepted and queued (emails, SMS, CSV export)
401Missing, malformed, unknown or expired API key
402The call uses a GOLD feature and the installation has no active GOLD licence
403The key lacks the scope the endpoint needs
404The resource does not exist in this account
409Conflict, for example deleting a list that still has members without confirm=1, or starting a funnel or A/B test in the wrong state
422Validation failed, or the resource is not ready (a campaign without lists, mailbox or content)
429Rate limit exceeded
500Server error; the instance log has the details

Pagination

List endpoints return pages of 25 items by default (50 for tags). Change the size with per_page and move with page. The response has the items in data, page links in links and counts such as current_page, per_page and total in meta. The inline list export pages with a cursor instead: pass limit (at most 5,000) and, while has_more is true, the returned next_cursor.

Endpoints

All paths below are relative to your instance, for example https://your-domain.com/api/v1/subscribers. Replace the parts in braces with IDs from your account. Rows marked GOLD need an active GOLD licence and answer 402 without one; group descriptions name the calls that need GOLD only for some values.

Account and connection test

Identify the account behind a key. /api/mcp/test is the connection check used by MCP clients.

MethodPathScope
GET/api/v1/accountany valid key
GET/api/mcp/testany valid key

Subscribers

Create, read, update and delete contacts. POST /subscribers needs contact_list_id, plus email for an email list or phone for an SMS list; optional fields are first_name, last_name, status, source, tags (tag IDs), custom_fields, ip_address, user_agent and device. If the address already exists, it is added to the list or reactivated instead of duplicated. Filter GET /subscribers with contact_list_id, status, email (partial match), tag_id, sort_by and sort_order. POST /subscribers/{id}/tags replaces all tags of the subscriber. Deleting is a soft delete.

MethodPathScope
GET/api/v1/subscribersany valid key
POST/api/v1/subscriberssubscribers:write
POST/api/v1/subscribers/batchsubscribers:write
GET/api/v1/subscribers/{id}any valid key
GET/api/v1/subscribers/by-email/{email}any valid key
PUT, PATCH/api/v1/subscribers/{id}subscribers:write
DELETE/api/v1/subscribers/{id}subscribers:write
POST/api/v1/subscribers/{id}/tagssubscribers:write
GET/api/v1/subscribers/{id}/activitysubscribers:read

Contact lists

Manage email and SMS lists. PUT merges settings: fields you leave out stay unchanged. Settings include double_opt_in, default_mailbox_id, default_sms_provider_id, max_subscribers, resubscription_behavior, signups_blocked, webhook_url and webhook_events. Deleting a list that has members answers 409 until you pass confirm=1; the subscribers themselves are kept. /stats returns members by status, engagement, 30-day growth and the settings that affect sending.

MethodPathScope
GET/api/v1/listsany valid key
POST/api/v1/listslists:write
GET/api/v1/lists/{id}any valid key
PUT, PATCH/api/v1/lists/{id}lists:write
DELETE/api/v1/lists/{id}lists:write
GET/api/v1/lists/{id}/statsany valid key
GET/api/v1/lists/{id}/subscribersany valid key
GET/api/v1/lists/{id}/cron-settingslists:read
PUT/api/v1/lists/{id}/cron-settingslists:write
GET/api/v1/settings/list-defaultslists:read
PUT/api/v1/settings/list-defaultslists:write

Import and export

Import CSV, TSV, JSON or a plain list of addresses (up to 5,000 rows per request) with column mapping and safety options such as skipping invalid, disposable, suppressed or role addresses and fixing domain typos. Preview first with /import/preview. GET /export returns members inline as JSON, CSV or NDJSON with cursor paging; POST /export queues a full CSV export and sends the account owner a download link.

MethodPathScope
POST/api/v1/lists/{id}/import/previewlists:read
POST/api/v1/lists/{id}/importlists:write
GET/api/v1/lists/{id}/exportsubscribers:read
GET/api/v1/lists/{id}/export/fieldsany valid key
POST/api/v1/lists/{id}/exportany valid key
GET/api/v1/exports/downloadnone (signed URL)

List hygiene

GET /hygiene scores a list from 0 to 100 and counts problem categories such as invalid syntax, typo domains, disposable domains, role addresses, duplicates, hard bounces, never engaged and dormant contacts. clean unsubscribes, removes, tags, deletes or suppresses chosen categories; dedupe merges contacts that point to the same mailbox; verify checks syntax and MX records. clean and dedupe default to dry_run=true, and destructive actions need confirm=true.

MethodPathScope
GET/api/v1/lists/hygiene-optionsany valid key
GET/api/v1/lists/{id}/hygienelists:read
POST/api/v1/lists/{id}/hygiene/cleanlists:write
POST/api/v1/lists/{id}/hygiene/dedupelists:write
POST/api/v1/lists/{id}/hygiene/verifylists:read

List membership (bulk)

Add existing contacts to a list, remove them, change their status, move or copy them to another list, or add and remove tags, for up to 5,000 contacts per call. Select contacts with subscriber_ids, emails or a filter (status, tags, engagement, signup date). Move and copy transfer only contacts that are active on the source list and need lists of the same channel. Pass trigger_automations: false to move people without restarting welcome sequences.

MethodPathScope
POST/api/v1/lists/{id}/memberslists:write
POST/api/v1/lists/{id}/members/removelists:write
POST/api/v1/lists/{id}/members/statuslists:write
POST/api/v1/lists/{id}/members/movelists:write
POST/api/v1/lists/{id}/members/copylists:write
POST/api/v1/lists/{id}/members/tagslists:write

Activity and engagement

/activity merges signups, confirmations, unsubscribes, opens, clicks and send results of a list into one feed, filterable by days, limit and types[]. /engagement returns audience make-up, growth and churn with a daily series, open and click rates, top messages and links, and the size of the inactive part of the list.

MethodPathScope
GET/api/v1/lists/{id}/activitylists:read
GET/api/v1/lists/{id}/engagementlists:read

Suppression list

An account-wide do-not-mail list that every import respects. POST takes emails[], an optional reason and unsubscribe_existing (default true, unsubscribes the address from all lists). DELETE lifts the block but does not resubscribe anyone.

MethodPathScope
GET/api/v1/suppressionssubscribers:read
POST/api/v1/suppressionssubscribers:write
DELETE/api/v1/suppressionssubscribers:write

Tags and custom fields

Read tags and custom fields with any valid key; create, change and delete them with lists:write. /custom-fields/placeholders lists every placeholder you can use in email and SMS content, such as [[fname]], [[unsubscribe]] and one per custom field. Deleting a custom field that is still in use answers 409.

MethodPathScope
GET/api/v1/tagsany valid key
GET/api/v1/tags/{id}any valid key
POST/api/v1/tagslists:write
PUT, PATCH/api/v1/tags/{id}lists:write
DELETE/api/v1/tags/{id}lists:write
GET/api/v1/custom-fieldsany valid key
GET/api/v1/custom-fields/placeholdersany valid key
GET/api/v1/custom-fields/{id}any valid key
POST/api/v1/custom-fieldslists:write
PUT, PATCH/api/v1/custom-fields/{id}lists:write
DELETE/api/v1/custom-fields/{id}lists:write

Email sending

POST /email/send queues one email: email, subject and content (HTML) are required; preheader, plain_text, mailbox_id, subscriber_id, schedule_at, tracking_enabled and extra headers are optional. POST /email/batch sends one message to a list (list_id or contact_list_ids), to tag_ids or to subscriber_ids, with optional excluded_list_ids. Both answer 202 with an ID you can pass to /email/status/{id}.

MethodPathScope
POST/api/v1/email/sendemail:write
POST/api/v1/email/batchemail:write
GET/api/v1/email/status/{id}email:read
GET/api/v1/email/mailboxesemail:read

SMS sending

POST /sms/send queues one SMS: phone and message (up to 1,600 characters) are required; provider_id, subscriber_id and schedule_at are optional. POST /sms/batch sends to a list_id, tag_ids or subscriber_ids. Placeholders such as [[fname]] are filled per recipient.

MethodPathScope
POST/api/v1/sms/sendsms:write
POST/api/v1/sms/batchsms:write
GET/api/v1/sms/status/{id}sms:read
GET/api/v1/sms/providerssms:read

Campaigns (messages)

Campaigns are called messages in the API, and every /messages path also works under /campaigns. Create one with subject, channel (email or sms) and type (broadcast or autoresponder), then set recipient lists and exclusions, and send or schedule it. Sending and scheduling answer 422 while the campaign has no list, no mailbox (email) or no content. schedule takes scheduled_at and an optional timezone. Filter the list with channel, type, status and search. Saving a campaign with an enabled ab_test_config of two or more variants needs GOLD and is refused with 402 before anything is saved.

MethodPathScope
GET/api/v1/messagesany valid key
POST/api/v1/messagesmessages:write
GET/api/v1/messages/{id}any valid key
PUT, PATCH/api/v1/messages/{id}messages:write
DELETE/api/v1/messages/{id}messages:write
POST/api/v1/messages/{id}/listsmessages:write
POST/api/v1/messages/{id}/exclusionsmessages:write
POST/api/v1/messages/{id}/schedulemessages:write
POST/api/v1/messages/{id}/sendmessages:write
POST/api/v1/messages/{id}/testmessages:write
GET, POST/api/v1/messages/{id}/previewmessages:read
GET/api/v1/messages/{id}/statsany valid key
GET/api/v1/messages/{id}/recipients-countmessages:read
POST/api/v1/messages/{id}/duplicatemessages:write
POST/api/v1/messages/{id}/toggle-activemessages:write
POST/api/v1/messages/{id}/resend-failedmessages:write
POST/api/v1/messages/{id}/send-to-missedmessages:write

A/B tests

Create a test for a campaign, add variants, start and end it, and read the results. Creating a test, adding variants and starting it need GOLD; ending a test, reading its results and deleting it work on every plan, so a test that was already running finishes after a GOLD licence ends.

MethodPathScope
GET/api/v1/ab-testsany valid key
POST/api/v1/ab-testsmessages:writeGOLD
GET/api/v1/ab-tests/{id}any valid key
POST/api/v1/ab-tests/{id}/variantsmessages:writeGOLD
POST/api/v1/ab-tests/{id}/startmessages:writeGOLD
POST/api/v1/ab-tests/{id}/endmessages:write
GET/api/v1/ab-tests/{id}/resultsany valid key
DELETE/api/v1/ab-tests/{id}messages:write

Funnels

Automation sequences with steps. A funnel starts on list_signup, tag_added, form_submit or manual. Add, change and remove steps, activate or pause the funnel, enrol a subscriber by hand (the funnel must be active, otherwise 409) and read its statistics. Funnels are part of SILVER; adding a step of type split (A/B split) or turning a step into one needs GOLD, while split steps a funnel already has stay editable.

MethodPathScope
GET/api/v1/funnelsany valid key
POST/api/v1/funnelsfunnels:write
GET/api/v1/funnels/{id}any valid key
PUT, PATCH/api/v1/funnels/{id}funnels:write
DELETE/api/v1/funnels/{id}funnels:write
POST/api/v1/funnels/{id}/stepsfunnels:write
PUT/api/v1/funnels/{id}/steps/{step}funnels:write
DELETE/api/v1/funnels/{id}/steps/{step}funnels:write
POST/api/v1/funnels/{id}/subscribersfunnels:write
POST/api/v1/funnels/{id}/activatefunnels:write
POST/api/v1/funnels/{id}/pausefunnels:write
GET/api/v1/funnels/{id}/statsany valid key

Automation rules

Rules of the form when a trigger happens, if conditions match, run actions. /automations/options lists the triggers, conditions and actions you can use. Rules can be switched on and off, duplicated, and their run logs read.

MethodPathScope
GET/api/v1/automations/optionsfunnels:read
GET/api/v1/automationsfunnels:read
POST/api/v1/automationsfunnels:write
GET/api/v1/automations/{id}funnels:read
PUT, PATCH/api/v1/automations/{id}funnels:write
DELETE/api/v1/automations/{id}funnels:write
POST/api/v1/automations/{id}/togglefunnels:write
POST/api/v1/automations/{id}/duplicatefunnels:write
GET/api/v1/automations/{id}/logsfunnels:read

Webhooks

Register URLs that receive signed event notifications. The signing secret is returned only when the webhook is created or its secret is regenerated. /webhooks/events lists the event names, and /test sends a test delivery. The webhooks documentation lists every event, its payload and how to verify signatures.

MethodPathScope
GET/api/v1/webhooks/eventsany valid key
GET/api/v1/webhooksany valid key
POST/api/v1/webhookswebhooks:write
GET/api/v1/webhooks/{id}any valid key
PUT, PATCH/api/v1/webhooks/{id}webhooks:write
DELETE/api/v1/webhooks/{id}webhooks:write
POST/api/v1/webhooks/{id}/testwebhooks:write
POST/api/v1/webhooks/{id}/regenerate-secretwebhooks:write

Templates and saved blocks

Email templates, their categories and the saved blocks of the email builder. /templates/{id}/preview renders a template.

MethodPathScope
GET/api/v1/templatesmessages:read
POST/api/v1/templatesmessages:write
GET/api/v1/templates/{id}messages:read
PUT, PATCH/api/v1/templates/{id}messages:write
DELETE/api/v1/templates/{id}messages:write
POST/api/v1/templates/{id}/duplicatemessages:write
POST/api/v1/templates/{id}/previewmessages:read
GET/api/v1/template-categoriesmessages:read
GET/api/v1/template-blocks/typesmessages:read
GET/api/v1/template-blocksmessages:read
POST/api/v1/template-blocksmessages:write
PUT, PATCH/api/v1/template-blocks/{id}messages:write
DELETE/api/v1/template-blocks/{id}messages:write

System emails and system pages

The emails and pages NetSendo shows around signup, confirmation and unsubscribe, addressed by slug. Without list_id you work on the global default; with list_id the first write creates an override for that list, and DELETE removes the override so the list falls back to the default. Global defaults cannot be deleted.

MethodPathScope
GET/api/v1/system-emailslists:read
GET/api/v1/system-emails/{slug}lists:read
PUT/api/v1/system-emails/{slug}lists:write
PUT/api/v1/system-emails/{slug}/activelists:write
DELETE/api/v1/system-emails/{slug}lists:write
GET/api/v1/system-pageslists:read
GET/api/v1/system-pages/{slug}lists:read
PUT/api/v1/system-pages/{slug}lists:write
DELETE/api/v1/system-pages/{slug}lists:write

Subscription forms

Create, change, duplicate and delete the signup forms of your lists.

MethodPathScope
GET/api/v1/formslists:read
POST/api/v1/formslists:write
GET/api/v1/forms/{id}lists:read
PUT, PATCH/api/v1/forms/{id}lists:write
DELETE/api/v1/forms/{id}lists:write
POST/api/v1/forms/{id}/duplicatelists:write

Notifications

Create notifications in the account owner's notification centre: title (required), message, type (info, success, warning or error), list_id or action_url, and free-form data. Nothing is sent to subscribers.

MethodPathScope
GET/api/v1/notificationsany valid key
POST/api/v1/notificationsnotifications:write

Integration helpers

Endpoints used by the WordPress, WooCommerce and other plugins: external pages, plugin version checks and connections. /api/webhooks/purchase receives purchases from e-commerce integrations and needs a valid API key.

MethodPathScope
GET/api/v1/external-pagesany valid key
GET/api/v1/plugin/check-versionany valid key
POST/api/v1/plugin/heartbeatany valid key
GET/api/v1/plugin/connectionsany valid key
DELETE/api/v1/plugin/connections/{id}any valid key
POST/api/v1/plugin/connections/{id}/disconnectany valid key
POST/api/webhooks/purchaseany valid key

NetSendo Brain

The AI assistant built into NetSendo: chat, conversations, knowledge base, action plans and their approval, settings and the Telegram connection. POST /brain/chat takes a message of up to 5,000 characters and an optional conversation_id. The chat in manual mode is part of SILVER. Brain working on its own needs GOLD: PUT /brain/settings answers 402 for work_mode semi_auto or autonomous, non-manual agent_modes, a telegram_bot_token or research keys (perplexity_api_key, serpapi_api_key). Knowledge base entries hold up to 10,000 characters, 100,000 with GOLD, where GET /brain/knowledge?search= also finds entries by meaning (semantic search, since 2.7.0).

MethodPathScope
POST/api/v1/brain/chatany valid key
GET/api/v1/brain/conversationsany valid key
GET/api/v1/brain/conversations/{id}any valid key
GET/api/v1/brain/knowledgeany valid key
POST/api/v1/brain/knowledgeany valid key
PUT/api/v1/brain/knowledge/{id}any valid key
DELETE/api/v1/brain/knowledge/{id}any valid key
GET/api/v1/brain/plansany valid key
GET/api/v1/brain/plans/{id}any valid key
POST/api/v1/brain/plans/{id}/approveany valid key
GET/api/v1/brain/settingsany valid key
PUT/api/v1/brain/settingsany valid key
POST/api/v1/brain/telegram/link-codeany valid keyGOLD
POST/api/v1/brain/telegram/disconnectany valid key
POST/api/v1/brain/telegram/testany valid keyGOLD
POST/api/v1/brain/telegram/set-webhookany valid keyGOLD

List subscription endpoints

For website forms and simple integrations that should reach one list only. These endpoints do not take an account API key: authenticate with the list's own key, generated in the list settings under Integration → List API Key. subscribe takes email (or phone for an SMS list), first_name and last_name; unsubscribe takes email. A list that does not accept signups answers 403. Errors here use success: false with error or errors.

MethodPathScope
POST/api/v1/lists/{id}/subscribethe list's own API key
POST/api/v1/lists/{id}/unsubscribethe list's own API key

Examples

Add a subscriber to a list
curl -X POST https://your-domain.com/api/v1/subscribers \
  -H "Authorization: Bearer ns_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "email": "anna@example.com",
    "contact_list_id": 5,
    "first_name": "Anna",
    "tags": [1, 3],
    "custom_fields": { "city": "Berlin" }
  }'

# 201 Created (200 when the address already existed)
{
  "data": {
    "id": 456,
    "email": "anna@example.com",
    "first_name": "Anna",
    "status": "active",
    "contact_list_id": 5,
    "contact_list_ids": [5],
    "source": "api",
    "tags": [{ "id": 1, "name": "VIP", ... }, { "id": 3, "name": "Newsletter", ... }],
    "custom_fields": { "email": "anna@example.com", "fname": "Anna", ..., "city": "Berlin" },
    "subscribed_at": "2026-11-07T10:30:00.000000Z",
    "created_at": "2026-11-07T10:30:00.000000Z",
    ...
  }
}
Send a transactional email
curl -X POST https://your-domain.com/api/v1/email/send \
  -H "Authorization: Bearer ns_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "email": "anna@example.com",
    "subject": "Your order is on its way",
    "content": "<p>Hi [[fname]], your order has shipped.</p>",
    "subscriber_id": 456,
    "mailbox_id": 1
  }'

# 202 Accepted
{
  "data": {
    "id": 123,
    "email": "anna@example.com",
    "subject": "Your order is on its way",
    "status": "queued",
    "mailbox": "Main mailbox",
    "scheduled_at": null
  },
  "message": "Email queued successfully"
}
Create and send a campaign
# 1. Create a draft email campaign for list 5
curl -X POST https://your-domain.com/api/v1/messages \
  -H "Authorization: Bearer ns_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "subject": "November news",
    "channel": "email",
    "type": "broadcast",
    "content": "<h1>Hello [[fname]]</h1>",
    "contact_list_ids": [5],
    "mailbox_id": 1
  }'

# 2a. Send it now (42 = the id returned in step 1)
curl -X POST https://your-domain.com/api/v1/messages/42/send \
  -H "Authorization: Bearer ns_live_YOUR_KEY" \
  -H "Accept: application/json"

# 2b. Or schedule it in a given time zone
curl -X POST https://your-domain.com/api/v1/messages/42/schedule \
  -H "Authorization: Bearer ns_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{ "scheduled_at": "2026-11-20 09:00", "timezone": "Europe/Warsaw" }'
JavaScript (fetch)
const res = await fetch(
  "https://your-domain.com/api/v1/subscribers?status=active&per_page=50",
  {
    headers: {
      Authorization: `Bearer ${process.env.NETSENDO_API_KEY}`,
      Accept: "application/json",
    },
  }
)

if (res.status === 429) {
  const wait = Number(res.headers.get("Retry-After") ?? 60)
  // try again after `wait` seconds
}

const { data, meta } = await res.json()
console.log(meta.total, data.map((s) => s.email))
Subscribe from a website form with the list key
curl -X POST https://your-domain.com/api/v1/lists/5/subscribe \
  -H "Authorization: Bearer YOUR_LIST_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{ "email": "anna@example.com", "first_name": "Anna" }'

# 201 Created
{
  "success": true,
  "message": "Successfully subscribed",
  "subscriber_id": 456,
  "status": "new"
}

OpenAPI specification in your instance

Every NetSendo installation also generates an OpenAPI description of its API. Sign in to the panel and open https://your-domain.com/docs/api for the interactive reference, or /docs/api.json for the raw specification. Both are visible only to signed-in users.

The interactive API reference inside a NetSendo installation

Frequently asked questions

How do I authenticate with the NetSendo API?

Create an API key in the NetSendo panel under Settings → API Keys and send it in the Authorization header as a Bearer token: Authorization: Bearer ns_live_… The key is shown only once, when you create it.

What is the NetSendo API rate limit?

3,000 requests per minute per API key. Over the limit the API answers 429 Too Many Requests with a Retry-After header. Batch endpoints accept up to 1,000 subscribers per request.

Which scope does a key need to add subscribers?

subscribers:write for POST /api/v1/subscribers and POST /api/v1/subscribers/batch. Imports and bulk membership changes on a list need lists:write. A write scope also grants the matching read scope.

Is there an OpenAPI specification for the NetSendo API?

Yes. Each installation generates one: sign in to the panel and open /docs/api for the interactive reference or /docs/api.json for the specification.

How can a website form add subscribers without an account API key?

Generate a key for the list in its settings under Integration → List API Key, then POST the form data to /api/v1/lists/{id}/subscribe with that key as a Bearer token. The key works only for that list.

What is the difference between /messages and /campaigns?

None. /campaigns is an alias of /messages: both paths reach the same campaign endpoints with the same scopes.