NevTan Engage

Features

Email MarketingSMS MarketingAutomations

Solutions

eCommerceSaaS & AppsB2B Lead GenAgencies

Resources

BlogHelp CenterDocumentationEmail TemplatesAPIFAQs

Company

AboutPricingContactNevTan

Legal

Data Processing AgreementSubprocessor ListAI & Data Usage PolicySecurity & Compliance

© 2026 NevTan Engage. All rights reserved.

Cookie Policy | Terms and Conditions | Privacy Policy

Skip to main content

Channels & Automation

Email Marketing

Beautiful campaigns that convert

SMS Marketing

Reach customers instantly

Automations

Visual drag-and-drop workflows

By Industry

eCommerce

Boost sales & reduce cart abandonment

SaaS & Apps

Onboard & retain users at scale

B2B Lead Gen

Nurture leads to conversion

Agencies

White-label for your clients

Plans & Pricing

Pricing Plans

Simple, transparent pricing

Learn & Build

Blog

Marketing tips and best practices

Email Marketing Templates

Ready-to-use campaign layouts

Documentation

Guides for every feature in Engage

API

Build custom integrations

About Us

Contact Us

LoginStart Free Trial(No card)

Getting started

  • Introduction
  • Create your account
  • Signing in
  • Your dashboard & navigation

Contacts

  • Contacts & profiles
  • Lists
  • Importing contacts
  • Segments
  • Suppression list

Campaigns

  • Campaigns overview
  • Create & send a campaign
  • Reading a campaign's results

Templates

  • Template library
  • Create with AI
  • The template editor
  • Template approval

Automations

  • Built-in flows
  • Custom flows

Analytics

  • Contact Growth
  • Campaign Reports
  • Automation Reports
  • Metrics glossary

Integrations

  • Webhooks
  • API Keys

Account & billing

  • Your profile
  • Custom sending domains
  • Plans & pricing
  • Billing, usage & invoices
  • Team & roles

Administration

  • Admin overview
  • Campaign & template review
  • Customers & packages

Documentation/Integrations

API Keys

Call the Engage API from your own code, without a signed-in session.

An API key lets your own code call Engage directly — send a push, read a segment, add a contact — with no person signed in. You create a key once, store it in your application's configuration, and send it on every request.

⚠️

A key acts on your account with your permissions. Treat it like a password: keep it in environment variables or a secret manager, never in front-end code, a public repository, or a URL.

1. Create a key

1

Open API Keys

From the sidebar, open API Key.

2

Click Create Key

Give it a name that says what will use it, e.g. "Casino site — push sender". You cannot revoke what you cannot identify.

3

Copy the key immediately

The full key is shown once. Engage stores only a hash of it, so it can never be shown again — if you lose it, revoke it and create another.

ℹ️

If you do not see API Key in the sidebar, your plan does not include it. Ask your account manager to enable API access.

2. Authenticate

Send the key in the Authorization header on every request. Every key starts with eng_live_.

http
Authorization: Bearer eng_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

The base URL is your Engage API host followed by /api/v1. Set both once and the examples below work as written:

bash
export ENGAGE_API_KEY='eng_live_...'
export ENGAGE_API='https://api.engage.nevtan.com/api/v1'

A quick check that the key works — it touches authentication, the account and permissions in one call:

bash
curl "$ENGAGE_API/push/segments" \
  -H "Authorization: Bearer $ENGAGE_API_KEY"

Every response uses the same envelope, so you can handle success and failure the same way everywhere:

json
{
  "status": "success",
  "message": "",
  "data": [ ... ]
}

3. Create a contact

POST /api/v1/contacts is the simplest complete example — nothing to look up first. It needs the contacts:write permission.

bash
curl -X POST "$ENGAGE_API/contacts" \
  -H "Authorization: Bearer $ENGAGE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "apikey@example.com",
    "first_name": "api",
    "last_name": "testing",
    "phone": "+919876543210"
  }'

Every field is optional. Two more are useful: external_id to carry your own identifier for the person, and list_id to drop them straight into a list in the same call.

json
{
  "status": "success",
  "message": "",
  "data": {
    "_id": "000000000000000000000001",
    "email": "apikey@example.com",
    "first_name": "api",
    "last_name": "testing",
    "phone": "+919876543210",
    "subscribed": true,
    "created_at": "2026-01-01T00:00:00Z"
  }
}
⚠️

Check status, not the HTTP code. A duplicate email comes back as HTTP 200 with "status": "failure", not a 409. Code that only tests response.ok will record a rejected contact as created.

Three behaviours worth knowing

  • Duplicate emails are rejected across the whole system, not just your account. If that address exists anywhere, you get Contact with this email already exists. Bulk imports run into this.
  • Sending `phone` opts the contact into SMS. A contact added this way is treated as a manual, consented add, so SMS consent is recorded as subscribed. If you are importing numbers you do not have consent for, leave phone out on create and add it with a PATCH afterwards.
  • The email is not validated. It is stored as given, so not-an-email is accepted.

Read them back with GET /api/v1/contacts, which needs only contacts:read.

4. Send a push

POST /api/v1/push/send needs campaigns:write. Pass a segmentId taken from /push/segments above, or leave it out to reach every live device on the account.

bash
curl -X POST "$ENGAGE_API/push/send" \
  -H "Authorization: Bearer $ENGAGE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Your bonus is live",
    "body": "Claim it before midnight.",
    "segmentId": "PASTE_A_SEGMENT_ID_HERE",
    "link": "https://example.com/promo",
    "dryRun": true
  }'
💡

Keep dryRun set to true while you are wiring this up. The message is fully validated and real errors come back, but nothing is delivered to anyone. Remove it when you are ready to send for real.

The response tells you what happened, with no invented certainty about delivery:

json
{
  "status": "success",
  "message": "sent",
  "data": {
    "campaignId": "000000000000000000000002",
    "segmentName": "Active players",
    "contactsInSegment": 4120,
    "reachableContacts": 3874,
    "total": 4192,
    "accepted": 4180,
    "retired": 12,
    "dryRun": false
  }
}
FieldWhat it means
totalDevices the message was addressed to. One person with three devices counts three times.
acceptedDevices the push service took responsibility for. Not a delivery count — what happens after that is invisible to us.
retiredTokens the push service rejected as dead. Engage removes them so the next send is cleaner.
campaignIdThe row this send created in your push history.
ℹ️

accepted is not the same as delivered. A push service returning 200 means it accepted the message for delivery; whether the handset was reachable is something no push platform reports back.

5. Run an email campaign

Four calls, all on campaigns:write except the last. Each returns an id the next one needs, so keep them as you go.

5.1 Put the audience in a list

A campaign sends to lists or segments. Creating a list needs lists:write:

bash
curl -X POST "$ENGAGE_API/lists" \
  -H "Authorization: Bearer $ENGAGE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "API test list", "description": "Created from the API" }'

Add someone to it. The contact is created if that email is not on file yet, so this doubles as an import:

bash
curl -X POST "$ENGAGE_API/lists/LIST_ID/contacts" \
  -H "Authorization: Bearer $ENGAGE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "apikey@example.com",
    "first_name": "api",
    "last_name": "testing"
  }'

5.2 Create the campaign

channel and type are numbers, not names. For a normal email campaign send channel: 2 (single channel) and type: 1 (email) — and with channel: 2, `date` and `type` are both required or the request is rejected before it reaches the handler.

bash
curl -X POST "$ENGAGE_API/campaign" \
  -H "Authorization: Bearer $ENGAGE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "API test campaign",
    "channel": 2,
    "type": 1,
    "date": "2026-01-01T00:00:00Z",
    "subject": "Hello from the API",
    "body": "<h1>Hi {{first_name}}</h1><p>Sent with an API key.</p>",
    "sender": "hello@yourdomain.com",
    "sender_name": "Your Team",
    "reply_to": "hello@yourdomain.com"
  }'
FieldValueMeaning
channel1 / 2Omni-channel / single-channel
type1 / 2 / 3 / 4Email / SMS / IM / Push
dateISO 8601Required when channel is 2
senderan address on a verified domainSending from an unverified domain will fail at send time, not here

5.3 Send it

list_id and segment_ids are arrays, even for one. template_source chooses where the content comes from: "LIST" for a saved template, "DRAFT" for a builder draft. Omit both and the campaign's own body is used.

bash
curl -X POST "$ENGAGE_API/campaign/CAMPAIGN_ID/send" \
  -H "Authorization: Bearer $ENGAGE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "list_id": ["LIST_ID"],
    "batch_size": 100,
    "delay_between_batches": 1.0
  }'
json
{
  "status": "success",
  "message": "",
  "data": {
    "success": true,
    "message": "Campaign sent",
    "total_members": 1,
    "emails_sent": 1,
    "errors": [],
    "started_at": "2026-01-01T00:00:00Z",
    "finished_at": "2026-01-01T00:00:04Z"
  }
}
⚠️

This sends real email the moment it returns. There is no dry-run on this endpoint — unlike /push/send, which has dryRun. Point it at a list containing only your own address the first time.

💡

batch_size (1–1000) and delay_between_batches (0.1–60 seconds) pace the send. The defaults — 100 per batch, one second apart — are a reasonable starting point; slow them down if your domain is newly warmed.

5.4 Read the results

Needs only campaigns:read:

bash
curl "$ENGAGE_API/campaign/CAMPAIGN_ID/stats" \
  -H "Authorization: Bearer $ENGAGE_API_KEY"

Related calls on the same key: GET /campaign lists campaigns, GET /campaign/{id}/recipients/activity shows per-recipient opens and clicks, and POST /campaign/{id}/ab-test/start begins an A/B test.

ℹ️

Analytics endpoints under /api/v1/analytics do not accept an API key yet — they still require a signed-in session. Per-campaign numbers from /campaign/{id}/stats do work, so most reporting is reachable without them.

What a key can and cannot do

A key is issued with everything the person who created it can do — sending campaigns, reading and writing contacts, segments and templates, reading analytics. It is never wider than its creator: if your own permissions are reduced later, every key you made narrows with them.

Four things are deliberately withheld from every key, however wide its creator's access:

  • Adding or changing team members
  • Billing
  • Changing account settings
  • Creating or listing other API keys

These are the permissions that would let a stolen key entrench itself — invite an accomplice, or mint fresh keys to survive the one you revoke. Nothing an integration legitimately does needs them.

Rate limits

Each key may make 120 requests per minute. The limit is per key, not per server, so scaling your integration across more workers does not buy more requests.

Going over returns HTTP 429 with a message telling you how long to wait. Back off and retry; do not spin.

json
{
  "message": "Too many requests. Try again in 34 seconds.",
  "success": false
}

Revoking a key

Open API Key, find the key in the table and click the delete icon. It stops working immediately, and anything using it starts failing on the next call.

💡

The record of the key is kept rather than erased, so you can still see what it was called and when it was last used. "Which key could send in March" is a question that outlives the key itself.

Revoke a key the moment you suspect it has leaked, and whenever the integration that used it is retired. Keys do not expire on their own.

Errors

StatusMeaningWhat to do
401The key is unknown, revoked, or expired — or the account it belongs to is deactivatedCheck the key was copied in full, then create a new one
403The key is valid but lacks the permission for that endpoint, or API keys are not enabled for the accountCheck the endpoint against the list above; a key never holds team, billing, settings or key-management permissions
429Rate limit exceededWait the number of seconds in the message, then retry
ℹ️

A 401 is deliberately the same answer for an unknown key, a revoked key and an expired one. Distinguishing them would let anyone with a list of guesses learn which keys exist.

Good practice

  • One key per integration. Then revoking a compromised key takes down one thing, not everything.
  • Keep it server-side. A key in browser or mobile code is a key you have published.
  • Rotate deliberately. Create the new key, deploy it, confirm traffic has moved, then revoke the old one — in that order, so nothing goes dark.
  • Watch Last used. A key that has not been used in months is a key nobody will miss when you revoke it.

Previous

Webhooks

Next

Your profile

Still need a hand?

Our support team usually replies within one business day.

Contact support

On this page

  • 1. Create a key
  • 2. Authenticate
  • 3. Create a contact
  • 4. Send a push
  • 5. Run an email campaign
  • What a key can and cannot do
  • Rate limits
  • Revoking a key
  • Errors
  • Good practice