Relay API Reference

REST API reference for Relay Solution: send SMS and WhatsApp messages, manage contacts, segments and campaigns, and read delivery logs. 47 endpoints, authenticated with a single API key. This page is public — no account needed to read it.

Two hosts, one API key

Generate a REST API key in the portal, then send it as Authorization: Bearer <key> or X-API-Key. One key works for both hosts. Which host serves a request is decided entirely by its path:

GW
Messaging — send SMS and WhatsApp, read and write contact tags and CDP attributes. Every path under /api/v1/.
https://ops.relay.cequens.com
CP
Management API — contacts, segments, campaigns, message logs, apps and billing reads. Every path under /api/mgmt/v1/. A key must have Management API access enabled, which is a per-key setting in the portal.
https://portal.relay.cequens.com

The messaging host sends no CORS headers, so those endpoints are server-to-server only — they cannot be called from a browser.

Platform

Platform connectivity check — no authentication required, useful for uptime monitoring. Every other endpoint uses API key auth (Authorization: Bearer <key> or X-API-Key: <key>).

GET /api/health Messaging host

Platform health check

Returns the connectivity status of MongoDB and NATS. No authentication required — useful for uptime monitoring.

cURL

curl -X GET "https://ops.relay.cequens.com/api/health" \
  -H "Content-Type: application/json"

JavaScript

const res = await fetch("https://ops.relay.cequens.com/api/health", {
  method: "GET",
  headers: {
    "Content-Type": "application/json"
},
});
const data = await res.json();
console.log(data);

Responses

200

Service statuses

{
  "status": "ok",
  "db": "connected",
  "nats": "connected"
}

Contacts

CRM for managing subscribers — create, query, import and delete contacts. Also includes the gateway API Key endpoints for reading and updating contacts and CDP attributes programmatically; those run on the messaging host, the rest on the management host. Each endpoint below names the host that serves it.

GET /api/mgmt/v1/contacts Management host API key

List contacts

Paginated contact list. Supports filtering by status, segment, tag, and full-text search.

Parameters

NameInTypeDescription
page query integer Page number (default 1) 1
limit query integer Items per page, max 200 (default 50) 50
status query string subscribed or unsubscribed subscribed
tag query string Filter by tag vip
segment query string Segment ObjectId 64f1...
search query string Full-text: name, phone, email, tag jane

cURL

curl -X GET "https://portal.relay.cequens.com/api/mgmt/v1/contacts" \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json"

JavaScript

const res = await fetch("https://portal.relay.cequens.com/api/mgmt/v1/contacts", {
  method: "GET",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <api_key>"
},
});
const data = await res.json();
console.log(data);

Responses

200

Contacts page

{
  "contacts": [
    {
      "_id": "...",
      "phone": "+1...",
      "name": "Jane",
      "status": "subscribed",
      "tags": [
        "vip"
      ]
    }
  ],
  "total": 1240,
  "page": 1,
  "limit": 50
}
GET /api/mgmt/v1/contacts/stats Management host API key

Contact statistics

Aggregate counts: total, subscribed, and per-segment breakdown.

cURL

curl -X GET "https://portal.relay.cequens.com/api/mgmt/v1/contacts/stats" \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json"

JavaScript

const res = await fetch("https://portal.relay.cequens.com/api/mgmt/v1/contacts/stats", {
  method: "GET",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <api_key>"
},
});
const data = await res.json();
console.log(data);

Responses

200

Stats

{
  "total": 1240,
  "subscribed": 1100,
  "subscribedPercent": 88.7,
  "segments": [
    {
      "name": "VIPs",
      "contactCount": 230
    }
  ]
}
GET /api/mgmt/v1/contacts/:id Management host API key

Get a contact

Returns a single contact by ObjectId, enriched with a freshly-computed engagementScore and delivered-message count in behaviorProfile.

Parameters

NameInTypeDescription
id * path string Contact ObjectId 64f1...

cURL

curl -X GET "https://portal.relay.cequens.com/api/mgmt/v1/contacts/{id}" \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json"

JavaScript

const res = await fetch("https://portal.relay.cequens.com/api/mgmt/v1/contacts/{id}", {
  method: "GET",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <api_key>"
},
});
const data = await res.json();
console.log(data);

Responses

200

Contact

{
  "_id": "...",
  "phone": "+12025551234",
  "name": "Jane Doe",
  "status": "subscribed",
  "tags": [
    "vip"
  ],
  "behaviorProfile": {
    "messagesSent": 12,
    "messagesDelivered": 11,
    "engagementScore": 72
  }
}
404

Not found

{
  "error": "Contact not found"
}
POST /api/mgmt/v1/contacts Management host API key

Create a contact

Creates a new contact. Phone number must be unique within the org.

Request body

{
  "phone": "+12025551234",
  "name": "Jane Doe",
  "email": "jane@example.com",
  "status": "subscribed",
  "tags": [
    "newsletter"
  ]
}

cURL

curl -X POST "https://portal.relay.cequens.com/api/mgmt/v1/contacts" \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json" \
  -d '{"phone":"+12025551234","name":"Jane Doe","email":"jane@example.com","status":"subscribed","tags":["newsletter"]}'

JavaScript

const res = await fetch("https://portal.relay.cequens.com/api/mgmt/v1/contacts", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <api_key>"
},
  body: JSON.stringify({
    "phone": "+12025551234",
    "name": "Jane Doe",
    "email": "jane@example.com",
    "status": "subscribed",
    "tags": [
        "newsletter"
    ]
}),
});
const data = await res.json();
console.log(data);

Responses

201

Created

{
  "_id": "...",
  "phone": "+12025551234",
  "name": "Jane Doe",
  "status": "subscribed"
}
409

Phone already exists

{
  "error": "Contact with this phone already exists"
}
POST /api/mgmt/v1/contacts/import Management host API key

Bulk import contacts

Upsert array of contacts. Existing phones are skipped (no update). Returns imported vs skipped counts.

Request body

{
  "contacts": [
    {
      "phone": "+12025551234",
      "name": "Jane",
      "status": "subscribed"
    },
    {
      "phone": "+12025559999",
      "name": "Bob",
      "tags": [
        "vip"
      ]
    }
  ]
}

cURL

curl -X POST "https://portal.relay.cequens.com/api/mgmt/v1/contacts/import" \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json" \
  -d '{"contacts":[{"phone":"+12025551234","name":"Jane","status":"subscribed"},{"phone":"+12025559999","name":"Bob","tags":["vip"]}]}'

JavaScript

const res = await fetch("https://portal.relay.cequens.com/api/mgmt/v1/contacts/import", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <api_key>"
},
  body: JSON.stringify({
    "contacts": [
        {
            "phone": "+12025551234",
            "name": "Jane",
            "status": "subscribed"
        },
        {
            "phone": "+12025559999",
            "name": "Bob",
            "tags": [
                "vip"
            ]
        }
    ]
}),
});
const data = await res.json();
console.log(data);

Responses

200

Import result

{
  "imported": 1,
  "skipped": 1
}
400

Invalid payload

{
  "error": "contacts array is required"
}
PUT /api/mgmt/v1/contacts/:id Management host API key

Update a contact

Full update of all editable contact fields.

Parameters

NameInTypeDescription
id * path string Contact ObjectId 64f1...

Request body

{
  "name": "Jane Smith",
  "tags": [
    "vip",
    "newsletter"
  ],
  "status": "subscribed"
}

cURL

curl -X PUT "https://portal.relay.cequens.com/api/mgmt/v1/contacts/{id}" \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json" \
  -d '{"name":"Jane Smith","tags":["vip","newsletter"],"status":"subscribed"}'

JavaScript

const res = await fetch("https://portal.relay.cequens.com/api/mgmt/v1/contacts/{id}", {
  method: "PUT",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <api_key>"
},
  body: JSON.stringify({
    "name": "Jane Smith",
    "tags": [
        "vip",
        "newsletter"
    ],
    "status": "subscribed"
}),
});
const data = await res.json();
console.log(data);

Responses

200

Updated contact

{
  "_id": "...",
  "phone": "+12025551234",
  "name": "Jane Smith"
}
404

Not found

{
  "error": "Contact not found"
}
GET /api/v1/contacts/:phone Messaging host API key

Get contact profile (Gateway)

Returns tags and CDP attributes for a phone number via API key. Returns 404 if the contact has never been seen by your account. **Served by the SMS gateway host, not the portal host** (see the base URL in the examples below).

Parameters

NameInTypeDescription
phone * path string E.164 phone (URL-encoded) %2B12025551234

cURL

curl -X GET "https://ops.relay.cequens.com/api/v1/contacts/{phone}" \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json"

JavaScript

const res = await fetch("https://ops.relay.cequens.com/api/v1/contacts/{phone}", {
  method: "GET",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <api_key>"
},
});
const data = await res.json();
console.log(data);

Responses

200

Contact profile

{
  "phone": "+12025551234",
  "name": "Jane Doe",
  "status": "subscribed",
  "tags": [
    "vip"
  ],
  "attributes": {
    "loyalty_tier": "gold"
  },
  "updatedAt": "2025-03-01T12:00:00Z"
}
404

Not found

{
  "error": "Contact not found"
}
POST /api/v1/contacts/:phone/tags Messaging host API key

Add tags to a contact (Gateway)

Appends tags without removing existing ones. Creates the contact if it does not exist. **Served by the SMS gateway host, not the portal host.**

Parameters

NameInTypeDescription
phone * path string E.164 phone (URL-encoded) %2B12025551234

Request body

{
  "tags": [
    "vip",
    "promo-june"
  ]
}

cURL

curl -X POST "https://ops.relay.cequens.com/api/v1/contacts/{phone}/tags" \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json" \
  -d '{"tags":["vip","promo-june"]}'

JavaScript

const res = await fetch("https://ops.relay.cequens.com/api/v1/contacts/{phone}/tags", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <api_key>"
},
  body: JSON.stringify({
    "tags": [
        "vip",
        "promo-june"
    ]
}),
});
const data = await res.json();
console.log(data);

Responses

200

Updated tag list

{
  "phone": "+12025551234",
  "tags": [
    "newsletter",
    "vip",
    "promo-june"
  ]
}
400

Invalid payload

{
  "error": "tags must be a non-empty array of strings"
}
PUT /api/v1/contacts/:phone/tags Messaging host API key

Replace all tags (Gateway)

Overwrites the entire tag list. Send [] to clear all tags. **Served by the SMS gateway host, not the portal host.**

Parameters

NameInTypeDescription
phone * path string E.164 phone (URL-encoded) %2B12025551234

Request body

{
  "tags": [
    "newsletter"
  ]
}

cURL

curl -X PUT "https://ops.relay.cequens.com/api/v1/contacts/{phone}/tags" \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json" \
  -d '{"tags":["newsletter"]}'

JavaScript

const res = await fetch("https://ops.relay.cequens.com/api/v1/contacts/{phone}/tags", {
  method: "PUT",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <api_key>"
},
  body: JSON.stringify({
    "tags": [
        "newsletter"
    ]
}),
});
const data = await res.json();
console.log(data);

Responses

200

New tag list

{
  "phone": "+12025551234",
  "tags": [
    "newsletter"
  ]
}
DELETE /api/v1/contacts/:phone/tags Messaging host API key

Remove tags from a contact (Gateway)

Removes the specified tags. Tags not present are silently ignored. **Served by the SMS gateway host, not the portal host.**

Parameters

NameInTypeDescription
phone * path string E.164 phone (URL-encoded) %2B12025551234

Request body

{
  "tags": [
    "promo-june"
  ]
}

cURL

curl -X DELETE "https://ops.relay.cequens.com/api/v1/contacts/{phone}/tags" \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json" \
  -d '{"tags":["promo-june"]}'

JavaScript

const res = await fetch("https://ops.relay.cequens.com/api/v1/contacts/{phone}/tags", {
  method: "DELETE",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <api_key>"
},
  body: JSON.stringify({
    "tags": [
        "promo-june"
    ]
}),
});
const data = await res.json();
console.log(data);

Responses

200

Remaining tags

{
  "phone": "+12025551234",
  "tags": [
    "newsletter",
    "vip"
  ]
}
404

Contact not found

{
  "error": "Contact not found"
}
PATCH /api/v1/contacts/:phone/attributes Messaging host API key

Set CDP attributes (Gateway)

Sets or updates CDP attributes. Existing keys not in the payload are preserved. Max 50 key-value pairs per request. Values are stored as strings. **Served by the SMS gateway host, not the portal host.**

Parameters

NameInTypeDescription
phone * path string E.164 phone (URL-encoded) %2B12025551234

Request body

{
  "attributes": {
    "loyalty_tier": "gold",
    "last_purchase": "2025-03-01",
    "preferred_language": "en"
  }
}

cURL

curl -X PATCH "https://ops.relay.cequens.com/api/v1/contacts/{phone}/attributes" \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json" \
  -d '{"attributes":{"loyalty_tier":"gold","last_purchase":"2025-03-01","preferred_language":"en"}}'

JavaScript

const res = await fetch("https://ops.relay.cequens.com/api/v1/contacts/{phone}/attributes", {
  method: "PATCH",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <api_key>"
},
  body: JSON.stringify({
    "attributes": {
        "loyalty_tier": "gold",
        "last_purchase": "2025-03-01",
        "preferred_language": "en"
    }
}),
});
const data = await res.json();
console.log(data);

Responses

200

Updated attributes

{
  "phone": "+12025551234",
  "attributes": {
    "loyalty_tier": "gold",
    "last_purchase": "2025-03-01",
    "preferred_language": "en"
  }
}
400

Invalid payload

{
  "error": "attributes must be a key-value object"
}
DELETE /api/v1/contacts/:phone/attributes/:key Messaging host API key

Remove a CDP attribute (Gateway)

Removes a single CDP attribute from the contact. **Served by the SMS gateway host, not the portal host.**

Parameters

NameInTypeDescription
phone * path string E.164 phone (URL-encoded) %2B12025551234
key * path string Attribute key to delete loyalty_tier

cURL

curl -X DELETE "https://ops.relay.cequens.com/api/v1/contacts/{phone}/attributes/{key}" \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json"

JavaScript

const res = await fetch("https://ops.relay.cequens.com/api/v1/contacts/{phone}/attributes/{key}", {
  method: "DELETE",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <api_key>"
},
});
const data = await res.json();
console.log(data);

Responses

200

Remaining attributes

{
  "phone": "+12025551234",
  "attributes": {
    "last_purchase": "2025-03-01"
  }
}
404

Contact not found

{
  "error": "Contact not found"
}

Segments

Create static or dynamic contact segments for campaign targeting.

GET /api/mgmt/v1/segments Management host API key

List segments

Returns all segments. Dynamic segments have their contactCount refreshed on each call.

cURL

curl -X GET "https://portal.relay.cequens.com/api/mgmt/v1/segments" \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json"

JavaScript

const res = await fetch("https://portal.relay.cequens.com/api/mgmt/v1/segments", {
  method: "GET",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <api_key>"
},
});
const data = await res.json();
console.log(data);

Responses

200

Segments

[
  {
    "_id": "...",
    "name": "VIPs",
    "type": "static",
    "contactCount": 230
  }
]
POST /api/mgmt/v1/segments Management host API key

Create a segment

Create a static (manually populated) or dynamic (filter-based) segment.

Request body

{
  "name": "High-value buyers",
  "description": "Top 10% by spend",
  "type": "static"
}

cURL

curl -X POST "https://portal.relay.cequens.com/api/mgmt/v1/segments" \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json" \
  -d '{"name":"High-value buyers","description":"Top 10% by spend","type":"static"}'

JavaScript

const res = await fetch("https://portal.relay.cequens.com/api/mgmt/v1/segments", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <api_key>"
},
  body: JSON.stringify({
    "name": "High-value buyers",
    "description": "Top 10% by spend",
    "type": "static"
}),
});
const data = await res.json();
console.log(data);

Responses

201

Created

{
  "_id": "...",
  "name": "High-value buyers",
  "type": "static",
  "contactCount": 0
}
PUT /api/mgmt/v1/segments/:id Management host API key

Update a segment

Update name, description, or filter rules.

Parameters

NameInTypeDescription
id * path string Segment ObjectId 64f1...

Request body

{
  "name": "Premium buyers",
  "description": "Updated description"
}

cURL

curl -X PUT "https://portal.relay.cequens.com/api/mgmt/v1/segments/{id}" \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json" \
  -d '{"name":"Premium buyers","description":"Updated description"}'

JavaScript

const res = await fetch("https://portal.relay.cequens.com/api/mgmt/v1/segments/{id}", {
  method: "PUT",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <api_key>"
},
  body: JSON.stringify({
    "name": "Premium buyers",
    "description": "Updated description"
}),
});
const data = await res.json();
console.log(data);

Responses

200

Updated

{
  "_id": "...",
  "name": "Premium buyers"
}
404

Not found

{
  "error": "Segment not found"
}
POST /api/mgmt/v1/segments/:id/contacts Management host API key

Add contacts to segment

Adds an array of contact IDs to a static segment.

Parameters

NameInTypeDescription
id * path string Segment ObjectId 64f1...

Request body

{
  "contactIds": [
    "64f1...",
    "64f2..."
  ]
}

cURL

curl -X POST "https://portal.relay.cequens.com/api/mgmt/v1/segments/{id}/contacts" \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json" \
  -d '{"contactIds":["64f1...","64f2..."]}'

JavaScript

const res = await fetch("https://portal.relay.cequens.com/api/mgmt/v1/segments/{id}/contacts", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <api_key>"
},
  body: JSON.stringify({
    "contactIds": [
        "64f1...",
        "64f2..."
    ]
}),
});
const data = await res.json();
console.log(data);

Responses

200

Added

{
  "added": 2,
  "contactCount": 232
}
GET /api/mgmt/v1/segments/:id/contacts Management host API key

List contacts in segment

Returns paginated contacts belonging to the segment.

Parameters

NameInTypeDescription
id * path string Segment ObjectId 64f1...
page query integer Page number 1
limit query integer Items per page (max 200) 50

cURL

curl -X GET "https://portal.relay.cequens.com/api/mgmt/v1/segments/{id}/contacts" \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json"

JavaScript

const res = await fetch("https://portal.relay.cequens.com/api/mgmt/v1/segments/{id}/contacts", {
  method: "GET",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <api_key>"
},
});
const data = await res.json();
console.log(data);

Responses

200

Contacts page

{
  "contacts": [],
  "total": 230,
  "page": 1,
  "limit": 50
}
POST /api/mgmt/v1/segments/:id/populate Management host API key

Auto-populate from filter

Applies a filter descriptor (from AI suggestions) to add matching contacts. Supported filter.type values: engagementScore, tag, cdpAttribute, subscribed.

Parameters

NameInTypeDescription
id * path string Segment ObjectId 64f1...

Request body

{
  "filter": {
    "type": "engagementScore",
    "min": 70
  }
}

cURL

curl -X POST "https://portal.relay.cequens.com/api/mgmt/v1/segments/{id}/populate" \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json" \
  -d '{"filter":{"type":"engagementScore","min":70}}'

JavaScript

const res = await fetch("https://portal.relay.cequens.com/api/mgmt/v1/segments/{id}/populate", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <api_key>"
},
  body: JSON.stringify({
    "filter": {
        "type": "engagementScore",
        "min": 70
    }
}),
});
const data = await res.json();
console.log(data);

Responses

200

Contacts added

{
  "added": 145
}

Campaigns

Create, manage, and broadcast SMS campaigns to contact segments.

GET /api/mgmt/v1/campaigns Management host API key

List campaigns

Paginated campaign list with optional status filtering.

Parameters

NameInTypeDescription
status query string draft | queued | sending | completed | failed | paused sending
page query integer Page number 1
limit query integer Items per page (max 100) 20

cURL

curl -X GET "https://portal.relay.cequens.com/api/mgmt/v1/campaigns" \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json"

JavaScript

const res = await fetch("https://portal.relay.cequens.com/api/mgmt/v1/campaigns", {
  method: "GET",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <api_key>"
},
});
const data = await res.json();
console.log(data);

Responses

200

Campaigns page

{
  "campaigns": [
    {
      "_id": "...",
      "name": "Summer Sale",
      "status": "completed",
      "estimatedReach": 500
    }
  ],
  "total": 12,
  "page": 1
}
POST /api/mgmt/v1/campaigns Management host API key

Create a campaign

Creates a campaign in draft status. Call the launch endpoint when ready.

Request body

{
  "name": "Summer Sale",
  "senderId": "BRAND",
  "content": "Hi {name}, get 20% off today! Reply STOP to unsubscribe.",
  "targetSegments": [
    "seg-id"
  ],
  "schedule": {
    "type": "immediate"
  }
}

cURL

curl -X POST "https://portal.relay.cequens.com/api/mgmt/v1/campaigns" \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json" \
  -d '{"name":"Summer Sale","senderId":"BRAND","content":"Hi {name}, get 20% off today! Reply STOP to unsubscribe.","targetSegments":["seg-id"],"schedule":{"type":"immediate"}}'

JavaScript

const res = await fetch("https://portal.relay.cequens.com/api/mgmt/v1/campaigns", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <api_key>"
},
  body: JSON.stringify({
    "name": "Summer Sale",
    "senderId": "BRAND",
    "content": "Hi {name}, get 20% off today! Reply STOP to unsubscribe.",
    "targetSegments": [
        "seg-id"
    ],
    "schedule": {
        "type": "immediate"
    }
}),
});
const data = await res.json();
console.log(data);

Responses

201

Draft created

{
  "_id": "...",
  "name": "Summer Sale",
  "status": "draft"
}
GET /api/mgmt/v1/campaigns/:id Management host API key

Get a campaign

Returns a single campaign with enriched delivery metrics.

Parameters

NameInTypeDescription
id * path string Campaign ObjectId 64f1...

cURL

curl -X GET "https://portal.relay.cequens.com/api/mgmt/v1/campaigns/{id}" \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json"

JavaScript

const res = await fetch("https://portal.relay.cequens.com/api/mgmt/v1/campaigns/{id}", {
  method: "GET",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <api_key>"
},
});
const data = await res.json();
console.log(data);

Responses

200

Campaign

{
  "_id": "...",
  "name": "Summer Sale",
  "status": "completed",
  "liveMetrics": {
    "sent": 450,
    "delivered": 430,
    "failed": 20
  }
}
404

Not found

{
  "error": "Campaign not found"
}
PUT /api/mgmt/v1/campaigns/:id Management host API key

Update a campaign

Update name, content, schedule, or targeting of a draft or paused campaign.

Parameters

NameInTypeDescription
id * path string Campaign ObjectId 64f1...

Request body

{
  "name": "Summer Sale (Final)",
  "content": "Updated message here."
}

cURL

curl -X PUT "https://portal.relay.cequens.com/api/mgmt/v1/campaigns/{id}" \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json" \
  -d '{"name":"Summer Sale (Final)","content":"Updated message here."}'

JavaScript

const res = await fetch("https://portal.relay.cequens.com/api/mgmt/v1/campaigns/{id}", {
  method: "PUT",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <api_key>"
},
  body: JSON.stringify({
    "name": "Summer Sale (Final)",
    "content": "Updated message here."
}),
});
const data = await res.json();
console.log(data);

Responses

200

Updated

{
  "_id": "...",
  "name": "Summer Sale (Final)"
}
404

Not found

{
  "error": "Campaign not found"
}
POST /api/mgmt/v1/campaigns/:id/recipients/batch Management host API key

Upload CSV recipients

Uploads a batch of phone numbers as campaign recipients. Set replace: true to overwrite the existing list.

Parameters

NameInTypeDescription
id * path string Campaign ObjectId 64f1...

Request body

{
  "recipients": [
    {
      "phone": "+12025551234",
      "name": "Jane"
    },
    {
      "phone": "+12025559999"
    }
  ],
  "replace": false
}

cURL

curl -X POST "https://portal.relay.cequens.com/api/mgmt/v1/campaigns/{id}/recipients/batch" \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json" \
  -d '{"recipients":[{"phone":"+12025551234","name":"Jane"},{"phone":"+12025559999"}],"replace":false}'

JavaScript

const res = await fetch("https://portal.relay.cequens.com/api/mgmt/v1/campaigns/{id}/recipients/batch", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <api_key>"
},
  body: JSON.stringify({
    "recipients": [
        {
            "phone": "+12025551234",
            "name": "Jane"
        },
        {
            "phone": "+12025559999"
        }
    ],
    "replace": false
}),
});
const data = await res.json();
console.log(data);

Responses

200

Accepted

{
  "accepted": 2,
  "total": 102
}
POST /api/mgmt/v1/campaigns/:id/launch Management host API key

Launch a campaign

Publishes messages to all matching contacts via NATS. Checks credits and enforces trial restrictions.

Parameters

NameInTypeDescription
id * path string Campaign ObjectId 64f1...

cURL

curl -X POST "https://portal.relay.cequens.com/api/mgmt/v1/campaigns/{id}/launch" \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json"

JavaScript

const res = await fetch("https://portal.relay.cequens.com/api/mgmt/v1/campaigns/{id}/launch", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <api_key>"
},
});
const data = await res.json();
console.log(data);

Responses

200

Launched

{
  "message": "Campaign launched",
  "recipientCount": 450,
  "status": "sending"
}
402

Insufficient credits

{
  "error": "Insufficient credits"
}
403

Trial quota exhausted

{
  "error": "Trial quota exhausted. Please upgrade to a paid plan."
}
400

No contacts match

{
  "error": "No contacts match the target criteria"
}
POST /api/mgmt/v1/campaigns/:id/pause Management host API key

Pause a sending campaign

Pauses a campaign that is currently in sending status.

Parameters

NameInTypeDescription
id * path string Campaign ObjectId 64f1...

cURL

curl -X POST "https://portal.relay.cequens.com/api/mgmt/v1/campaigns/{id}/pause" \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json"

JavaScript

const res = await fetch("https://portal.relay.cequens.com/api/mgmt/v1/campaigns/{id}/pause", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <api_key>"
},
});
const data = await res.json();
console.log(data);

Responses

200

Paused

{
  "_id": "...",
  "status": "paused"
}
404

Not found or not sending

{
  "error": "Campaign not found or not sending"
}
GET /api/mgmt/v1/campaigns/:id/metrics Management host API key

Campaign delivery metrics

Returns live sent / delivered / failed counts from the shared message log.

Parameters

NameInTypeDescription
id * path string Campaign ObjectId 64f1...

cURL

curl -X GET "https://portal.relay.cequens.com/api/mgmt/v1/campaigns/{id}/metrics" \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json"

JavaScript

const res = await fetch("https://portal.relay.cequens.com/api/mgmt/v1/campaigns/{id}/metrics", {
  method: "GET",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <api_key>"
},
});
const data = await res.json();
console.log(data);

Responses

200

Metrics

{
  "_id": "...",
  "name": "Summer Sale",
  "liveMetrics": {
    "sent": 450,
    "delivered": 430,
    "failed": 20
  }
}

SMS

Send SMS via the gateway API key, and query, filter, or export the delivery log from the portal. The two sit on different hosts — check the base URL on each endpoint.

GET /api/mgmt/v1/senders Management host API key

Get available sender IDs

Returns all approved sender IDs for the organisation.

cURL

curl -X GET "https://portal.relay.cequens.com/api/mgmt/v1/senders" \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json"

JavaScript

const res = await fetch("https://portal.relay.cequens.com/api/mgmt/v1/senders", {
  method: "GET",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <api_key>"
},
});
const data = await res.json();
console.log(data);

Responses

200

Sender IDs

[
  "BRAND",
  "ALERTS",
  "PROMO"
]
POST /api/v1/sms/messages Messaging host API key

Send an SMS

Submits an outbound SMS through the gateway routing pipeline. Returns 202 Accepted when queued. Pass the API key as Authorization: Bearer <key> or X-API-Key: <key>. **Served by the SMS gateway host, not the portal host** (see the base URL in the examples below).

Request body

from — registered Sender ID. to — E.164 destination. campaignId — optional, links the log to a campaign.

{
  "from": "BRAND",
  "to": "+12025551234",
  "text": "Hello from Relay!",
  "campaignId": "64f1..."
}

cURL

curl -X POST "https://ops.relay.cequens.com/api/v1/sms/messages" \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json" \
  -d '{"from":"BRAND","to":"+12025551234","text":"Hello from Relay!","campaignId":"64f1..."}'

JavaScript

const res = await fetch("https://ops.relay.cequens.com/api/v1/sms/messages", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <api_key>"
},
  body: JSON.stringify({
    "from": "BRAND",
    "to": "+12025551234",
    "text": "Hello from Relay!",
    "campaignId": "64f1..."
}),
});
const data = await res.json();
console.log(data);

Responses

202

Message accepted

{
  "messageId": "uuid",
  "status": "queued",
  "clientId": "CPAAS-XXXX"
}
401

Missing or invalid API key

{
  "error": "Missing API key"
}
422

Rejected (blocklist / quota)

{
  "status": "rejected",
  "reason": "Destination on DND list"
}
400

Missing required fields

{
  "error": "from, to, and text are required"
}
POST /api/v1/sms/messages/bulk Messaging host API key

Bulk send SMS

Send to up to 1,000 recipients in one call. Use {variable} placeholders in text for per-recipient personalization. The entire request counts as 1 TPS unit. App limits are enforced automatically from the API key — no appId needed in the body. If quota covers only part of the batch, the first N are queued and the rest returned as quota_exhausted rejections. DND and content-filter checks happen downstream at dispatch time. **Served by the SMS gateway host, not the portal host** (see the base URL in the examples below).

Request body

text supports {variable} placeholders (case-insensitive replacement). Recipients without variables receive the raw template (broadcast mode). App quota is resolved from the API key automatically. Max 1,000 recipients per call.

{
  "from": "BRAND",
  "text": "Hi {name}, your order {orderId} is ready for pickup!",
  "recipients": [
    {
      "to": "+12025551001",
      "variables": {
        "name": "Alice",
        "orderId": "A-123"
      }
    },
    {
      "to": "+12025551002",
      "variables": {
        "name": "Bob",
        "orderId": "B-456"
      }
    },
    {
      "to": "+12025551003"
    }
  ],
  "campaignId": "64f1..."
}

cURL

curl -X POST "https://ops.relay.cequens.com/api/v1/sms/messages/bulk" \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json" \
  -d '{"from":"BRAND","text":"Hi {name}, your order {orderId} is ready for pickup!","recipients":[{"to":"+12025551001","variables":{"name":"Alice","orderId":"A-123"}},{"to":"+12025551002","variables":{"name":"Bob","orderId":"B-456"}},{"to":"+12025551003"}],"campaignId":"64f1..."}'

JavaScript

const res = await fetch("https://ops.relay.cequens.com/api/v1/sms/messages/bulk", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <api_key>"
},
  body: JSON.stringify({
    "from": "BRAND",
    "text": "Hi {name}, your order {orderId} is ready for pickup!",
    "recipients": [
        {
            "to": "+12025551001",
            "variables": {
                "name": "Alice",
                "orderId": "A-123"
            }
        },
        {
            "to": "+12025551002",
            "variables": {
                "name": "Bob",
                "orderId": "B-456"
            }
        },
        {
            "to": "+12025551003"
        }
    ],
    "campaignId": "64f1..."
}),
});
const data = await res.json();
console.log(data);

Responses

202

Queued (full or partial)

{
  "queued": 2,
  "rejected": 1,
  "messageIds": [
    {
      "to": "+12025551001",
      "messageId": "MSG-XXXXXXXXXXXX"
    },
    {
      "to": "+12025551002",
      "messageId": "MSG-YYYYYYYYYYYY"
    }
  ],
  "rejections": [
    {
      "to": "+12025551003",
      "reason": "quota_exhausted"
    }
  ]
}
400

Validation error

{
  "error": "recipients must be a non-empty array of up to 1000 items"
}
401

Missing or invalid API key

{
  "error": "Missing API key (Authorization: Bearer <key> or X-API-Key header)"
}
403

Sender not approved / app paused / IP blocked

{
  "error": "Sender ID \"BRAND\" is not in the approved senders list for this account"
}
429

TPS cap reached

{
  "error": "TPS cap of 50 msg/s reached"
}
GET /api/mgmt/v1/messages/stats Management host API key

Message statistics

Aggregate counts by status and average latency for the requested window.

Parameters

NameInTypeDescription
from query string ISO 8601 start date 2025-03-01
to query string ISO 8601 end date 2025-03-31
status query string Delivered | Failed | Pending | … Failed
appId query string Filter to a specific App 64f1...

cURL

curl -X GET "https://portal.relay.cequens.com/api/mgmt/v1/messages/stats" \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json"

JavaScript

const res = await fetch("https://portal.relay.cequens.com/api/mgmt/v1/messages/stats", {
  method: "GET",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <api_key>"
},
});
const data = await res.json();
console.log(data);

Responses

200

Stats

{
  "total": 12400,
  "byStatus": {
    "Delivered": 11800,
    "Failed": 420,
    "Pending": 180
  },
  "avgLatencyMs": 312
}
GET /api/mgmt/v1/messages Management host API key

List message logs

Paginated log of outbound messages for the last 90 days.

Parameters

NameInTypeDescription
page query integer Page number 1
limit query integer Items per page (max 200) 50
status query string Delivered | Failed | Pending | Rejected | Blocked Failed
from query string ISO 8601 start date 2025-03-01
to query string ISO 8601 end date 2025-03-31
search query string Search sender, destination, msgId, text BRAND
appId query string Filter to an App ObjectId 64f1...

cURL

curl -X GET "https://portal.relay.cequens.com/api/mgmt/v1/messages" \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json"

JavaScript

const res = await fetch("https://portal.relay.cequens.com/api/mgmt/v1/messages", {
  method: "GET",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <api_key>"
},
});
const data = await res.json();
console.log(data);

Responses

200

Logs

{
  "logs": [
    {
      "messageId": "uuid",
      "sender": "BRAND",
      "destination": "+1...",
      "status": "Delivered",
      "latencyMs": 320
    }
  ],
  "total": 12400,
  "page": 1,
  "pages": 248
}
GET /api/mgmt/v1/messages/export Management host API key

Export messages as CSV

Streams a CSV file (up to 10,000 rows) matching the filters. Sets Content-Disposition: attachment.

Parameters

NameInTypeDescription
status query string Filter by status Failed
from query string ISO 8601 start date 2025-03-01
to query string ISO 8601 end date 2025-03-31
appId query string Filter by App 64f1...

cURL

curl -X GET "https://portal.relay.cequens.com/api/mgmt/v1/messages/export" \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json"

JavaScript

const res = await fetch("https://portal.relay.cequens.com/api/mgmt/v1/messages/export", {
  method: "GET",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <api_key>"
},
});
const data = await res.json();
console.log(data);

Responses

200

CSV download

{
  "note": "Returns text/csv — use in a download link or curl -o export.csv"
}

WhatsApp

Send WhatsApp messages via your registered numbers. Supports template, text, and media (image / document / audio / video / sticker). Same API-key auth as SMS, and like SMS these routes run on the SMS gateway host, not the portal host.

POST /api/v1/whatsapp/messages Messaging host API key

Send a template

Submits an outbound template message through Meta Cloud API. The only message type allowed when the contact has no open 24h service window. **Served by the SMS gateway host, not the portal host** (see the base URL in the examples below). Pass API key as Authorization: Bearer <key> or X-API-Key: <key>. Idempotency-Key header (optional): identical key from the same account returns the previously-queued messageId without re-sending.

Request body

from — your registered WhatsApp number (E.164 or phoneNumberId). to — E.164 destination. template.name — Meta-approved template name. template.language — locale code (must be APPROVED for that template). template.variables — positional map matching {{1}}, {{2}}, … placeholders in the template body. For advanced cases pass template.components (Meta-shape) directly instead of variables.

{
  "from": "+31644102243",
  "to": "+12025551234",
  "type": "template",
  "template": {
    "name": "order_shipping",
    "language": "en",
    "variables": {
      "1": "Omar Rodrigues",
      "2": "omar@example.com"
    }
  }
}

cURL

curl -X POST "https://ops.relay.cequens.com/api/v1/whatsapp/messages" \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json" \
  -d '{"from":"+31644102243","to":"+12025551234","type":"template","template":{"name":"order_shipping","language":"en","variables":{"1":"Omar Rodrigues","2":"omar@example.com"}}}'

JavaScript

const res = await fetch("https://ops.relay.cequens.com/api/v1/whatsapp/messages", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <api_key>"
},
  body: JSON.stringify({
    "from": "+31644102243",
    "to": "+12025551234",
    "type": "template",
    "template": {
        "name": "order_shipping",
        "language": "en",
        "variables": {
            "1": "Omar Rodrigues",
            "2": "omar@example.com"
        }
    }
}),
});
const data = await res.json();
console.log(data);

Responses

202

Queued for delivery

{
  "messageId": "wa_lxky9z_a1b2c3d4",
  "status": "queued"
}
200

Duplicate idempotency key

{
  "messageId": "wa_lxky9z_a1b2c3d4",
  "status": "sent",
  "deduplicated": true
}
400

Missing or invalid fields

{
  "error": "`from`, `to`, and `type` are required"
}
401

Missing or invalid API key

{
  "error": "Missing API key (Authorization: Bearer <key> or X-API-Key header)"
}
404

Sender not found for this account

{
  "error": "Sender +31644102243 not found for this account"
}
409

Sender not Registered on Cloud API

{
  "error": "Sender is not Registered (state: Pending)"
}
422

Template / suppression failure

{
  "error": "Template 'order_shipping' (en) is not APPROVED (status: PENDING)"
}
POST /api/v1/whatsapp/messages Messaging host API key

Send a text reply

Free-form text. Only allowed when the recipient has messaged you within the last 24 hours (open service window). Outside the window, use a template send.

Request body

text.body — message text (max 4096 chars). text.preview_url — set true to render link previews. replyToWamid — optional: thread this message as a reply to a previous inbound.

{
  "from": "+31644102243",
  "to": "+12025551234",
  "type": "text",
  "text": {
    "body": "Thanks for reaching out — your order will ship today.",
    "preview_url": false
  },
  "replyToWamid": "wamid.HBgL…"
}

cURL

curl -X POST "https://ops.relay.cequens.com/api/v1/whatsapp/messages" \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json" \
  -d '{"from":"+31644102243","to":"+12025551234","type":"text","text":{"body":"Thanks for reaching out — your order will ship today.","preview_url":false},"replyToWamid":"wamid.HBgL…"}'

JavaScript

const res = await fetch("https://ops.relay.cequens.com/api/v1/whatsapp/messages", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <api_key>"
},
  body: JSON.stringify({
    "from": "+31644102243",
    "to": "+12025551234",
    "type": "text",
    "text": {
        "body": "Thanks for reaching out — your order will ship today.",
        "preview_url": false
    },
    "replyToWamid": "wamid.HBgL…"
}),
});
const data = await res.json();
console.log(data);

Responses

202

Queued

{
  "messageId": "wa_…",
  "status": "queued"
}
409

Service window closed

{
  "error": "No open 24h service window for this contact — use a template send instead",
  "code": "service_window_closed"
}
POST /api/v1/whatsapp/messages Messaging host API key

Send media

Send image, document, audio, video, or sticker. Same service-window rules as text. Provide either link (public HTTPS URL Meta fetches) or id (a Meta media id from a prior upload).

Request body

For document add filename. For audio and sticker, captions are not supported. Replace the image key with document / audio / video / sticker to match the chosen type.

{
  "from": "+31644102243",
  "to": "+12025551234",
  "type": "image",
  "image": {
    "link": "https://cdn.example.com/orders/abc.jpg",
    "caption": "Your order is on its way!"
  }
}

cURL

curl -X POST "https://ops.relay.cequens.com/api/v1/whatsapp/messages" \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json" \
  -d '{"from":"+31644102243","to":"+12025551234","type":"image","image":{"link":"https://cdn.example.com/orders/abc.jpg","caption":"Your order is on its way!"}}'

JavaScript

const res = await fetch("https://ops.relay.cequens.com/api/v1/whatsapp/messages", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <api_key>"
},
  body: JSON.stringify({
    "from": "+31644102243",
    "to": "+12025551234",
    "type": "image",
    "image": {
        "link": "https://cdn.example.com/orders/abc.jpg",
        "caption": "Your order is on its way!"
    }
}),
});
const data = await res.json();
console.log(data);

Responses

202

Queued

{
  "messageId": "wa_…",
  "status": "queued"
}
400

Media reference missing

{
  "error": "image requires either `image.link` (public HTTPS URL) or `image.id` (Meta media id)"
}
GET /api/v1/whatsapp/messages/:messageId Messaging host API key

Get message status

Returns the current status and Meta wamid for a previously-submitted message. **Served by the SMS gateway host, not the portal host** (see the base URL in the examples below).

Parameters

NameInTypeDescription
messageId * path string The messageId returned by POST /api/v1/whatsapp/messages wa_lxky9z_a1b2c3d4

cURL

curl -X GET "https://ops.relay.cequens.com/api/v1/whatsapp/messages/{messageId}" \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json"

JavaScript

const res = await fetch("https://ops.relay.cequens.com/api/v1/whatsapp/messages/{messageId}", {
  method: "GET",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <api_key>"
},
});
const data = await res.json();
console.log(data);

Responses

200

Current status

{
  "messageId": "wa_lxky9z_a1b2c3d4",
  "wamid": "wamid.HBgL…",
  "status": "delivered",
  "type": "template",
  "contactPhone": "+12025551234",
  "phoneNumberId": "1136187512911204",
  "sentAt": "2026-05-21T10:00:00Z",
  "deliveredAt": "2026-05-21T10:00:02Z"
}
404

Not found

{
  "error": "Message not found"
}

Apps

Logical usage containers — each app has its own TPS cap, daily/monthly limits, and assigned credentials.

GET /api/mgmt/v1/apps Management host API key

List apps

Returns all apps for the organisation.

cURL

curl -X GET "https://portal.relay.cequens.com/api/mgmt/v1/apps" \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json"

JavaScript

const res = await fetch("https://portal.relay.cequens.com/api/mgmt/v1/apps", {
  method: "GET",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <api_key>"
},
});
const data = await res.json();
console.log(data);

Responses

200

Apps

[
  {
    "_id": "...",
    "name": "My App",
    "type": "standard",
    "status": "active",
    "tpsCap": 10
  }
]
POST /api/mgmt/v1/apps Management host API key

Create an app

Creates a new app. type can be standard or bulk. Requires admin role.

Request body

{
  "name": "My Marketing App",
  "type": "standard",
  "tpsCap": 20,
  "dailyMessageLimit": 10000,
  "senders": [
    "BRAND"
  ]
}

cURL

curl -X POST "https://portal.relay.cequens.com/api/mgmt/v1/apps" \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json" \
  -d '{"name":"My Marketing App","type":"standard","tpsCap":20,"dailyMessageLimit":10000,"senders":["BRAND"]}'

JavaScript

const res = await fetch("https://portal.relay.cequens.com/api/mgmt/v1/apps", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <api_key>"
},
  body: JSON.stringify({
    "name": "My Marketing App",
    "type": "standard",
    "tpsCap": 20,
    "dailyMessageLimit": 10000,
    "senders": [
        "BRAND"
    ]
}),
});
const data = await res.json();
console.log(data);

Responses

201

Created

{
  "_id": "...",
  "name": "My Marketing App",
  "status": "active"
}
GET /api/mgmt/v1/apps/:id Management host API key

Get an app

Returns a single app by ID.

Parameters

NameInTypeDescription
id * path string App ObjectId 64f1...

cURL

curl -X GET "https://portal.relay.cequens.com/api/mgmt/v1/apps/{id}" \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json"

JavaScript

const res = await fetch("https://portal.relay.cequens.com/api/mgmt/v1/apps/{id}", {
  method: "GET",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <api_key>"
},
});
const data = await res.json();
console.log(data);

Responses

200

App

{
  "_id": "...",
  "name": "My App",
  "status": "active",
  "tpsCap": 10
}
PUT /api/mgmt/v1/apps/:id Management host API key

Update an app

Updates app configuration. The isDefault flag cannot be changed via this endpoint.

Parameters

NameInTypeDescription
id * path string App ObjectId 64f1...

Request body

{
  "tpsCap": 30,
  "dailyMessageLimit": 20000
}

cURL

curl -X PUT "https://portal.relay.cequens.com/api/mgmt/v1/apps/{id}" \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json" \
  -d '{"tpsCap":30,"dailyMessageLimit":20000}'

JavaScript

const res = await fetch("https://portal.relay.cequens.com/api/mgmt/v1/apps/{id}", {
  method: "PUT",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <api_key>"
},
  body: JSON.stringify({
    "tpsCap": 30,
    "dailyMessageLimit": 20000
}),
});
const data = await res.json();
console.log(data);

Responses

200

Updated

{
  "_id": "...",
  "tpsCap": 30
}
PATCH /api/mgmt/v1/apps/:id/status Management host API key

Toggle app status

Activates or pauses the app. Paused apps will have their messages rejected at routing time.

Parameters

NameInTypeDescription
id * path string App ObjectId 64f1...

Request body

{
  "status": "paused"
}

cURL

curl -X PATCH "https://portal.relay.cequens.com/api/mgmt/v1/apps/{id}/status" \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json" \
  -d '{"status":"paused"}'

JavaScript

const res = await fetch("https://portal.relay.cequens.com/api/mgmt/v1/apps/{id}/status", {
  method: "PATCH",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <api_key>"
},
  body: JSON.stringify({
    "status": "paused"
}),
});
const data = await res.json();
console.log(data);

Responses

200

Updated

{
  "_id": "...",
  "status": "paused"
}
GET /api/mgmt/v1/apps/:id/stats Management host API key

App usage stats

Returns time-series message counts for the app.

Parameters

NameInTypeDescription
id * path string App ObjectId 64f1...
from query string ISO 8601 start 2025-03-01
to query string ISO 8601 end 2025-03-31

cURL

curl -X GET "https://portal.relay.cequens.com/api/mgmt/v1/apps/{id}/stats" \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json"

JavaScript

const res = await fetch("https://portal.relay.cequens.com/api/mgmt/v1/apps/{id}/stats", {
  method: "GET",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <api_key>"
},
});
const data = await res.json();
console.log(data);

Responses

200

Stats

[
  {
    "date": "2025-03-01",
    "sent": 420,
    "delivered": 400
  }
]

Billing

Subscription management, credit purchases, and invoice history.

GET /api/mgmt/v1/billing/subscription Management host API key

Get subscription

Returns current plan, credit balance, and billing cycle.

cURL

curl -X GET "https://portal.relay.cequens.com/api/mgmt/v1/billing/subscription" \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json"

JavaScript

const res = await fetch("https://portal.relay.cequens.com/api/mgmt/v1/billing/subscription", {
  method: "GET",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <api_key>"
},
});
const data = await res.json();
console.log(data);

Responses

200

Subscription

{
  "plan": "pro",
  "status": "active",
  "credits": {
    "total": 5000,
    "used": 1200,
    "resetDate": "2025-04-01"
  },
  "monthlyPrice": 49
}
GET /api/mgmt/v1/billing/package Management host API key

Plan, wallet & usage details

Returns the assigned plan, included quotas, prepaid wallet balance, and current-month usage counts.

cURL

curl -X GET "https://portal.relay.cequens.com/api/mgmt/v1/billing/package" \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json"

JavaScript

const res = await fetch("https://portal.relay.cequens.com/api/mgmt/v1/billing/package", {
  method: "GET",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <api_key>"
},
});
const data = await res.json();
console.log(data);

Responses

200

Plan & wallet info

{
  "currency": "SAR",
  "monthlyUsed": 8400,
  "partsUsed": 9100,
  "plan": {
    "name": "Growth",
    "isFree": false,
    "includedContacts": 30000,
    "includedSeats": 8,
    "includedAiConversations": 1500,
    "includedWaNumbers": 3
  },
  "wallet": {
    "balance": 420.5,
    "formatted": "ر.س420.50"
  },
  "smsPriceMinor": 14,
  "monthStart": "2026-06-01T00:00:00Z"
}
GET /api/mgmt/v1/billing/plans Management host API key

Available plans

Returns all self-service plans that can be subscribed to.

cURL

curl -X GET "https://portal.relay.cequens.com/api/mgmt/v1/billing/plans" \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json"

JavaScript

const res = await fetch("https://portal.relay.cequens.com/api/mgmt/v1/billing/plans", {
  method: "GET",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <api_key>"
},
});
const data = await res.json();
console.log(data);

Responses

200

Plans

[
  {
    "id": "...",
    "name": "Starter",
    "price": 19,
    "credits": 2000,
    "tps": 5,
    "currency": "USD"
  }
]
GET /api/mgmt/v1/billing/invoices Management host API key

List invoices

Invoice history sorted by most recent.

Parameters

NameInTypeDescription
limit query integer Max results (default 20, max 100) 20

cURL

curl -X GET "https://portal.relay.cequens.com/api/mgmt/v1/billing/invoices" \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json"

JavaScript

const res = await fetch("https://portal.relay.cequens.com/api/mgmt/v1/billing/invoices", {
  method: "GET",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <api_key>"
},
});
const data = await res.json();
console.log(data);

Responses

200

Invoices

[
  {
    "invoiceNumber": "INV-123",
    "amount": 49,
    "currency": "USD",
    "status": "paid",
    "paidAt": "2025-03-01T00:00:00Z"
  }
]