# GET Legacy usage compatibility
Source: https://docs.msgflash.com/api-reference/billing/legacy-usage
Legacy alias for public usage under `/api/v1/usage`.
## Endpoint
```txt theme={null}
GET /api/v1/usage
```
## Success response `200`
The response has the same format as `GET /api/v1/billing/usage`.
## Workspace rules
Like `/api/v1/billing/usage`, this endpoint requires a **personal API key** (`msgf_live_…`). A **team API key** is rejected with `403` and code [`TEAM_KEY_NOT_ALLOWED`](/resources/error-codes#teams-workspaces).
# GET Payment history
Source: https://docs.msgflash.com/api-reference/billing/payments
Paginated list of account payments.
## Endpoint
```txt theme={null}
GET /api/v1/billing/payments
```
## Parameters query
| Nom | Type | Required | Description |
| ------- | ------- | -------- | ---------------------- |
| `page` | integer | no | Défaut `1` |
| `limit` | integer | no | Défaut `20`, max `100` |
## Success response `200`
```json theme={null}
{
"data": {
"payments": [
{
"id": "pay_uuid",
"provider": "dodo",
"planCode": "pro",
"planName": "Pro",
"amount": 900,
"currency": "EUR",
"status": "succeeded",
"periodStart": "2026-04-01T09:43:13.737Z",
"periodEnd": "2026-05-01T09:43:13.737Z",
"createdAt": "2026-04-01T09:43:13.745Z"
}
],
"total": 1,
"page": 1,
"totalPages": 1
}
}
```
## Workspace rules
This endpoint returns the **personal payment history** of the key owner. It requires a **personal API key** (`msgf_live_…`). A **team API key** is rejected with `403` and code [`TEAM_KEY_NOT_ALLOWED`](/resources/error-codes#teams-workspaces). Team payment history is only available to the team owner from the console under `GET /api/billing/payments` with `X-Team-Id`.
# GET List plans
Source: https://docs.msgflash.com/api-reference/billing/plans
Return the catalog of active MsgFlash plans (EUR).
## Endpoint
```txt theme={null}
GET /api/v1/billing/plans
```
## Success response `200`
Returns active plans: **free**, **pro**, **max**. Prices are in **EUR** (`priceEur`). FCFA is not exposed on this endpoint.
```json theme={null}
{
"data": [
{
"code": "free",
"name": "Free",
"priceEur": 0,
"limits": {
"maxInstances": 1,
"maxApiKeys": 1,
"maxWebhookEndpoints": 3,
"monthlyOutboundQuota": 500,
"monthlyApiRequestQuota": 500
},
"features": {
"campaigns": true,
"statuses": false,
"voiceNotes": true,
"webhooks": true,
"numberLookups": true
}
},
{
"code": "pro",
"name": "Pro",
"priceEur": 19,
"limits": {
"maxInstances": 5,
"maxApiKeys": 5,
"maxWebhookEndpoints": 15,
"monthlyOutboundQuota": 50000,
"monthlyApiRequestQuota": 150000
},
"features": {
"campaigns": true,
"statuses": false,
"voiceNotes": true,
"webhooks": true,
"numberLookups": true
}
},
{
"code": "max",
"name": "MAX",
"priceEur": 39,
"limits": {
"maxInstances": 20,
"maxApiKeys": 10,
"maxWebhookEndpoints": 50,
"monthlyOutboundQuota": 150000,
"monthlyApiRequestQuota": 500000
},
"features": {
"campaigns": true,
"statuses": false,
"voiceNotes": true,
"webhooks": true,
"numberLookups": true
}
}
]
}
```
# GET Current subscription
Source: https://docs.msgflash.com/api-reference/billing/subscription
Return the current subscription, portal usage, and current period.
## Endpoint
```txt theme={null}
GET /api/v1/billing/subscription
```
## Success response `200`
```json theme={null}
{
"data": {
"subscription": {
"plan": {
"code": "free",
"limits": {
"maxInstances": 1
},
"features": {
"campaigns": false
}
},
"scheduledPlan": null,
"scheduledPlanAt": null,
"scheduledAction": null
},
"usage": {
"messagesCount": 0,
"statusesCount": 0,
"effectiveOutboundUsage": 0,
"apiRequestsCount": 0,
"activeInstancesCount": 1,
"activeApiKeysCount": 1
},
"period": {
"start": "2026-04-01T00:00:00.000Z",
"end": "2026-04-30T23:59:59.999Z"
}
}
}
```
The Free plan now exposes `maxApiKeys: 1` under `subscription.plan.limits`.
## Workspace rules
This endpoint reflects the **personal account** of the key owner. It requires a **personal API key** (`msgf_live_…`). A **team API key** is rejected with `403` and code [`TEAM_KEY_NOT_ALLOWED`](/resources/error-codes#teams-workspaces). For team-aware billing data in the console, use `GET /api/billing/subscription` with `Authorization: Bearer ` and the optional `X-Team-Id` header. See [Team context](/api-reference/teams/team-context).
# GET Extended billing usage
Source: https://docs.msgflash.com/api-reference/billing/usage
Return plan, limits, features, usage, and current period.
## Endpoint
```txt theme={null}
GET /api/v1/billing/usage
```
## Success response `200`
```json theme={null}
{
"data": {
"plan": {
"code": "pro",
"name": "Pro"
},
"limits": {
"maxInstances": 5,
"maxApiKeys": 10,
"maxWebhookEndpoints": 10,
"monthlyOutboundQuota": 5000,
"monthlyApiRequestQuota": 50000
},
"features": {
"campaigns": true,
"statuses": true,
"voiceNotes": true,
"webhooks": true
},
"usage": {
"messagesCount": 1240,
"statusesCount": 85,
"effectiveOutboundUsage": 1325,
"apiRequestsCount": 4500,
"activeInstancesCount": 3,
"activeApiKeysCount": 2
},
"period": {
"start": "2026-04-01T00:00:00.000Z",
"end": "2026-04-30T23:59:59.999Z"
}
}
}
```
## Workspace rules
This endpoint reflects the **personal account** of the key owner. It requires a **personal API key** (`msgf_live_…`). A **team API key** is rejected with `403` and code [`TEAM_KEY_NOT_ALLOWED`](/resources/error-codes#teams-workspaces). See [Team context](/api-reference/teams/team-context).
# POST Create a campaign
Source: https://docs.msgflash.com/api-reference/campaigns/create
Create and schedule a messaging campaign.
## Endpoint
```txt theme={null}
POST /api/v1/campaigns
```
## Important — how to get `instanceId`
`instanceId` is required to create a campaign.
To get it:
1. call `GET /api/v1/instances`
2. choose an instance with `status = "connected"` when possible
3. copy `data[n].id`
4. send this value in `instanceId`
Do not use these instead:
* `name`
* `waNumber`
## Body
| Field | Type | Required | Description |
| ------------- | -------- | -------------------- | ----------------------------------------------------------------------------------- |
| `instanceId` | UUID | yes | Technical identifier of the MsgFlash instance, obtained via `GET /api/v1/instances` |
| `name` | string | yes | Nom interne |
| `schedule` | ISO 8601 | yes | Date de démarrage |
| `repeat` | string | no | `noe`, `daily`, `weekly` |
| `recipients` | object | yes | Recipient selector |
| `templateId` | UUID | yes si mode template | Template used |
| `variables` | object | no | Variables `custom.*` globales |
| `type` | enum | yes si mode direct | `text`, `image`, `video`, `audio`, `document`, `voice_note`, `buttons` |
| `body` | string | depends on type | Obligatoire pour `text`, optionnel pour certains medias |
| `mediaUrl` | string | depends on type | Obligatoire pour les types media |
| `title` | string | if `buttons` | Button message title |
| `description` | string | if `buttons` | Button message description |
| `footer` | string | no | Button message footer |
| `buttons` | array | if `buttons` | List of buttons (max 2) |
## Content rule
A campaign must have exactly one content mode:
* soit `templateId`
* soit `type` + contenu direct
Ne pas envoyer les deux en même temps.
### `recipients`
| Field | Type | Required | Description |
| --------- | --------- | --------------- | ---------------------------------- |
| `type` | enum | yes | `all`, `tags`, `explicit`, `group` |
| `value` | string\[] | depends on type | Tags ou IDs de contacts |
| `groupId` | UUID | if `type=group` | Target group |
## Success response `201`
```json theme={null}
{
"data": {
"id": "cmp_uuid",
"userId": "user_uuid",
"instanceId": "inst_uuid",
"name": "April Newsletter",
"status": "scheduled",
"templateId": "tmpl_uuid",
"type": null,
"body": null,
"mediaUrl": null,
"schedule": "2026-04-15T09:00:00.000Z",
"repeat": "noe",
"recipients": {
"type": "all"
},
"stats": {
"planned": 0,
"queued": 0,
"sent": 0,
"delivered": 0,
"read": 0,
"failed": 0,
"cancelled": 0
},
"safety": {
"decision": "warn",
"riskLevel": "medium",
"score": 56,
"state": "warming",
"reasons": [
"This instance is still warming up and campaign pacing should stay gradual."
],
"recommendations": [
"Start with previously engaged contacts before scaling volume."
],
"appliedLimits": {
"maxCampaignRecipients": 100,
"maxColdRatio": 0.4
},
"audience": {
"totalRecipients": 120,
"coldRatio": 0.625
}
},
"createdAt": "2026-04-10T09:00:00.000Z",
"updatedAt": "2026-04-10T09:00:00.000Z"
}
}
```
In V1, `safety.decision` is `allow` or `warn`. A warning does not prevent campaign creation.
## Common errors
| HTTP | Code | When |
| ----- | --------------------------------- | --------------------------------------------------------------- |
| `400` | `VALIDATION_ERROR` | Empty campaign, missing body, missing media, invalid recipients |
| `403` | `CAMPAIGNS_NOT_AVAILABLE_ON_PLAN` | Plan trop faible |
| `403` | `UNSUPPORTED_FEATURE` | Unsupported feature (buttons without WhatsApp Business) |
| `404` | `NOT_FOUND` | Instance ou template not found |
## Examples
### Simple text campaign
```bash theme={null}
curl -X POST https://srv.msgflash.com/api/v1/campaigns \
-H "x-api-key: msgf_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"instanceId": "INSTANCE_UUID",
"name": "April Newsletter",
"schedule": "2026-04-15T09:00:00.000Z",
"recipients": {
"type": "all"
},
"type": "text",
"body": "Discover our latest offers!"
}'
```
### Campaign with buttons
```bash theme={null}
curl -X POST https://srv.msgflash.com/api/v1/campaigns \
-H "x-api-key: msgf_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"instanceId": "INSTANCE_UUID",
"name": "Satisfaction survey",
"schedule": "2026-04-15T10:00:00.000Z",
"recipients": {
"type": "tags",
"value": ["client"]
},
"type": "buttons",
"title": "Your opinion matters!",
"description": "Are you satisfied with our services?",
"footer": "Reply before tomorrow",
"buttons": [
{
"type": "reply",
"displayText": "Very satisfied",
"id": "satisfied"
},
{
"type": "reply",
"displayText": "Needs improvement",
"id": "needs_improvement"
}
]
}'
```
# GET Retrieve a campaign
Source: https://docs.msgflash.com/api-reference/campaigns/get
Retrieve campaign details.
## Endpoint
```txt theme={null}
GET /api/v1/campaigns/{id}
```
## Parameters path
| Nom | Type | Required | Description |
| ---- | ---- | -------- | ----------- |
| `id` | UUID | yes | Campaign ID |
## Success response `200`
```json theme={null}
{
"data": {
"id": "cmp_uuid",
"userId": "user_uuid",
"name": "Promo Black Friday",
"instanceId": "inst_uuid",
"templateId": null,
"type": "image",
"body": "Découvrez notre offre",
"mediaUrl": "https://cdn.example.com/promo.jpg",
"schedule": "2026-04-02T10:00:00.000Z",
"repeat": "noe",
"status": "running",
"recipients": { "type": "all" },
"templateVariables": null,
"stats": {
"planned": 500,
"queued": 120,
"sent": 350,
"delivered": 330,
"read": 210,
"failed": 30,
"cancelled": 0,
"processingStartedAt": "2026-04-02T10:00:01.000Z",
"lastEnqueuedAt": "2026-04-02T10:03:20.000Z",
"completedAt": null,
"cancelledAt": null
},
"createdAt": "2026-04-01T17:00:00.000Z",
"updatedAt": "2026-04-02T10:03:20.000Z"
}
}
```
## Lecture du contenu
* if `templateId` is present: the campaign is in template mode
* otherwise if `type` is present: the campaign is in direct message mode
* `body` et `mediaUrl` peuvent alors être affichés dans le détail
## Common errors
| HTTP | Code | When |
| ----- | ----------- | ------------------ |
| `404` | `NOT_FOUND` | Campaign not found |
# POST Pause a campaign
Source: https://docs.msgflash.com/api-reference/campaigns/pause
Pause a campaign when its status allows it.
## Endpoint
```txt theme={null}
POST /api/v1/campaigns/{id}/pause
```
## Parameters path
| Nom | Type | Required | Description |
| ---- | ---- | -------- | ----------- |
| `id` | UUID | yes | Campaign ID |
Pas de body.
## Success response `200`
```json theme={null}
{
"data": {
"id": "cmp_uuid",
"userId": "user_uuid",
"instanceId": "inst_uuid",
"name": "Promo Black Friday",
"templateId": null,
"type": "text",
"body": "Hello, découvrez notre promo.",
"mediaUrl": null,
"schedule": "2026-04-02T10:00:00.000Z",
"repeat": "noe",
"status": "paused",
"recipients": { "type": "all" },
"templateVariables": null,
"stats": {
"planned": 500,
"queued": 120,
"sent": 350,
"delivered": 330,
"read": 210,
"failed": 30,
"cancelled": 0
},
"createdAt": "2026-04-01T17:00:00.000Z",
"updatedAt": "2026-04-02T10:05:00.000Z"
}
}
```
## Common errors
| HTTP | Code | When |
| ----- | ------------- | --------------------------------- |
| `400` | `BAD_REQUEST` | Status incompatible avec la pause |
| `404` | `NOT_FOUND` | Campaign not found |
# POST Resume a campaign
Source: https://docs.msgflash.com/api-reference/campaigns/resume
Resume a paused campaign.
## Endpoint
```txt theme={null}
POST /api/v1/campaigns/{id}/resume
```
## Parameters path
| Nom | Type | Required | Description |
| ---- | ---- | -------- | ----------- |
| `id` | UUID | yes | Campaign ID |
## Success response `200`
```json theme={null}
{
"data": {
"id": "cmp_uuid",
"userId": "user_uuid",
"instanceId": "inst_uuid",
"name": "Promo Black Friday",
"templateId": null,
"type": "text",
"body": "Hello, découvrez notre promo.",
"mediaUrl": null,
"schedule": "2026-04-02T10:00:00.000Z",
"repeat": "noe",
"status": "running",
"recipients": { "type": "all" },
"templateVariables": null,
"safety": {
"decision": "warn",
"riskLevel": "medium",
"state": "warming",
"score": 56,
"reasons": [
"This instance is still warming up and campaign pacing should stay gradual."
],
"recommendations": [
"Start with previously engaged contacts before scaling volume."
]
},
"createdAt": "2026-04-01T17:00:00.000Z",
"updatedAt": "2026-04-02T10:06:00.000Z"
}
}
```
Le resume peut retourner de la guidance safety. En V1, elle n'empêche pas la reprise.
# GET Campaign statistics
Source: https://docs.msgflash.com/api-reference/campaigns/stats
Retrieve campaign statistics and execution timeline.
## Endpoint
```txt theme={null}
GET /api/v1/campaigns/{id}/stats
```
## Parameters path
| Nom | Type | Required | Description |
| ---- | ---- | -------- | ----------- |
| `id` | UUID | yes | Campaign ID |
## Success response `200`
```json theme={null}
{
"data": {
"campaignId": "cmp_uuid",
"status": "running",
"stats": {
"total": 1000,
"planned": 1000,
"queued": 150,
"sent": 600,
"delivered": 580,
"read": 420,
"failed": 10,
"cancelled": 5
},
"progressPercent": 85,
"timeline": {
"scheduledFor": "2026-04-02T10:00:00.000Z",
"processingStartedAt": "2026-04-02T10:00:01.000Z",
"lastEnqueuedAt": "2026-04-02T10:03:20.000Z",
"completedAt": null,
"cancelledAt": null,
"lastActivityAt": "2026-04-02T10:04:12.000Z"
}
}
}
```
# POST Add members
Source: https://docs.msgflash.com/api-reference/contact-groups/add-members
Add multiple contacts to a group.
## Endpoint
```txt theme={null}
POST /api/v1/contacts/groups/{groupId}/members
```
## Parameters path
| Nom | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `groupId` | UUID | yes | Group ID |
## Body
| Field | Type | Required | Description |
| ------------ | ------- | -------- | ----------------------------- |
| `contactIds` | UUID\[] | yes | 1 to 10000 contact IDs to add |
## Success response `200`
```json theme={null}
{
"data": {
"added": 2,
"alreadyInGroup": 0,
"notFound": 0,
"total": 2
}
}
```
## Async response `202`
When `contactIds.length >= 100`, the operation runs in background.
```json theme={null}
{
"data": {
"mode": "async",
"jobId": "job_abc123",
"status": "pending",
"operation": "add_to_group",
"requestedCount": 4900,
"groupId": "uuid-group",
"progress": 0,
"message": "Bulk operation queued. Check status via GET /api/contacts/bulk-jobs/job_abc123"
}
}
```
## Notes
* `< 100` contacts: synchronous response
* `>= 100` contacts: asynchronous response with HTTP `202`
* Poll `GET /api/v1/contacts/bulk-jobs/{jobId}/progress` every `2s`
# GET List groups for a contact
Source: https://docs.msgflash.com/api-reference/contact-groups/contact-groups
List the groups a contact belongs to.
## Endpoint
```txt theme={null}
GET /api/v1/contacts/{id}/groups
```
## Parameters path
| Nom | Type | Required | Description |
| ---- | ---- | -------- | ------------- |
| `id` | UUID | yes | ID du contact |
## Success response `200`
```json theme={null}
{
"data": {
"groups": [
{
"id": "grp_uuid",
"name": "Clients VIP",
"color": "#F59E0B"
}
]
}
}
```
# POST Create a group
Source: https://docs.msgflash.com/api-reference/contact-groups/create
Create a contact group.
## Endpoint
```txt theme={null}
POST /api/v1/contacts/groups
```
## Body
| Field | Type | Required | Description |
| ------------- | ------ | -------- | ------------------ |
| `name` | string | yes | Group name |
| `description` | string | no | Description |
| `color` | string | no | Color hexadécimale |
## Success response `201`
```json theme={null}
{
"data": {
"id": "grp_uuid",
"name": "Clients VIP",
"description": "Clients à forte valeur",
"color": "#F59E0B",
"contactCount": 0
}
}
```
# DELETE Delete a group
Source: https://docs.msgflash.com/api-reference/contact-groups/delete
Delete a contact group.
## Endpoint
```txt theme={null}
DELETE /api/v1/contacts/groups/{groupId}
```
## Success response `200`
```json theme={null}
{
"data": {
"deleted": true
}
}
```
# GET Retrieve a group
Source: https://docs.msgflash.com/api-reference/contact-groups/get
Retrieve contact group details.
## Endpoint
```txt theme={null}
GET /api/v1/contacts/groups/{groupId}
```
## Parameters path
| Nom | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `groupId` | UUID | yes | Group ID |
## Success response `200`
```json theme={null}
{
"data": {
"id": "grp_uuid",
"name": "Clients VIP",
"contactCount": 12
}
}
```
# GET List groups
Source: https://docs.msgflash.com/api-reference/contact-groups/list
List your account contact groups with cursor pagination and optional search.
## Endpoint
```txt theme={null}
GET /api/v1/contacts/groups
```
## Query parameters
| Field | Type | Required | Description |
| -------- | ------- | -------- | --------------------------------------------------------------- |
| `limit` | integer | no | Page size, default `50`, max `100` |
| `cursor` | string | no | Opaque cursor returned by the previous page |
| `search` | string | no | Case-insensitive match on `name` or `description` (1-100 chars) |
## Success response `200`
```json theme={null}
{
"data": {
"groups": [
{
"id": "grp_uuid",
"name": "VIPs",
"description": "High-priority contacts",
"color": "#facc15",
"contactCount": 42,
"createdAt": "2026-04-01T10:00:00.000Z"
}
],
"total": 7,
"limit": 50,
"nextCursor": null,
"hasMore": false
}
}
```
## Notes
* Pass the `nextCursor` value back as `cursor` to fetch the next page. Stop when `hasMore` is `false` or `nextCursor` is `null`.
# GET List group members
Source: https://docs.msgflash.com/api-reference/contact-groups/list-members
List group members with pagination and search.
## Endpoint
```txt theme={null}
GET /api/v1/contacts/groups/{groupId}/members
```
## Parameters
| Name | Type | Required | Location | Description |
| --------- | ------- | -------- | -------- | ---------------------------------------------------------------- |
| `groupId` | UUID | yes | path | Group ID |
| `limit` | integer | no | query | Page size, default `50`, max `100` |
| `cursor` | string | no | query | Opaque cursor returned by the previous page |
| `search` | string | no | query | Case-insensitive match on member `name` or `phone` (1-100 chars) |
## Success response `200`
```json theme={null}
{
"data": {
"contacts": [
{
"id": "cnt_uuid",
"name": "Awa Doe",
"phone": "+33612345000",
"tags": ["vip"],
"addedAt": "2026-04-01T10:00:00.000Z"
}
],
"limit": 50,
"total": 42,
"nextCursor": "MjAyNi0wNC0wMVQxMDowMDowMC4wMDBafGNudF91dWlk",
"hasMore": true
}
}
```
## Notes
* Pass the `nextCursor` value back as `cursor` to fetch the next page. Stop when `hasMore` is `false` or `nextCursor` is `null`.
* Each member object includes `addedAt` (when the contact joined this group).
# DELETE Remove members
Source: https://docs.msgflash.com/api-reference/contact-groups/remove-members
Remove multiple contacts from a group.
## Endpoint
```txt theme={null}
DELETE /api/v1/contacts/groups/{groupId}/members
```
## Parameters path
| Nom | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `groupId` | UUID | yes | Group ID |
## Body
| Field | Type | Required | Description |
| ------------ | ------- | -------- | -------------------------------- |
| `contactIds` | UUID\[] | yes | 1 to 10000 contact IDs to remove |
## Success response `200`
```json theme={null}
{
"data": {
"removed": 1,
"notInGroup": 1
}
}
```
## Async response `202`
When `contactIds.length >= 100`, the operation runs in background.
```json theme={null}
{
"data": {
"mode": "async",
"jobId": "job_def456",
"status": "pending",
"operation": "remove_from_group",
"requestedCount": 280,
"groupId": "uuid-group",
"progress": 0,
"message": "Bulk operation queued. Check status via GET /api/contacts/bulk-jobs/job_def456"
}
}
```
## Notes
* `< 100` contacts: synchronous response
* `>= 100` contacts: asynchronous response with HTTP `202`
* Poll `GET /api/v1/contacts/bulk-jobs/{jobId}/progress` every `2s`
# PUT Update a group
Source: https://docs.msgflash.com/api-reference/contact-groups/update
Update a contact group.
## Endpoint
```txt theme={null}
PUT /api/v1/contacts/groups/{groupId}
```
## Parameters path
| Nom | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `groupId` | UUID | yes | Group ID |
## Body
| Field | Type | Required | Description |
| ------------- | ------ | -------- | -------------------- |
| `name` | string | no | Nouveau nom |
| `description` | string | no | Nouvelle description |
| `color` | string | no | Nouvelle couleur |
## Success response `200`
```json theme={null}
{
"data": {
"id": "grp_uuid",
"name": "Clients VIP"
}
}
```
# POST Bulk delete contacts
Source: https://docs.msgflash.com/api-reference/contacts/bulk-delete
Delete multiple contacts in a single request.
## Endpoint
```txt theme={null}
POST /api/v1/contacts/bulk-delete
```
## Body
| Field | Type | Required | Description |
| ------------ | ------- | -------- | -------------- |
| `contactIds` | UUID\[] | yes | 1 to 10000 IDs |
## Success response `200`
```json theme={null}
{
"data": {
"requested": 2,
"deleted": 1,
"notFound": ["cnt_missing"]
}
}
```
## Async response `202`
When `contactIds.length >= 100`, the operation runs in background.
```json theme={null}
{
"data": {
"mode": "async",
"jobId": "job_abc123",
"status": "pending",
"operation": "bulk_delete_contacts",
"requestedCount": 900,
"progress": 0,
"message": "Bulk operation queued. Check status via GET /api/contacts/bulk-jobs/job_abc123"
}
}
```
## Notes
* `< 100` contacts: synchronous response
* `>= 100` contacts: asynchronous response with HTTP `202`
* Poll `GET /api/v1/contacts/bulk-jobs/{jobId}/progress` every `2s`
# POST Cancel bulk contact job
Source: https://docs.msgflash.com/api-reference/contacts/bulk-jobs-cancel
Cancel a pending or processing asynchronous bulk contact job.
## Endpoint
```txt theme={null}
POST /api/v1/contacts/bulk-jobs/{jobId}/cancel
```
## Parameters
| Field | Type | Required | Location | Description |
| ------- | ---- | -------: | -------- | ------------------ |
| `jobId` | UUID | yes | path | ID of the bulk job |
## Success response `200`
```json theme={null}
{
"data": {
"id": "job_abc123",
"operation": "add_to_group",
"status": "cancelled",
"requestedCount": 4900,
"processedCount": 2550,
"progress": 52,
"groupId": "uuid-group",
"summary": {
"added": 2431,
"alreadyInGroup": 91,
"notFound": 28
},
"completedAt": "2026-05-08T10:00:09.000Z"
}
}
```
# GET Bulk contact job
Source: https://docs.msgflash.com/api-reference/contacts/bulk-jobs-get
Return details for one asynchronous bulk contact job.
## Endpoint
```txt theme={null}
GET /api/v1/contacts/bulk-jobs/{jobId}
```
## Parameters
| Field | Type | Required | Location | Description |
| ------- | ---- | -------: | -------- | ------------------ |
| `jobId` | UUID | yes | path | ID of the bulk job |
## Success response `200`
```json theme={null}
{
"data": {
"id": "job_abc123",
"operation": "add_to_group",
"status": "done",
"requestedCount": 4900,
"processedCount": 4900,
"progress": 100,
"groupId": "uuid-group",
"summary": {
"added": 4721,
"alreadyInGroup": 131,
"notFound": 48
},
"error": null,
"createdAt": "2026-05-08T10:00:00.000Z",
"updatedAt": "2026-05-08T10:00:12.000Z",
"completedAt": "2026-05-08T10:00:12.000Z"
}
}
```
# GET List bulk contact jobs
Source: https://docs.msgflash.com/api-reference/contacts/bulk-jobs-list
List recent asynchronous bulk jobs triggered from contact bulk operations.
## Endpoint
```txt theme={null}
GET /api/v1/contacts/bulk-jobs
```
## Query parameters
| Field | Type | Required | Description |
| -------- | ------- | -------- | ---------------------------------- |
| `limit` | integer | no | Page size, default `20`, max `100` |
| `cursor` | UUID | no | Cursor for pagination |
## Success response `200`
```json theme={null}
{
"data": {
"jobs": [
{
"id": "job_abc123",
"operation": "add_to_group",
"status": "processing",
"requestedCount": 4900,
"processedCount": 2300,
"progress": 47,
"groupId": "uuid-group",
"summary": {
"added": 2190,
"alreadyInGroup": 80,
"notFound": 30
},
"createdAt": "2026-05-08T10:00:00.000Z",
"updatedAt": "2026-05-08T10:00:08.000Z",
"completedAt": null
}
],
"nextCursor": null,
"hasMore": false
}
}
```
# GET Bulk contact job progress
Source: https://docs.msgflash.com/api-reference/contacts/bulk-jobs-progress
Return the current progress and partial summary for an asynchronous bulk contact job.
## Endpoint
```txt theme={null}
GET /api/v1/contacts/bulk-jobs/{jobId}/progress
```
## Parameters
| Field | Type | Required | Location | Description |
| ------- | ---- | -------: | -------- | ------------------ |
| `jobId` | UUID | yes | path | ID of the bulk job |
## Success response `200`
```json theme={null}
{
"data": {
"id": "job_abc123",
"operation": "add_to_group",
"status": "processing",
"requestedCount": 4900,
"processedCount": 2300,
"progress": 47,
"groupId": "uuid-group",
"summary": {
"added": 2190,
"alreadyInGroup": 80,
"notFound": 30
},
"error": null,
"createdAt": "2026-05-08T10:00:00.000Z",
"updatedAt": "2026-05-08T10:00:08.000Z",
"completedAt": null
}
}
```
## Notes
* Use this endpoint to poll every `2s`
* Stop polling when `status` becomes `done`, `failed`, or `cancelled`
# POST Create a contact
Source: https://docs.msgflash.com/api-reference/contacts/create
Create a contact in your MsgFlash directory.
## Endpoint
```txt theme={null}
POST /api/v1/contacts
```
## Body
| Field | Type | Required | Description |
| ------- | --------- | -------- | ------------ |
| `name` | string | yes | Nom complet |
| `phone` | string | yes | E.164 number |
| `tags` | string\[] | no | Tags |
| `meta` | object | no | Métadonnées |
## Success response `201`
```json theme={null}
{
"data": {
"id": "cnt_uuid",
"name": "Awa Doe",
"phone": "+33612345000"
}
}
```
# DELETE Delete a contact
Source: https://docs.msgflash.com/api-reference/contacts/delete
Delete a contact from your account.
## Endpoint
```txt theme={null}
DELETE /api/v1/contacts/{id}
```
## Success response `200`
```json theme={null}
{
"data": {
"deleted": true
}
}
```
# GET Retrieve a contact
Source: https://docs.msgflash.com/api-reference/contacts/get
Retrieve contact details.
## Endpoint
```txt theme={null}
GET /api/v1/contacts/{id}
```
## Parameters path
| Nom | Type | Required | Description |
| ---- | ---- | -------- | ------------- |
| `id` | UUID | yes | ID du contact |
## Success response `200`
```json theme={null}
{
"data": {
"id": "cnt_uuid",
"name": "Awa Doe",
"phone": "+33612345000"
}
}
```
# GET List contacts
Source: https://docs.msgflash.com/api-reference/contacts/list
List your account contacts with cursor pagination and optional search.
## Endpoint
```txt theme={null}
GET /api/v1/contacts
```
## Query parameters
| Field | Type | Required | Description |
| -------- | ------- | -------- | --------------------------------------------------------- |
| `limit` | integer | no | Page size, default `50`, max `100` |
| `cursor` | string | no | Opaque cursor returned by the previous page |
| `search` | string | no | Case-insensitive match on `name` or `phone` (1-100 chars) |
| `sort` | string | no | `createdAt_desc` (default) or `name_asc` |
## Success response `200`
```json theme={null}
{
"data": {
"contacts": [
{
"id": "cnt_uuid",
"name": "Awa Doe",
"phone": "+33612345000",
"tags": ["vip"],
"temperature": null,
"createdAt": "2026-04-01T10:00:00.000Z",
"updatedAt": "2026-04-01T10:00:00.000Z"
}
],
"total": 240,
"limit": 50,
"sort": "createdAt_desc",
"nextCursor": "MjAyNi0wNC0wMVQxMDowMDowMC4wMDBafGNudF91dWlk",
"hasMore": true
}
}
```
## Notes
* Pass the `nextCursor` value back as `cursor` to fetch the next page. Stop when `hasMore` is `false` or `nextCursor` is `null`.
* The cursor encodes the sort tuple (`createdAt|id` or `name|id`). Do not mix sorts across pages.
# PUT Update a contact
Source: https://docs.msgflash.com/api-reference/contacts/update
Update an existing contact.
## Endpoint
```txt theme={null}
PUT /api/v1/contacts/{id}
```
## Parameters path
| Nom | Type | Required | Description |
| ---- | ---- | -------- | ------------- |
| `id` | UUID | yes | ID du contact |
## Body
| Field | Type | Required | Description |
| ------- | --------- | -------- | ------------------------ |
| `name` | string | no | Nouveau nom |
| `phone` | string | no | Nouveau téléphone |
| `tags` | string\[] | no | Remplace les tags |
| `meta` | object | no | Remplace les métadonnées |
## Success response `200`
```json theme={null}
{
"data": {
"id": "cnt_uuid",
"name": "Awa Doe"
}
}
```
# GET Retrieve an instance
Source: https://docs.msgflash.com/api-reference/instances/get
Retrieve instance details by ID.
## Endpoint
```txt theme={null}
GET /api/v1/instances/{id}
```
## Parameters path
| Nom | Type | Required | Description |
| ---- | ---- | -------- | ---------------- |
| `id` | UUID | yes | ID de l'instance |
## Request example
```bash theme={null}
curl https://srv.msgflash.com/api/v1/instances/INSTANCE_ID \
-H "x-api-key: msgf_live_your_api_key_here"
```
## Success response `200`
```json theme={null}
{
"data": {
"id": "inst_uuid",
"name": "boutique-principale",
"waNumber": "+33612345678",
"status": "connected",
"webhook": null,
"meta": null,
"deletedAt": null,
"createdAt": "2026-04-01T09:00:00.000Z",
"updatedAt": "2026-04-01T09:10:00.000Z"
}
}
```
## Common errors
| HTTP | Code | When |
| ----- | -------------- | -------------------------------------------------- |
| `401` | `UNAUTHORIZED` | API key missing or invalid |
| `404` | `NOT_FOUND` | Instance does not exist or is outside your account |
# GET Instance health
Source: https://docs.msgflash.com/api-reference/instances/health
Return an instance warmup and safety state to guide message sends.
## Endpoint
```txt theme={null}
GET /api/v1/instances/{id}/health
```
## Parameters path
| Nom | Type | Required | Description |
| ---- | ---- | -------- | ---------------- |
| `id` | UUID | yes | ID de l'instance |
## Success response `200`
```json theme={null}
{
"data": {
"instanceId": "inst_uuid",
"safetyState": "warming",
"safetyScore": 56,
"firstConnectedAt": "2026-04-10T08:00:00.000Z",
"warmupPolicy": {
"state": "warming",
"instanceAgeDays": 1.4,
"hourlyOutboundCap": 25,
"dailyOutboundCap": 100,
"hourlyUniqueRecipientsCap": 25,
"dailyUniqueRecipientsCap": 100,
"maxCampaignRecipients": 100,
"maxColdRatio": 0.4
},
"usageWindowSummary": {
"outbound1h": 4,
"outbound24h": 18,
"uniqueRecipients1h": 4,
"uniqueRecipients24h": 16,
"inboundReplies24h": 2,
"inboundReplies7d": 3
},
"recommendations": [
"Start with previously engaged contacts before scaling volume."
]
}
}
```
## États possibles
| État | Description |
| ------------ | ------------------------------------- |
| `new` | Instance très récente, warmup initial |
| `warming` | Warmup en cours |
| `stable` | Instance plus mature |
| `at_risk` | Signaux récents plus risqués |
| `restricted` | Risque élevé détecté |
Cette V1 retourne uniquement de la guidance. Elle ne bloque pas directement les envois.
## Common errors
| HTTP | Code | When |
| ----- | ----------- | -------------------------------------------------- |
| `404` | `NOT_FOUND` | Instance does not exist or is outside your account |
# GET List instances
Source: https://docs.msgflash.com/api-reference/instances/list
List all active WhatsApp instances for your account.
## Endpoint
```txt theme={null}
GET /api/v1/instances
```
## Authentication
`x-api-key: `
## Parameters
No path, query, or body parameters.
## Request example
```bash theme={null}
curl https://srv.msgflash.com/api/v1/instances \
-H "x-api-key: msgf_live_your_api_key_here"
```
## Success response `200`
```json theme={null}
{
"data": [
{
"id": "inst_uuid",
"name": "boutique-principale",
"waNumber": "+33612345678",
"status": "connected",
"webhook": null,
"meta": null,
"deletedAt": null,
"createdAt": "2026-04-01T09:00:00.000Z",
"updatedAt": "2026-04-01T09:10:00.000Z"
}
]
}
```
## How to get the correct `instanceId`
For all MsgFlash sending endpoints, you must provide `instanceId`.
Field differences:
| Field | Usage |
| ---------- | -------------------------------------------------------- |
| `id` | Technical identifier to send as `instanceId` in the API |
| `name` | Human-readable name for UI usage |
| `waNumber` | Connected WhatsApp number, useful for display and checks |
Recommended flow:
1. Call `GET /api/v1/instances`.
2. Choose an instance with `status = "connected"`.
3. Copy `data[n].id`.
4. Use this value as `instanceId` in:
* `POST /api/v1/messages/send`
* `POST /api/v1/messages/schedule`
* `POST /api/v1/campaigns`
## Common errors
| HTTP | Code | When |
| ----- | ------------------------- | ---------------------------- |
| `401` | `UNAUTHORIZED` | API key missing or invalid |
| `429` | `API_RATE_LIMIT_EXCEEDED` | Too many requests per second |
# GET Live instance state
Source: https://docs.msgflash.com/api-reference/instances/state
Query a live instance connection state from the provider.
## Endpoint
```txt theme={null}
GET /api/v1/instances/{id}/state
```
## Parameters path
| Nom | Type | Required | Description |
| ---- | ---- | -------- | ---------------- |
| `id` | UUID | yes | ID de l'instance |
## Request example
```bash theme={null}
curl https://srv.msgflash.com/api/v1/instances/INSTANCE_ID/state \
-H "x-api-key: msgf_live_your_api_key_here"
```
## Success response `200`
```json theme={null}
{
"data": {
"instanceId": "inst_uuid",
"status": "connected",
"providerState": "open"
}
}
```
## Common errors
| HTTP | Code | When |
| ----- | ----------- | -------------------------------------------------- |
| `404` | `NOT_FOUND` | Instance does not exist or is outside your account |
# API reference
Source: https://docs.msgflash.com/api-reference/introduction
Base URL, authentication, rate limiting, response format, and complete MsgFlash public API surface.
## Base URL
```txt theme={null}
https://srv.msgflash.com
```
All public endpoints are prefixed with `/api/v1/`.
Examples:
```txt theme={null}
https://srv.msgflash.com/api/v1/messages/send
https://srv.msgflash.com/api/v1/templates
https://srv.msgflash.com/api/v1/contacts
https://srv.msgflash.com/api/v1/billing/usage
```
***
## Authentication
All requests to `/api/v1/*` require a MsgFlash API key in the `x-api-key` header.
```bash theme={null}
curl https://srv.msgflash.com/api/v1/usage \
-H "x-api-key: msgf_live_your_api_key_here"
```
See [Authentication](/authentication) for exact header format and security requirements.
### Team workspace (`msgf_team_…`)
Team API keys require **`X-Team-Id`** matching the key’s team; workspace **`/api/v1/*`** routes then use the team’s shared data and the team owner’s plan. Personal keys must not send `X-Team-Id` (`TEAM_KEY_REQUIRED`). Account, compact usage, and most billing reads require a personal key (`TEAM_KEY_NOT_ALLOWED` with a team key). Full rules: [Team context](/api-reference/teams/team-context).
***
## Rate limiting
| Limit | Value |
| ------------------- | ------------------------- |
| Requests per second | 10 req/s |
| Scope | Per API key |
| Error code | `API_RATE_LIMIT_EXCEEDED` |
If the limit is exceeded:
```json theme={null}
{
"error": {
"code": "API_RATE_LIMIT_EXCEEDED",
"message": "Too many API requests"
}
}
```
The real-time rate limit is separate from the monthly `monthlyApiRequestQuota`. You can stay under your monthly quota and still receive `429` if you exceed 10 req/s.
***
## Response format
### Success
All successful responses are wrapped in `data`.
```json theme={null}
{
"data": {
"id": "uuid",
"status": "queued"
}
}
```
For simple lists:
```json theme={null}
{
"data": [
{ "id": "..." },
{ "id": "..." }
]
}
```
For paginated lists:
```json theme={null}
{
"data": {
"messages": [],
"nextCursor": null,
"hasMore": false
}
}
```
### Error
```json theme={null}
{
"error": {
"code": "NOT_FOUND",
"message": "Message not found"
}
}
```
See [Error codes](/resources/error-codes) for the main codes.
***
## Available endpoints
### Messages
| Method | Endpoint | Description |
| ------ | --------------------------- | ----------------------- |
| `POST` | `/api/v1/messages/send` | Send an instant message |
| `POST` | `/api/v1/messages/schedule` | Schedule a message |
| `GET` | `/api/v1/messages` | List messages |
| `GET` | `/api/v1/messages/{id}` | Message details |
### Campaigns
| Method | Endpoint | Description |
| ------ | ------------------------------- | ------------------- |
| `POST` | `/api/v1/campaigns` | Create a campaign |
| `GET` | `/api/v1/campaigns/{id}` | Campaign details |
| `GET` | `/api/v1/campaigns/{id}/stats` | Delivery statistics |
| `POST` | `/api/v1/campaigns/{id}/pause` | Pause a campaign |
| `POST` | `/api/v1/campaigns/{id}/resume` | Resume a campaign |
### Templates
| Method | Endpoint | Description |
| -------- | -------------------------------- | ------------------------- |
| `GET` | `/api/v1/templates` | List templates |
| `POST` | `/api/v1/templates` | Create a template |
| `GET` | `/api/v1/templates/{id}` | Template details |
| `PUT` | `/api/v1/templates/{id}` | Update a template |
| `DELETE` | `/api/v1/templates/{id}` | Delete a template |
| `POST` | `/api/v1/templates/{id}/preview` | Preview a template render |
### Contacts
| Method | Endpoint | Description |
| -------- | --------------------------------------------- | ---------------------- |
| `GET` | `/api/v1/contacts` | List contacts |
| `POST` | `/api/v1/contacts` | Create a contact |
| `GET` | `/api/v1/contacts/{id}` | Contact details |
| `PUT` | `/api/v1/contacts/{id}` | Update a contact |
| `DELETE` | `/api/v1/contacts/{id}` | Delete a contact |
| `POST` | `/api/v1/contacts/bulk-delete` | Bulk delete contacts |
| `GET` | `/api/v1/contacts/bulk-jobs` | List bulk contact jobs |
| `GET` | `/api/v1/contacts/bulk-jobs/{jobId}` | Bulk job details |
| `GET` | `/api/v1/contacts/bulk-jobs/{jobId}/progress` | Bulk job progress |
| `POST` | `/api/v1/contacts/bulk-jobs/{jobId}/cancel` | Cancel a bulk job |
### Contact groups
| Method | Endpoint | Description |
| -------- | ------------------------------------------- | ------------------------- |
| `GET` | `/api/v1/contacts/groups` | List groups |
| `POST` | `/api/v1/contacts/groups` | Create a group |
| `GET` | `/api/v1/contacts/groups/{groupId}` | Group details |
| `PUT` | `/api/v1/contacts/groups/{groupId}` | Update a group |
| `DELETE` | `/api/v1/contacts/groups/{groupId}` | Delete a group |
| `POST` | `/api/v1/contacts/groups/{groupId}/members` | Add members |
| `DELETE` | `/api/v1/contacts/groups/{groupId}/members` | Remove members |
| `GET` | `/api/v1/contacts/groups/{groupId}/members` | List members |
| `GET` | `/api/v1/contacts/{id}/groups` | List groups for a contact |
### Webhooks
| Method | Endpoint | Description |
| -------- | ----------------------- | --------------------------- |
| `GET` | `/api/v1/webhooks` | List your webhook endpoints |
| `POST` | `/api/v1/webhooks` | Create a webhook endpoint |
| `DELETE` | `/api/v1/webhooks/{id}` | Delete a webhook endpoint |
### WhatsApp statuses
| Method | Endpoint | Description |
| ------ | ------------------ | ------------------------- |
| `POST` | `/api/v1/statuses` | Publish a WhatsApp status |
### Instances
| Method | Endpoint | Description |
| ------ | ------------------------------ | ------------------- |
| `GET` | `/api/v1/instances` | List instances |
| `GET` | `/api/v1/instances/{id}` | Instance details |
| `GET` | `/api/v1/instances/{id}/state` | Live instance state |
### Billing public read-only
| Method | Endpoint | Description |
| ------ | ------------------------------ | --------------------------------------- |
| `GET` | `/api/v1/usage` | Compact usage summary |
| `GET` | `/api/v1/billing/plans` | Plan catalog |
| `GET` | `/api/v1/billing/subscription` | Current subscription and billing period |
| `GET` | `/api/v1/billing/usage` | Usage, limits, and features |
| `GET` | `/api/v1/billing/payments` | Payment history |
# GET Retrieve a message
Source: https://docs.msgflash.com/api-reference/messages/get
Retrieve message details by message ID.
## Endpoint
```txt theme={null}
GET /api/v1/messages/{id}
```
## Parameters path
| Nom | Type | Required | Description |
| ---- | ---- | -------- | ------------- |
| `id` | UUID | yes | ID du message |
## Success response `200`
```json theme={null}
{
"data": {
"id": "msg_uuid",
"userId": "user_uuid",
"instanceId": "inst_uuid",
"contactId": null,
"campaignId": null,
"direction": "outbound",
"type": "text",
"from": null,
"to": "+33612345678",
"body": "Hello",
"mediaUrl": null,
"status": "delivered",
"error": null,
"meta": {
"providerResult": {},
"workerSafety": {
"decision": "allow",
"riskLevel": "low",
"state": "stable",
"score": 72
}
},
"providerMessageId": "BAE5D1A2B3C4D5E6",
"createdAt": "2026-04-01T10:00:00.000Z",
"updatedAt": "2026-04-01T10:01:10.000Z"
}
}
```
## Common errors
| HTTP | Code | When |
| ----- | ----------- | ----------------- |
| `404` | `NOT_FOUND` | Message not found |
# GET List messages
Source: https://docs.msgflash.com/api-reference/messages/list
List messages with cursor-based pagination.
## Endpoint
```txt theme={null}
GET /api/v1/messages
```
## Parameters query
| Nom | Type | Required | Description |
| ------------ | ------- | -------- | --------------------- |
| `limit` | integer | no | Taille de page |
| `cursor` | string | no | Curseur de pagination |
| `instanceId` | UUID | no | Filtre par instance |
| `status` | string | no | Filter by status |
## Request example
```bash theme={null}
curl "https://srv.msgflash.com/api/v1/messages?limit=20&status=delivered" \
-H "x-api-key: msgf_live_your_api_key_here"
```
## Success response `200`
```json theme={null}
{
"data": {
"messages": [
{
"id": "msg_uuid",
"userId": "user_uuid",
"instanceId": "inst_uuid",
"contactId": null,
"campaignId": null,
"direction": "outbound",
"type": "text",
"from": null,
"to": "+33612345678",
"body": "Hello",
"mediaUrl": null,
"status": "delivered",
"error": null,
"meta": {},
"providerMessageId": "BAE5D1A2B3C4D5E6",
"createdAt": "2026-04-01T10:00:00.000Z",
"updatedAt": "2026-04-01T10:01:10.000Z"
}
],
"nextCursor": null,
"hasMore": false
}
}
```
## Common errors
| HTTP | Code | When |
| ----- | -------------- | --------------- |
| `401` | `UNAUTHORIZED` | Invalid API key |
# POST Schedule a message
Source: https://docs.msgflash.com/api-reference/messages/schedule
Schedule a future message in direct mode or with a template.
## Endpoint
```txt theme={null}
POST /api/v1/messages/schedule
```
## Important — how to get `instanceId`
`instanceId` is the technical identifier required to schedule a message.
To get it:
1. call `GET /api/v1/instances`
2. choisir l’instance souhaitée
3. copy `data[n].id`
4. utiliser cette valeur dans `instanceId`
Ne pas envoyer :
* `name`
* `waNumber`
## Body
| Field | Type | Required | Description |
| ------------- | -------- | ---------------------- | --------------------------------------------------------------------------------------------- |
| `instanceId` | UUID | yes | Technical identifier of the MsgFlash instance, obtained via `GET /api/v1/instances` |
| `to` | string | yes | E.164 number |
| `scheduledAt` | ISO 8601 | yes | Date/heure d'envoi |
| `type` | enum | yes if no `templateId` | `text`, `image`, `video`, `audio`, `document`, `voice_note`, `location`, `contact`, `buttons` |
| `templateId` | UUID | yes if no `type` | Template to render |
| `variables` | object | no | `custom.*` values |
Les autres champs suivent les mêmes règles que `/messages/send`, y compris les champs spécifiques aux boutons (`title`, `description`, `footer`, `buttons`).
## Request example
```bash theme={null}
curl -X POST https://srv.msgflash.com/api/v1/messages/schedule \
-H "x-api-key: msgf_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"instanceId": "INSTANCE_UUID",
"to": "+33612345678",
"type": "text",
"text": "Hello dans 30 minutes",
"scheduledAt": "2026-04-02T08:00:00.000Z"
}'
```
## Example avec boutons
```bash theme={null}
curl -X POST https://srv.msgflash.com/api/v1/messages/schedule \
-H "x-api-key: msgf_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"instanceId": "INSTANCE_UUID",
"to": "+33612345678",
"type": "buttons",
"title": "Rappel programmé",
"description": "Votre rendez-vous est dans 30 minutes",
"scheduledAt": "2026-04-02T08:00:00.000Z",
"buttons": [
{
"type": "reply",
"displayText": "Confirmer",
"id": "confirm_appointment"
}
]
}'
```
## Success response `201`
```json theme={null}
{
"data": {
"id": "msg_uuid",
"userId": "user_uuid",
"instanceId": "inst_uuid",
"contactId": null,
"campaignId": null,
"direction": "outbound",
"type": "text",
"from": null,
"to": "+33612345678",
"body": "Hello dans 30 minutes",
"mediaUrl": null,
"status": "queued",
"error": null,
"providerMessageId": null,
"warnings": [
"This instance is still in the warmup phase."
],
"safety": {
"decision": "warn",
"riskLevel": "medium",
"state": "warming",
"score": 56
},
"createdAt": "2026-04-02T07:30:00.000Z",
"updatedAt": "2026-04-02T07:30:00.000Z"
}
}
```
Les warnings safety sont informatifs. En V1, ils ne bloquent pas les messages planifiés.
## Common errors
| HTTP | Code | When |
| ----- | --------------------------------- | ---------------------------------------- |
| `400` | `VALIDATION_ERROR` | Invalid body |
| `400` | `TEMPLATE_INVALID` | Template invalid |
| `404` | `NOT_FOUND` | Instance, contact, or template not found |
| `429` | `MONTHLY_OUTBOUND_QUOTA_EXCEEDED` | Quota already exhausted |
# POST Send a message
Source: https://docs.msgflash.com/api-reference/messages/send
Send an immediate message in direct mode or with a template.
## Endpoint
```txt theme={null}
POST /api/v1/messages/send
```
## Important — how to get `instanceId`
`instanceId` is required to send a message.
Do not use these instead:
* `name`
* `waNumber`
To get the correct `instanceId`:
1. call `GET /api/v1/instances`
2. select the target instance
3. copy `data[n].id`
4. send this value in the `instanceId` field
Example:
* `id` = technical identifier required by the API
* `name` = human-readable label
* `waNumber` = connected number shown to the user
## Body
| Field | Type | Required | Description |
| ----------------- | ------ | ---------------------- | --------------------------------------------------------------------------------------------- |
| `instanceId` | UUID | yes | Technical identifier of the MsgFlash instance, obtained via `GET /api/v1/instances` |
| `to` | string | yes | E.164 number |
| `type` | enum | yes if no `templateId` | `text`, `image`, `video`, `audio`, `document`, `voice_note`, `location`, `contact`, `buttons` |
| `templateId` | UUID | yes if no `type` | Template to render |
| `text` | string | depends on type | Body du message |
| `mediaUrl` | string | depends on type | Public media URL |
| `latitude` | number | if `location` | Latitude |
| `longitude` | number | if `location` | Longitude |
| `locationName` | string | no | Location name |
| `locationAddress` | string | no | Address |
| `contactId` | UUID | no | Shared contact or template context |
| `variables` | object | no | `custom.*` values |
| `title` | string | if `buttons` | Button message title |
| `description` | string | if `buttons` | Button message description |
| `footer` | string | no | Button message footer |
| `buttons` | array | if `buttons` | List of buttons (max 2) |
### Buttons
Each button must follow this structure:
```json theme={null}
{
"title": "reply", // reply | url | call | copy | pix
"displayText": "Button text",
"id": "button_id",
"url": "https://example.com", // for type url
"phoneNumber": "+5511999999999", // for type call
"copyCode": "dGV4dA==", // for type copy
"currency": "BRL", // for type pix
"name": "John Doe", // for type pix
"keyType": "random", // for type pix
"key": "0ea59ac5-f001-4f0e-9785-c7..." // for type pix
}
```
## Request example
```bash theme={null}
curl -X POST https://srv.msgflash.com/api/v1/messages/send \
-H "x-api-key: msgf_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"instanceId": "INSTANCE_UUID",
"to": "+33612345678",
"type": "text",
"text": "Hello"
}'
```
## Messages with buttons
Messages with buttons require a WhatsApp Business account. They let you add up to 2 interactive buttons.
### Supported button types
| Type | Description | Required fields |
| ------- | --------------------------- | --------------------------------------------------- |
| `reply` | Quick reply button | `displayText`, `id` |
| `copy` | Button to copy text | `displayText`, `copyCode` |
| `url` | Button that opens a URL | `displayText`, `url` |
| `call` | Button to call | `displayText`, `phoneNumber` |
| `pix` | Pix payment button (Brazil) | `displayText`, `currency`, `name`, `keyType`, `key` |
### Button message example
```bash theme={null}
curl -X POST https://srv.msgflash.com/api/v1/messages/send \
-H "x-api-key: msgf_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"instanceId": "INSTANCE_UUID",
"to": "+33612345678",
"type": "buttons",
"title": "Choose an option",
"description": "What would you like to do?",
"footer": "Reply quickly",
"buttons": [
{
"title": "reply",
"displayText": "View menu",
"id": "menu_option"
},
{
"title": "url",
"displayText": "Visit website",
"url": "https://example.com"
}
]
}'
```
## Success response `201`
```json theme={null}
{
"data": {
"id": "msg_uuid",
"instanceId": "inst_uuid",
"contactId": null,
"campaignId": null,
"type": "text",
"to": "+33612345678",
"body": "Hello",
"mediaUrl": null,
"status": "queued",
"warnings": [
"This instance is still in the warmup phase."
],
"safety": {
"decision": "warn",
"riskLevel": "medium",
"state": "warming",
"score": 56,
"reasons": [
"This instance is still in the warmup phase."
],
"recommendations": [
"Start with previously engaged contacts before scaling volume."
]
},
"error": null,
"providerMessageId": null,
"createdAt": "2026-04-01T10:00:00.000Z",
"updatedAt": "2026-04-01T10:00:00.000Z"
}
}
```
In V1, safety warnings do not block sending. They are informational only.
## Common errors
| HTTP | Code | When |
| ----- | --------------------------------- | ------------------------------------------------------- |
| `400` | `VALIDATION_ERROR` | Invalid body |
| `400` | `TEMPLATE_INVALID` | Template invalid |
| `400` | `TEMPLATE_VARIABLES_MISSING` | Required variables missing |
| `400` | `TEMPLATE_CONTEXT_UNAVAILABLE` | Invalid contact or instance context |
| `403` | `SUBSCRIPTION_INACTIVE` | Subscription not usable |
| `403` | `UNSUPPORTED_FEATURE` | Unsupported feature (buttons without WhatsApp Business) |
| `404` | `NOT_FOUND` | Instance, contact, or template not found |
| `429` | `MONTHLY_OUTBOUND_QUOTA_EXCEEDED` | Sending quota exhausted |
# POST Cancel a lookup
Source: https://docs.msgflash.com/api-reference/number-lookups/cancel
Cancel a pending or processing async number lookup.
## Endpoint
```txt theme={null}
POST /api/v1/number-lookups/{id}/cancel
```
## Parameters
| Field | Type | Required | Location | Description |
| ----- | ---- | -------: | -------- | ---------------- |
| `id` | UUID | yes | path | ID of the lookup |
## Success response `200`
```json theme={null}
{
"data": {
"id": "lookup_uuid",
"status": "cancelled",
"completedAt": "2026-05-07T10:00:08.000Z"
}
}
```
## Common errors
| Code | HTTP | When |
| ---------------------------- | ---- | -------------------------------------------- |
| `LOOKUP_NOT_FOUND` | 404 | Lookup not found |
| `LOOKUP_CANNOT_BE_CANCELLED` | 400 | Lookup is already done, failed, or cancelled |
# POST Create a number lookup
Source: https://docs.msgflash.com/api-reference/number-lookups/create
Check one or more WhatsApp numbers synchronously or asynchronously depending on volume.
## Endpoint
```txt theme={null}
POST /api/v1/number-lookups
```
## Body
| Field | Type | Required | Description |
| ------------ | --------- | -------: | ------------------------------------------ |
| `instanceId` | UUID | yes | ID de l’instance connectée à utiliser |
| `numbers` | string\[] | yes | 1 à 10 000 numéros au format international |
## Processing rules
* moins de `100` numéros : réponse synchrone
* `100` numéros ou plus : réponse asynchrone `202`
* les numéros valides sont traités côté provider par batchs de `20`
## Success response `200` ou `202`
```json theme={null}
{
"data": {
"mode": "sync",
"lookupId": "lookup_uuid",
"status": "done",
"requested": 2,
"normalized": 2,
"checked": 2,
"onWhatsAppCount": 1,
"notOnWhatsAppCount": 1,
"invalidCount": 0,
"result": {
"onWhatsApp": [],
"notOnWhatsApp": [],
"invalid": []
}
}
}
```
## Common errors
| Code | HTTP | When |
| ------------------ | ---- | --------------------------- |
| `VALIDATION_ERROR` | 400 | Invalid body |
| `NOT_FOUND` | 404 | Instance not found |
| `PROVIDER_ERROR` | 502 | Verification provider error |
# GET Lookup details
Source: https://docs.msgflash.com/api-reference/number-lookups/get
Return status, metrics, and results for a number lookup.
## Endpoint
```txt theme={null}
GET /api/v1/number-lookups/{id}
```
## Parameters
| Field | Type | Required | Location | Description |
| ----- | ---- | -------: | -------- | --------------------- |
| `id` | UUID | yes | path | ID de la vérification |
## Success response `200`
```json theme={null}
{
"data": {
"id": "lookup_uuid",
"status": "done",
"progress": 100,
"requestedCount": 1250,
"normalizedCount": 1180,
"checkedCount": 1180,
"onWhatsAppCount": 730,
"notOnWhatsAppCount": 450,
"invalidCount": 70,
"result": {
"onWhatsApp": [],
"notOnWhatsApp": [],
"invalid": []
},
"completedAt": "2026-04-07T10:00:00.000Z"
}
}
```
## Status values
* `pending`
* `processing`
* `done`
* `failed`
* `cancelled`
# POST Import matched numbers as contacts
Source: https://docs.msgflash.com/api-reference/number-lookups/import-contacts
Create or update MsgFlash contacts from numbers detected on WhatsApp.
## Endpoint
```txt theme={null}
POST /api/v1/number-lookups/{id}/import-contacts
```
## Parameters
| Field | Type | Required | Location | Description |
| --------- | ------ | -------: | -------- | ------------------------------------- |
| `id` | UUID | yes | path | ID de la vérification terminée |
| `groupId` | UUID | no | body | Target group |
| `tag` | string | no | body | Tag à appliquer aux contacts importés |
## Success response `200`
```json theme={null}
{
"data": {
"requested": 730,
"created": 650,
"updated": 80,
"skipped": 0
}
}
```
# GET List lookups
Source: https://docs.msgflash.com/api-reference/number-lookups/list
List number lookups already created on your account.
## Endpoint
```txt theme={null}
GET /api/v1/number-lookups
```
## Query params
| Parameter | Type | Required | Description |
| --------- | ------- | -------: | --------------------- |
| `limit` | integer | no | Taille de page |
| `cursor` | UUID | no | Curseur de pagination |
## Success response `200`
```json theme={null}
{
"data": {
"lookups": [],
"nextCursor": null,
"hasMore": false
}
}
```
# GET Lookup progress
Source: https://docs.msgflash.com/api-reference/number-lookups/progress
Return the current progress and intermediate counters for an async number lookup.
## Endpoint
```txt theme={null}
GET /api/v1/number-lookups/{id}/progress
```
## Parameters
| Field | Type | Required | Location | Description |
| ----- | ---- | -------: | -------- | ---------------- |
| `id` | UUID | yes | path | ID of the lookup |
## Success response `200`
```json theme={null}
{
"data": {
"id": "lookup_uuid",
"status": "processing",
"progress": 60,
"requestedCount": 400,
"normalizedCount": 392,
"checkedCount": 240,
"onWhatsAppCount": 151,
"notOnWhatsAppCount": 89,
"invalidCount": 8,
"error": null,
"createdAt": "2026-05-07T10:00:00.000Z",
"updatedAt": "2026-05-07T10:00:07.000Z",
"completedAt": null
}
}
```
## Notes
* Use this endpoint to poll every `2s` for async lookups.
* Stop polling when `status` becomes `done`, `failed`, or `cancelled`.
# Team context
Source: https://docs.msgflash.com/api-reference/teams/team-context
How X-Team-Id selects the team workspace for the public API (team keys) and the console API (JWT).
Team features use a **workspace** (personal or team). Integrations express that with **API key type** plus **`X-Team-Id`** where applicable.
## Public API (`/api/v1/*`, `x-api-key`)
### Team API keys (`msgf_team_…`)
* Created in the portal (**Team → API keys**). Owner or Admin only.
* Send **`X-Team-Id: `**; it **must** match the team bound to the key. If the header is missing or does not match, authentication fails (`401`, same message as an invalid key).
* Successful calls use the team’s shared workspace (contacts, templates, campaigns, etc.) and the **team owner’s** subscription for limits and billing.
### Personal API keys (`msgf_live_…`)
* Do **not** send `X-Team-Id` on workspace routes. If you send it, the API returns **`403`** with code **`TEAM_KEY_REQUIRED`** (use a team key for team workspace).
### Which `v1` routes use the team workspace?
Any **`/api/v1/*`** route that reads or writes workspace data resolves the workspace from a **team key** + **`X-Team-Id`**. That includes messages (send, schedule, bulk, history), campaigns, templates, contacts, groups, webhooks, outbound statuses, instances (list, get, state, health), number lookups, media, and related operations.
### Endpoints **not** available with team keys
These require a **personal** key (`403`, **`TEAM_KEY_NOT_ALLOWED`**):
| Method | Path |
| ------ | ------------------------------ |
| `GET` | `/api/v1/me` |
| `GET` | `/api/v1/usage` |
| `GET` | `/api/v1/billing/subscription` |
| `GET` | `/api/v1/billing/usage` |
| `GET` | `/api/v1/billing/payments` |
`GET /api/v1/billing/plans` (plan catalog) **is** allowed with a team key.
### Instance operations
Create, connect, logout, and delete instances still run as the **owning user** behind the workspace, with collaborator permission checks. List and read operations use the team workspace scope.
### Example
```bash theme={null}
curl -X POST https://srv.msgflash.com/api/v1/messages/send \
-H "x-api-key: msgf_team_your_team_key_here" \
-H "X-Team-Id: YOUR_TEAM_UUID" \
-H "Content-Type: application/json" \
-d '{"instanceId":"...","to":"+33612345678","type":"text","text":"Hello from team"}'
```
***
## Console API (`/api/*`, `Authorization: Bearer `)
Resource routes (JWT) accept an **optional** header:
* **`X-Team-Id: `** — operate in that team’s **shared workspace** (you must be an active member).
* **Omitted** — operate in your **personal** workspace (default).
Applies to routes such as **`/api/messages`**, **`/api/contacts`**, **`/api/templates`**, **`/api/campaigns`**, **`/api/instances`**, **`/api/webhooks`**, **`/api/statuses`**, **`/api/media`**, **`/api/number-lookups`**, and their sub-paths.
If `X-Team-Id` is not a team you belong to (or the team is gone), the API returns **`404`** with code **`TEAM_NOT_FOUND`**.
Team membership and billing under **`/api/teams`** are documented separately; they do not use this header to “switch workspace” for those admin endpoints.
# POST Create a template
Source: https://docs.msgflash.com/api-reference/templates/create
Create a text or media template. Variables are recalculated automatically.
## Endpoint
```txt theme={null}
POST /api/v1/templates
```
## Body
| Field | Type | Required | Description |
| ---------- | ------ | --------------------- | --------------------------------------------- |
| `name` | string | yes | Name of the template |
| `type` | enum | yes | `text`, `image`, `video`, `audio`, `document` |
| `body` | string | yes if `type=text` | Contenu avec placeholders |
| `mediaUrl` | string | yes si template media | Public media URL |
## Success response `201`
```json theme={null}
{
"data": {
"id": "tmpl_uuid",
"name": "Relance panier",
"type": "text",
"body": "Hello {{contact.firstName}}",
"mediaUrl": null,
"variables": ["contact.firstName"]
}
}
```
## Common errors
| HTTP | Code | When |
| ----- | ------------------ | ------------------- |
| `400` | `VALIDATION_ERROR` | Invalid body |
| `400` | `TEMPLATE_INVALID` | Placeholder invalid |
# DELETE Delete a template
Source: https://docs.msgflash.com/api-reference/templates/delete
Delete a template from your account.
## Endpoint
```txt theme={null}
DELETE /api/v1/templates/{id}
```
## Parameters path
| Nom | Type | Required | Description |
| ---- | ---- | -------- | -------------- |
| `id` | UUID | yes | ID du template |
## Success response `200`
```json theme={null}
{
"data": {
"deleted": true
}
}
```
# GET Retrieve a template
Source: https://docs.msgflash.com/api-reference/templates/get
Retrieve template details.
## Endpoint
```txt theme={null}
GET /api/v1/templates/{id}
```
## Parameters path
| Nom | Type | Required | Description |
| ---- | ---- | -------- | -------------- |
| `id` | UUID | yes | ID du template |
## Success response `200`
```json theme={null}
{
"data": {
"id": "tmpl_uuid",
"name": "Relance panier",
"type": "text",
"variables": ["contact.firstName", "custom.code"]
}
}
```
# GET List templates
Source: https://docs.msgflash.com/api-reference/templates/list
List account templates with simple pagination.
## Endpoint
```txt theme={null}
GET /api/v1/templates
```
## Parameters query
| Nom | Type | Required | Description |
| ------- | ------- | -------- | ---------------------- |
| `page` | integer | no | Défaut `1` |
| `limit` | integer | no | Défaut `20`, max `100` |
## Success response `200`
```json theme={null}
{
"data": {
"templates": [],
"total": 0
}
}
```
# POST Preview a template
Source: https://docs.msgflash.com/api-reference/templates/preview
Render a template with the provided context without sending a message.
## Endpoint
```txt theme={null}
POST /api/v1/templates/{id}/preview
```
## Parameters path
| Nom | Type | Required | Description |
| ---- | ---- | -------- | -------------- |
| `id` | UUID | yes | ID du template |
## Body
| Field | Type | Required | Description |
| ------------ | ------ | -------- | -------------------- |
| `instanceId` | UUID | no | `instance.*` context |
| `contactId` | UUID | no | `contact.*` context |
| `variables` | object | no | `custom.*` values |
## Success response `200`
```json theme={null}
{
"data": {
"rendered": "Hello Awa, utilisez PROMO10.",
"variables": ["contact.firstName", "custom.code"],
"missingVariables": [],
"valid": true
}
}
```
# PUT Update a template
Source: https://docs.msgflash.com/api-reference/templates/update
Update an existing template and recalculate its variables.
## Endpoint
```txt theme={null}
PUT /api/v1/templates/{id}
```
## Parameters path
| Nom | Type | Required | Description |
| ---- | ---- | -------- | -------------- |
| `id` | UUID | yes | ID du template |
## Body
| Field | Type | Required | Description |
| ---------- | ------ | -------- | ------------------ |
| `name` | string | no | Nouveau nom |
| `body` | string | no | Nouveau body |
| `mediaUrl` | string | no | Nouvelle URL media |
## Success response `200`
```json theme={null}
{
"data": {
"id": "tmpl_uuid",
"variables": ["contact.firstName"]
}
}
```
# POST Create a webhook
Source: https://docs.msgflash.com/api-reference/webhooks/create
Create a webhook endpoint and return its secret.
## Endpoint
```txt theme={null}
POST /api/v1/webhooks
```
## Body
| Field | Type | Required | Description |
| -------- | --------- | -------- | ----------------- |
| `url` | string | yes | URL de réception |
| `events` | string\[] | yes | 1 à 50 événements |
## Success response `201`
```json theme={null}
{
"data": {
"id": "wh_uuid",
"url": "https://example.com/webhooks/msgflash",
"secret": "generated_once_secret",
"events": ["message.delivered"],
"active": true
}
}
```
# DELETE Delete a webhook
Source: https://docs.msgflash.com/api-reference/webhooks/delete
Delete an existing webhook endpoint.
## Endpoint
```txt theme={null}
DELETE /api/v1/webhooks/{id}
```
## Parameters path
| Nom | Type | Required | Description |
| ---- | ---- | -------- | ------------- |
| `id` | UUID | yes | ID du webhook |
## Success response `200`
```json theme={null}
{
"data": {
"deleted": true
}
}
```
# GET List webhooks
Source: https://docs.msgflash.com/api-reference/webhooks/list
List your MsgFlash webhook endpoints.
## Endpoint
```txt theme={null}
GET /api/v1/webhooks
```
## Success response `200`
```json theme={null}
{
"data": [
{
"id": "wh_uuid",
"url": "https://example.com/webhooks/msgflash",
"events": ["message.delivered"],
"active": true,
"createdAt": "2026-04-01T10:00:00.000Z",
"updatedAt": "2026-04-01T10:00:00.000Z"
}
]
}
```
# Authentication
Source: https://docs.msgflash.com/authentication
How to authenticate requests for the dashboard and the public MsgFlash API.
## The two surfaces
| Surface | Method | Header | Usage |
| ---------- | ---------- | ----------------------------- | ------------------------------------------------------- |
| Dashboard | JWT Bearer | `Authorization: Bearer ` | Web interface and console `/api/*` endpoints |
| Public API | API key | `x-api-key: ` | `/api/v1/*` endpoints for server-to-server integrations |
For any server-to-server integration, use an API key.
The web console and the public API do not use the same authentication method.
***
## API key format
A MsgFlash API key looks like this:
```txt theme={null}
msgf_live_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2
```
Structure:
* prefix `msgf_live_`
* 64 lowercase hexadecimal characters
The full key is only shown once when it is created.
***
## Using your API key
```bash theme={null}
curl https://srv.msgflash.com/api/v1/messages/send \
-H "x-api-key: msgf_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"instanceId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"to": "+33612345000",
"type": "text",
"text": "Hello"
}'
```
Do not use `Authorization: Bearer ` for the public API. The backend explicitly expects the `x-api-key` header.
***
## Creating and revoking a key
1. Sign in to [app.msgflash.com](https://app.msgflash.com)
2. Open **API Keys**
3. Click **New API Key**
4. Give it a descriptive name
5. Copy the key immediately
To revoke a key:
1. Go back to **API Keys**
2. Click **Revoke**
3. Confirm
Revocation is immediate.
***
## Dashboard authentication
The main dashboard flows are:
* signup
* login
* Google OAuth
* email verification
* password reset
Detailed guide:
[See the dashboard auth guide](/guides/dashboard-auth)
***
## Number of keys per plan
| Plan | Allowed API keys |
| ------- | ---------------- |
| Free | 1 |
| Starter | 3 |
| Pro | 10 |
| Plus | 10 |
***
## Best practices
Use separate keys for production, staging, and development.
Store your key in an environment variable such as `MSGFLASH_API_KEY` or in a secrets manager.
***
## Authentication-related errors
| Code | HTTP | Description |
| ------------------------------------ | ---- | ------------------------------------------------------------------------- |
| `UNAUTHORIZED` | 401 | Missing, invalid, or revoked key |
| `FORBIDDEN` | 403 | The key is valid but cannot access the resource |
| `SUBSCRIPTION_INACTIVE` | 403 | The account no longer has an active subscription for the requested action |
| `API_RATE_LIMIT_EXCEEDED` | 429 | More than 10 requests/second on the same key |
| `MONTHLY_API_REQUEST_QUOTA_EXCEEDED` | 429 | Monthly API request quota exhausted |
Example:
```json theme={null}
{
"error": {
"code": "UNAUTHORIZED",
"message": "Invalid or revoked API key"
}
}
```
# Fundamentals
Source: https://docs.msgflash.com/fundamentals
Core concepts to understand MsgFlash before integrating the API.
## Why this page exists
Most integration issues are not coding issues.
They usually come from unclear terms:
* what is an instance?
* what is the difference between `instanceId`, `name`, and `waNumber`?
* what is an API key?
* what is a webhook?
* what is a payload?
This page is a compact glossary you can keep open while integrating.
***
## System overview
MsgFlash connects three layers:
* your MsgFlash account
* one or more connected WhatsApp numbers
* your technical integrations (backend, WordPress, Zapier, etc.)
Typical flow:
1. Create a WhatsApp instance
2. Connect a real WhatsApp number to it
3. Generate an API key
4. Call MsgFlash API from your backend
5. MsgFlash sends through the selected instance
***
## WhatsApp instance
A **WhatsApp instance** is a WhatsApp number connected to MsgFlash.
It is **not**:
* a user
* an API key
* a template
* a campaign
Think of it as an independent sending channel.
***
## `instanceId` vs `name` vs `waNumber`
| Field | Meaning |
| ------------ | -------------------------------------- |
| `instanceId` | technical identifier used in API calls |
| `name` | human-readable label in the dashboard |
| `waNumber` | connected WhatsApp number |
Example from `GET /api/v1/instances`:
```json theme={null}
{
"data": [
{
"id": "d6f66e76-9e59-41a5-bba8-a45c7fb72bfc",
"name": "Main Store",
"waNumber": "2250715516311",
"status": "connected"
}
]
}
```
When sending a message, always use `id` as `instanceId`.
***
## API key
An **API key** is the secret used to authenticate your requests.
Example:
```txt theme={null}
msgf_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```
Use it in request headers:
```txt theme={null}
x-api-key: msgf_live_...
```
Never expose it in public browser JavaScript or commit it to git.
***
## Webhook
A **webhook** is an endpoint in your system that MsgFlash calls automatically when events happen.
Example endpoint:
```txt theme={null}
https://my-app.com/webhooks/msgflash
```
This replaces polling loops for delivery/read/status events.
***
## Campaign vs scheduled message
* **Scheduled message**: one recipient, sent later
* **Campaign**: same message pattern to many recipients
Campaigns include an instance, audience, content, and timing rules.
***
## QR code
The QR code is used only to connect WhatsApp (like WhatsApp Web).
Flow:
1. Create instance
2. Scan QR code from phone
3. Instance becomes connected
It is not a sending method by itself.
***
## Quotas
A **quota** is a limit from your plan.
Examples:
* monthly messages
* monthly API requests
* max instances
* max API keys
Two quota families:
| Type | Example |
| --------------- | -------------------------------- |
| monthly quotas | messages, statuses, API requests |
| capacity limits | instances, API keys, webhooks |
***
## Payload
A **payload** is the JSON body sent to or returned by the API.
Example send payload:
```json theme={null}
{
"instanceId": "d6f66e76-9e59-41a5-bba8-a45c7fb72bfc",
"to": "+2250715516311",
"type": "text",
"text": "Hello"
}
```
Example webhook payload:
```json theme={null}
{
"event": "message.delivered",
"data": {
"messageId": "msg_uuid",
"status": "delivered"
}
}
```
***
## Quick glossary
* **Instance**: connected WhatsApp number
* **instanceId**: technical ID to send in API calls
* **API key**: secret used for auth
* **Webhook**: URL called by MsgFlash on events
* **Campaign**: one operation targeting many contacts
* **Scheduled message**: delayed single-recipient message
* **Quota**: allowed usage limits
* **Payload**: JSON body in requests/responses
***
## Go further
* [Quickstart](/quickstart)
* [Guide: Instances](/guides/instances)
* [Guide: Send message](/guides/send-message)
* [Guide: Webhooks](/guides/webhooks)
# Billing
Source: https://docs.msgflash.com/guides/billing
Read your current plan, usage, and billing history from the public API.
## Public billing endpoints
| Method | Endpoint |
| ------ | ------------------------------ |
| `GET` | `/api/v1/billing/plans` |
| `GET` | `/api/v1/billing/subscription` |
| `GET` | `/api/v1/billing/usage` |
| `GET` | `/api/v1/billing/payments` |
| `GET` | `/api/v1/usage` |
Public billing through an API key is read-only. Sensitive actions such as checkout, cancellation, or downgrade happen from the dashboard.
Billing reads (`/me`, `/usage`, `/billing/subscription`, `/billing/usage`, `/billing/payments`) require a **personal API key** (`msgf_live_…`). A **team API key** (`msgf_team_…`) is rejected with `403` `TEAM_KEY_NOT_ALLOWED`. For team-aware billing data, use the console API with `Authorization: Bearer ` and the optional `X-Team-Id` header — see [Team context](/api-reference/teams/team-context).
## Query parameters
All of these routes are `GET`.
| Endpoint | Query params | Body |
| ------------------------------ | --------------- | ---- |
| `/api/v1/billing/plans` | noe | noe |
| `/api/v1/billing/subscription` | noe | noe |
| `/api/v1/billing/usage` | noe | noe |
| `/api/v1/usage` | noe | noe |
| `/api/v1/billing/payments` | `page`, `limit` | noe |
***
## List plans
```bash theme={null}
curl https://srv.msgflash.com/api/v1/billing/plans \
-H "x-api-key: msgf_live_your_api_key_here"
```
### Success response
```json theme={null}
{
"data": [
{
"code": "pro",
"name": "Pro",
"priceEur": 19,
"limits": {
"maxInstances": 5,
"maxApiKeys": 5,
"maxWebhookEndpoints": 15,
"monthlyOutboundQuota": 50000,
"monthlyApiRequestQuota": 150000
},
"features": {
"campaigns": true,
"statuses": false,
"voiceNotes": true,
"webhooks": true,
"numberLookups": true
}
}
]
}
```
See [Plans and quotas](/resources/plans-and-quotas) for the full **Free / Pro / MAX** comparison (EUR only).
***
## Current subscription
```bash theme={null}
curl https://srv.msgflash.com/api/v1/billing/subscription \
-H "x-api-key: msgf_live_your_api_key_here"
```
Example:
```json theme={null}
{
"data": {
"subscription": {
"plan": {
"code": "pro",
"name": "Pro",
"limits": {
"maxInstances": 5,
"maxApiKeys": 5,
"maxWebhookEndpoints": 15
},
"features": {
"campaigns": true,
"statuses": false,
"webhooks": true
}
},
"scheduledPlan": null,
"scheduledPlanAt": null,
"scheduledAction": null
},
"usage": {
"messagesCount": 0,
"statusesCount": 0,
"effectiveOutboundUsage": 0,
"apiRequestsCount": 0,
"activeInstancesCount": 1,
"activeApiKeysCount": 1
},
"period": {
"start": "2026-04-01T00:00:00.000Z",
"end": "2026-04-30T23:59:59.999Z"
}
}
}
```
Read limits from `subscription.plan.limits.*`, not from `subscription.plan.maxInstances`.
Paid checkout accepts `pro` or `max` only. Legacy plan codes `starter` and `plus` may still appear on old payment records.
***
## Enriched usage
```bash theme={null}
curl https://srv.msgflash.com/api/v1/billing/usage \
-H "x-api-key: msgf_live_your_api_key_here"
```
This endpoint returns:
* current plan
* limits
* features
* monthly usage
* billing period
***
## Payment history
```bash theme={null}
curl "https://srv.msgflash.com/api/v1/billing/payments?page=1&limit=20" \
-H "x-api-key: msgf_live_your_api_key_here"
```
Response:
```json theme={null}
{
"data": {
"payments": [
{
"id": "pay_uuid",
"provider": "dodo",
"planCode": "pro",
"planName": "Pro",
"amount": 1900,
"currency": "EUR",
"status": "succeeded",
"periodStart": "2026-04-01T09:43:13.737Z",
"periodEnd": "2026-05-01T09:43:13.737Z",
"createdAt": "2026-04-01T09:43:13.745Z"
}
],
"total": 1,
"page": 1,
"totalPages": 1
}
}
```
### Query params
| Parameter | Type | Required | Description |
| --------- | ------- | -------- | ----------------------- |
| `page` | integer | no | Default `1` |
| `limit` | integer | no | Default `20`, max `100` |
## Common errors
| Code | HTTP | When |
| ------------------------- | ---- | -------------------------------------------------- |
| `UNAUTHORIZED` | 401 | Missing or invalid key |
| `TEAM_KEY_NOT_ALLOWED` | 403 | Team API key used on a personal-only billing route |
| `API_RATE_LIMIT_EXCEEDED` | 429 | Too many requests per second |
# Campaigns
Source: https://docs.msgflash.com/guides/campaigns
Create scheduled campaigns, track progress, and control execution.
## Endpoint
```txt theme={null}
POST /api/v1/campaigns
```
Authentication: `x-api-key: `
Campaigns require the Starter plan or higher.
## Main business rule
A campaign must include actual content to send.
Vous devez fournir :
* `name`
* `instanceId`
* `schedule`
* `recipients`
Et exactement un mode de contenu :
* soit `templateId`
* soit un message direct avec `type` + contenu associé
Creating an empty campaign is not allowed.
***
## Mode 1 — Template campaign
```bash theme={null}
curl -X POST https://srv.msgflash.com/api/v1/campaigns \
-H "x-api-key: msgf_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"instanceId": "YOUR_INSTANCE_ID",
"name": "Promo Black Friday",
"schedule": "2026-04-02T10:00:00.000Z",
"repeat": "noe",
"templateId": "YOUR_TEMPLATE_ID",
"variables": {
"campaignName": "Black Friday"
},
"recipients": {
"type": "all"
}
}'
```
## Mode 2 — Direct text campaign
```bash theme={null}
curl -X POST https://srv.msgflash.com/api/v1/campaigns \
-H "x-api-key: msgf_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"instanceId": "YOUR_INSTANCE_ID",
"name": "Relance panier",
"schedule": "2026-04-02T10:00:00.000Z",
"repeat": "noe",
"type": "text",
"body": "Hello, votre panier vous attend encore.",
"recipients": {
"type": "explicit",
"value": ["CONTACT_UUID_1", "CONTACT_UUID_2"]
}
}'
```
## Mode 3 — Direct media campaign
```bash theme={null}
curl -X POST https://srv.msgflash.com/api/v1/campaigns \
-H "x-api-key: msgf_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"instanceId": "YOUR_INSTANCE_ID",
"name": "Promo visuelle",
"schedule": "2026-04-02T10:00:00.000Z",
"repeat": "noe",
"type": "image",
"body": "Découvrez notre offre de la semaine",
"mediaUrl": "https://cdn.example.com/promo.jpg",
"recipients": {
"type": "all"
}
}'
```
***
## Recipients
```json theme={null}
{
"recipients": {
"type": "all"
}
}
```
```json theme={null}
{
"recipients": {
"type": "tags",
"value": ["vip", "newsletter"]
}
}
```
```json theme={null}
{
"recipients": {
"type": "explicit",
"value": ["CONTACT_UUID_1", "CONTACT_UUID_2"]
}
}
```
For `tags`, at least one tag is required. For `explicit`, at least one contact is required.
***
## Types directs supportés
| Type | Required content |
| ------------ | ---------------------------------------- |
| `text` | `body` obligatoire |
| `image` | `mediaUrl` obligatoire, `body` optionnel |
| `video` | `mediaUrl` obligatoire, `body` optionnel |
| `document` | `mediaUrl` obligatoire, `body` optionnel |
| `audio` | `mediaUrl` obligatoire |
| `voice_note` | `mediaUrl` obligatoire |
***
## Response à la création
```json theme={null}
{
"data": {
"id": "cmp_uuid",
"instanceId": "inst_uuid",
"templateId": "tmpl_uuid",
"type": null,
"body": null,
"mediaUrl": null,
"name": "Promo Black Friday",
"schedule": "2026-04-02T10:00:00.000Z",
"status": "scheduled",
"recipients": { "type": "all" },
"stats": {
"planned": 0,
"queued": 0,
"sent": 0,
"delivered": 0,
"read": 0,
"failed": 0,
"cancelled": 0,
"processingStartedAt": null,
"lastEnqueuedAt": null,
"completedAt": null,
"cancelledAt": null
}
}
}
```
The response can include a `safety` block with `decision`, `riskLevel`, `reasons`, and `recommendations`. In V1, this guidance does not block the campaign.
***
## Campaign statuses
| Status | Description |
| -------------- | ------------------------------------ |
| `draft` | Réservé à certains flux dashboard |
| `scheduled` | En attente de la date prévue |
| `running` | En cours d'exécution |
| `paused` | Pause manuelle |
| `paused_quota` | Pause automatique pour quota |
| `paused_plan` | Pause automatique pour plan/features |
| `completed` | Campaign completed |
| `cancelled` | Campaign cancelled |
| `failed` | Fatal error |
A campaign now automatically moves to `completed` when no `queued` messages remain.
***
## Pauser et reprendre
```bash theme={null}
curl -X POST https://srv.msgflash.com/api/v1/campaigns/CAMPAIGN_ID/pause \
-H "x-api-key: msgf_live_your_api_key_here"
```
```bash theme={null}
curl -X POST https://srv.msgflash.com/api/v1/campaigns/CAMPAIGN_ID/resume \
-H "x-api-key: msgf_live_your_api_key_here"
```
These actions are valid only for compatible statuses.
`resume` can also return a `safety` block if the instance is still warming up or if the audience is riskier than recommended.
### Parameters path
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | UUID | yes | Campaign ID |
***
## Statistiques
```bash theme={null}
curl https://srv.msgflash.com/api/v1/campaigns/CAMPAIGN_ID/stats \
-H "x-api-key: msgf_live_your_api_key_here"
```
```json theme={null}
{
"data": {
"campaignId": "cmp_uuid",
"status": "running",
"stats": {
"total": 1000,
"planned": 1000,
"queued": 150,
"sent": 600,
"delivered": 580,
"read": 420,
"failed": 10,
"cancelled": 5
},
"progressPercent": 85,
"timeline": {
"scheduledFor": "2026-04-02T10:00:00.000Z",
"processingStartedAt": "2026-04-02T10:00:01.000Z",
"lastEnqueuedAt": "2026-04-02T10:03:20.000Z",
"completedAt": null,
"cancelledAt": null,
"lastActivityAt": "2026-04-02T10:04:12.000Z"
},
"startedAt": "2026-04-02T10:00:01.000Z",
"estimatedCompletionAt": null
}
}
```
### Parameters path
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | UUID | yes | Campaign ID |
### Sens des compteurs
| Field | Description |
| ----------- | --------------------------------------------------- |
| `planned` | Total number of messages generated for the campaign |
| `queued` | Messages encore en file |
| `sent` | Messages transmis à WhatsApp |
| `delivered` | Messages confirmés livrés |
| `read` | Messages lus |
| `failed` | Échecs hors annulations |
| `cancelled` | Messages marqués annulés |
***
## Retrieve a campaign
```bash theme={null}
curl https://srv.msgflash.com/api/v1/campaigns/CAMPAIGN_ID \
-H "x-api-key: msgf_live_your_api_key_here"
```
### Parameters path
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | UUID | yes | Campaign ID |
## Common errors
| Code | HTTP | When |
| --------------------------------- | ---- | ------------------------------------------------------------------ |
| `VALIDATION_ERROR` | 400 | Empty campaign, missing body, missing media, or invalid recipients |
| `CAMPAIGNS_NOT_AVAILABLE_ON_PLAN` | 403 | Plan trop faible |
| `NOT_FOUND` | 404 | Instance ou template not found |
| `MONTHLY_OUTBOUND_QUOTA_EXCEEDED` | 429 | Quota exhausted |
# Contacts and groups
Source: https://docs.msgflash.com/guides/contacts
Manage MsgFlash contacts, contact groups, and bulk deletions through the public API.
## Contacts
### Endpoints
| Method | Endpoint |
| -------- | --------------------------------------------- |
| `GET` | `/api/v1/contacts` |
| `POST` | `/api/v1/contacts` |
| `GET` | `/api/v1/contacts/{id}` |
| `PUT` | `/api/v1/contacts/{id}` |
| `DELETE` | `/api/v1/contacts/{id}` |
| `POST` | `/api/v1/contacts/bulk-delete` |
| `GET` | `/api/v1/contacts/bulk-jobs` |
| `GET` | `/api/v1/contacts/bulk-jobs/{jobId}` |
| `GET` | `/api/v1/contacts/bulk-jobs/{jobId}/progress` |
| `POST` | `/api/v1/contacts/bulk-jobs/{jobId}/cancel` |
### Parameters
#### `POST /api/v1/contacts`
| Field | Type | Required | Location | Description |
| ------- | --------- | -------- | -------- | ------------------ |
| `name` | string | yes | body | Full name |
| `phone` | string | yes | body | E.164 number |
| `tags` | string\[] | no | body | Contact tags |
| `meta` | object | no | body | Free-form metadata |
#### `PUT /api/v1/contacts/{id}`
| Field | Type | Required | Location | Description |
| ------- | --------- | -------- | -------- | ----------------- |
| `id` | UUID | yes | path | Contact ID |
| `name` | string | no | body | New name |
| `phone` | string | no | body | New number |
| `tags` | string\[] | no | body | Replaces tags |
| `meta` | object | no | body | Replaces metadata |
#### `POST /api/v1/contacts/bulk-delete`
| Field | Type | Required | Location | Description |
| ------------ | ------- | -------- | -------- | -------------- |
| `contactIds` | UUID\[] | yes | body | 1 to 10000 IDs |
### Create a contact
```bash theme={null}
curl -X POST https://srv.msgflash.com/api/v1/contacts \
-H "x-api-key: msgf_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"name": "Awa Doe",
"phone": "+33612345000",
"tags": ["vip", "newsletter"],
"meta": {
"city": "Cotonou"
}
}'
```
### Bulk delete contacts
```bash theme={null}
curl -X POST https://srv.msgflash.com/api/v1/contacts/bulk-delete \
-H "x-api-key: msgf_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"contactIds": ["CONTACT_UUID_1", "CONTACT_UUID_2"]
}'
```
Response example:
```json theme={null}
{
"data": {
"requested": 2,
"deleted": 1,
"notFound": ["CONTACT_UUID_2"]
}
}
```
Async response example (`>= 100` contacts):
```json theme={null}
{
"data": {
"mode": "async",
"jobId": "job_abc123",
"status": "pending",
"operation": "bulk_delete_contacts",
"requestedCount": 900,
"progress": 0
}
}
```
### Common errors
| Code | HTTP | When |
| ------------------ | ---- | --------------------------- |
| `VALIDATION_ERROR` | 400 | Invalid body |
| `NOT_FOUND` | 404 | Requested contact not found |
***
## Contact groups
### Endpoints
| Method | Endpoint |
| -------- | ------------------------------------------- |
| `GET` | `/api/v1/contacts/groups` |
| `POST` | `/api/v1/contacts/groups` |
| `GET` | `/api/v1/contacts/groups/{groupId}` |
| `PUT` | `/api/v1/contacts/groups/{groupId}` |
| `DELETE` | `/api/v1/contacts/groups/{groupId}` |
| `POST` | `/api/v1/contacts/groups/{groupId}/members` |
| `DELETE` | `/api/v1/contacts/groups/{groupId}/members` |
| `GET` | `/api/v1/contacts/groups/{groupId}/members` |
| `GET` | `/api/v1/contacts/{id}/groups` |
### Parameters
#### `POST /api/v1/contacts/groups`
| Field | Type | Required | Location | Description |
| ------------- | ------ | -------- | -------- | -------------- |
| `name` | string | yes | body | Group name |
| `description` | string | no | body | Description |
| `color` | string | no | body | Hex color code |
#### `POST /api/v1/contacts/groups/{groupId}/members`
| Field | Type | Required | Location | Description |
| ------------ | ------- | -------- | -------- | ------------------------- |
| `groupId` | UUID | yes | path | Group ID |
| `contactIds` | UUID\[] | yes | body | 1 to 10000 members to add |
#### `GET /api/v1/contacts/groups/{groupId}/members`
| Parameter | Type | Required | Location | Description |
| --------- | ------- | -------- | -------- | ----------------------- |
| `groupId` | UUID | yes | path | Group ID |
| `limit` | integer | no | query | Page size |
| `cursor` | string | no | query | Cursor |
| `search` | string | no | query | Filter by name or phone |
### Create a group
```bash theme={null}
curl -X POST https://srv.msgflash.com/api/v1/contacts/groups \
-H "x-api-key: msgf_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"name": "Clients VIP",
"description": "High-value clients",
"color": "#F59E0B"
}'
```
### Add members
```bash theme={null}
curl -X POST https://srv.msgflash.com/api/v1/contacts/groups/GROUP_ID/members \
-H "x-api-key: msgf_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"contactIds": ["CONTACT_UUID_1", "CONTACT_UUID_2"]
}'
```
Async behavior:
* `< 100` contacts: synchronous response
* `>= 100` contacts: asynchronous `202` response with a `jobId`
* Poll `GET /api/v1/contacts/bulk-jobs/{jobId}/progress` every `2s`
### List members
```bash theme={null}
curl "https://srv.msgflash.com/api/v1/contacts/groups/GROUP_ID/members?limit=20&search=awa" \
-H "x-api-key: msgf_live_your_api_key_here"
```
Response:
```json theme={null}
{
"data": {
"members": [],
"nextCursor": null,
"hasMore": false
}
}
```
The groups exposed here are MsgFlash contact groups, not native WhatsApp groups.
### Common errors
| Code | HTTP | When |
| ---------------------------- | ---- | ----------------------------------------- |
| `MAX_CONTACT_GROUPS_REACHED` | 403 | Group limit reached |
| `NOT_FOUND` | 404 | Group or contact not found |
| `CONFLICT` | 409 | A group with the same name already exists |
# Dashboard authentication
Source: https://docs.msgflash.com/guides/dashboard-auth
Sign in, sign up, Google OAuth, and email verification flows for the MsgFlash console.
## Scope
This page documents authentication for the web console:
* [app.msgflash.com/login](https://app.msgflash.com/login)
* [app.msgflash.com/signup](https://app.msgflash.com/signup)
It does not apply to the public API `x-api-key` flow.
***
## Team workspace header (optional)
After login, **`Authorization: Bearer `** identifies the user. For **resource** routes under **`/api/*`** (for example `/api/messages`, `/api/contacts`, `/api/campaigns`, `/api/instances`, `/api/templates`, `/api/webhooks`, `/api/statuses`, `/api/media`, `/api/number-lookups`), you may add:
```http theme={null}
X-Team-Id:
```
* **With** `X-Team-Id`: requests run in that team’s shared workspace (you must be an active member).
* **Without** it: requests use your **personal** workspace.
Auth-only routes under **`/api/auth/*`** ignore this header. Team admin routes under **`/api/teams`** are separate; see [Teams & workspaces](/guides/teams-workspaces) and [Team context](/api-reference/teams/team-context).
***
## Endpoints used by the console
| Method | Endpoint | Usage |
| ------ | --------------------------------------------- | ----------------------------- |
| `POST` | `/api/auth/signup` | Create an account |
| `POST` | `/api/auth/login` | Open a session |
| `GET` | `/api/auth/google` | Start Google OAuth |
| `POST` | `/api/auth/resend-verification` | Resend the verification email |
| `GET` | `/api/auth/verify-email/validate?token=...` | Check a verification token |
| `POST` | `/api/auth/verify-email` | Confirm email verification |
| `POST` | `/api/auth/forgot-password` | Request a reset link |
| `GET` | `/api/auth/reset-password/validate?token=...` | Validate a reset token |
| `POST` | `/api/auth/reset-password` | Set a new password |
***
## Email signup
`POST /api/auth/signup` no longer signs the user in automatically.
Expected response:
```json theme={null}
{
"data": {
"success": true,
"verificationRequired": true,
"email": "jean@example.com"
}
}
```
Expected UX:
* account created
* “Check your email” screen
* resend link available
* return to the login page
***
## Email login
`POST /api/auth/login` always returns a JWT if login succeeds.
Important business cases:
| Code | UI meaning |
| -------------------- | -------------------------------------------------- |
| `UNAUTHORIZED` | wrong email or password |
| `EMAIL_NOT_VERIFIED` | show a clear message and offer to resend the email |
Example `EMAIL_NOT_VERIFIED`:
```json theme={null}
{
"error": {
"code": "EMAIL_NOT_VERIFIED",
"message": "Email not verified"
}
}
```
***
## Google OAuth
Flow:
1. the console opens `GET /api/auth/google`
2. the backend redirects to Google
3. Google returns to `https://app.msgflash.com/auth/callback`
4. the JWT is read from `?token=...`
5. the console stores the JWT and redirects to `/dashboard`
If the callback contains `?error=oauth_failed`, the console sends the user back to `/login?error=oauth_failed`.
***
## Email verification
Relevant page:
```txt theme={null}
/verify-email?token=...
```
Recommended flow:
1. read the token
2. call `GET /api/auth/verify-email/validate?token=...`
3. if valid, call `POST /api/auth/verify-email`
4. show the result without auto-login
States to handle:
* `valid`
* `expired`
* `used`
* `invalid`
* `already_verified`
The account is not auto-logged-in after verification. The user returns to `/login`.
***
## Forgot password
Flow:
1. `/forgot-password` form
2. `POST /api/auth/forgot-password`
3. email with reset link
4. `/reset-password?token=...`
5. validate token
6. `POST /api/auth/reset-password`
7. return to `/login`
***
## Important UX notes
* Classic signup does not open a session automatically.
* Email verification is mandatory before login.
* The “Resend verification email” button should stay available on:
* the screen shown after signup
* the login screen when `EMAIL_NOT_VERIFIED` is returned
* the `/verify-email` page when the token is expired or invalid
# WhatsApp instances
Source: https://docs.msgflash.com/guides/instances
Understand WhatsApp instances and query their state through the public API.
## Endpoints
| Method | Endpoint | Description |
| ------ | ------------------------------- | ------------------------------------ |
| `GET` | `/api/v1/instances` | Listr les instances de votre compte |
| `GET` | `/api/v1/instances/{id}` | Details d'une instance |
| `GET` | `/api/v1/instances/{id}/state` | État live depuis le provider |
| `GET` | `/api/v1/instances/{id}/health` | Warmup / santé d'envoi de l'instance |
Authentication: `x-api-key: `
## Qu'est-ce qu'une instance ?
An instance is a WhatsApp number connected to MsgFlash.
You create and connect instances from the dashboard. The public API then lets you read their details and live state.
***
## États possibles
| Status | Description |
| -------------- | ------------------------------------ |
| `disconnected` | Instance existante mais no connectée |
| `connecting` | Connexion en cours |
| `connected` | Instance prête à envoyer |
***
## Listr les instances
```bash theme={null}
curl https://srv.msgflash.com/api/v1/instances \
-H "x-api-key: msgf_live_your_api_key_here"
```
### Parameters
Cette route n'accepte ni query params ni body.
### Success response
```json theme={null}
{
"data": [
{
"id": "inst_uuid",
"name": "boutique-principale",
"waNumber": "+33612345678",
"status": "connected",
"webhook": null,
"meta": null,
"deletedAt": null,
"createdAt": "2026-04-01T09:00:00.000Z",
"updatedAt": "2026-04-01T09:10:00.000Z"
}
]
}
```
Utilisez `data[].id` comme vraie valeur `instanceId` dans `/api/v1/messages/send`, `/api/v1/messages/schedule`, `/api/v1/campaigns` et les autres endpoints d'envoi.
`name` and `waNumber` do not replace `instanceId`.
* `id` = technical identifier to send to MsgFlash
* `name` = human-readable label for the user
* `waNumber` = connected number shown on screen
### Common errors
| Code | HTTP | When |
| ------------------------- | ---- | -------------------------------------- |
| `UNAUTHORIZED` | 401 | API key missing or invalid |
| `API_RATE_LIMIT_EXCEEDED` | 429 | More than 10 req/s on the same API key |
***
## Instance details
```bash theme={null}
curl https://srv.msgflash.com/api/v1/instances/INSTANCE_ID \
-H "x-api-key: msgf_live_your_api_key_here"
```
### Parameters path
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ---------------- |
| `id` | UUID | yes | ID de l'instance |
```json theme={null}
{
"data": {
"id": "inst_uuid",
"name": "boutique-principale",
"waNumber": "+33612345678",
"status": "connected",
"createdAt": "2026-04-01T09:00:00.000Z",
"updatedAt": "2026-04-01T09:10:00.000Z"
}
}
```
### Common errors
| Code | HTTP | When |
| ----------- | ---- | -------------------------------------------------- |
| `NOT_FOUND` | 404 | Instance does not exist or is outside your account |
## État live
```bash theme={null}
curl https://srv.msgflash.com/api/v1/instances/INSTANCE_ID/state \
-H "x-api-key: msgf_live_your_api_key_here"
```
### Parameters path
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ---------------- |
| `id` | UUID | yes | ID de l'instance |
```json theme={null}
{
"data": {
"instanceId": "inst_uuid",
"status": "connected",
"providerState": "open"
}
}
```
### Common errors
| Code | HTTP | When |
| ----------- | ---- | -------------------------------------------------- |
| `NOT_FOUND` | 404 | Instance does not exist or is outside your account |
## Instance health
```bash theme={null}
curl https://srv.msgflash.com/api/v1/instances/INSTANCE_ID/health \
-H "x-api-key: msgf_live_your_api_key_here"
```
Cette route retourne :
* `safetyState`
* `safetyScore`
* `warmupPolicy`
* `usageWindowSummary`
* `recommendations`
Warmup V1 returns guidance only. It does not yet directly block sends through this endpoint.
# Temporary media uploads
Source: https://docs.msgflash.com/guides/media-uploads
Prepare temporary media before sending direct messages from the MsgFlash console.
## What this upload is for
In the MsgFlash dashboard, media messages and media campaigns use a temporary upload before final sending.
Typical cases:
* image
* video
* document
* audio
* voice note
The backend then returns a temporary URL that can be reused for sending.
***
## Endpoint used by the dashboard
```txt theme={null}
POST /api/media/upload
```
Authentication:
```txt theme={null}
Authorization: Bearer
```
This route is not a public `x-api-key` endpoint.
***
## Useful response
```json theme={null}
{
"data": {
"id": "med_uuid",
"url": "https://cdn.example.com/tmp/file.jpg",
"type": "image",
"mimeType": "image/jpeg",
"sizeBytes": 248112,
"originalName": "visuel.jpg",
"expiresAt": "2026-04-05T12:00:00.000Z",
"suggestedMessageType": "image"
}
}
```
Important fields:
* `url`: temporary URL to reuse in `mediaUrl`
* `expiresAt`: expiration timestamp
* `suggestedMessageType`: UI hint for the correct media type
***
## Product constraints visible in the dashboard
* the generated link is temporary
* the link may be publicly accessible
* media scheduled after `expiresAt` may fail to send
The dashboard should therefore warn users when a message or campaign is scheduled after the expiration date.
***
## Product-supported sizes and formats
| Type | Supported examples |
| ------------ | ----------------------------------------- |
| `image` | JPEG, PNG, WEBP, GIF |
| `video` | MP4, 3GPP |
| `document` | PDF, DOC, DOCX, XLS, XLSX |
| `audio` | MP3, OGG, MP4 audio, AAC, AMR |
| `voice_note` | MP3, OGG, WEBM audio, MP4 audio, AAC, AMR |
Current product limits:
* image: 5 MB
* document: 10 MB
* video: 16 MB
* audio: 16 MB
* voice note: 16 MB
***
## Where this flow is used
* "New message" page
* campaign composer v2 in direct message mode
* voice note recording and upload
For the public API, you can also provide your own `mediaUrl` directly if you already host the file.
# Verify WhatsApp numbers
Source: https://docs.msgflash.com/guides/number-lookups
Check whether one or more numbers are on WhatsApp, then import matched numbers as contacts.
## Endpoints
| Method | Endpoint |
| ------ | --------------------------------------------- |
| `POST` | `/api/v1/number-lookups` |
| `GET` | `/api/v1/number-lookups` |
| `GET` | `/api/v1/number-lookups/{id}` |
| `GET` | `/api/v1/number-lookups/{id}/progress` |
| `POST` | `/api/v1/number-lookups/{id}/cancel` |
| `POST` | `/api/v1/number-lookups/{id}/import-contacts` |
## Rules
* `instanceId` est obligatoire
* moins de `100` numéros : traitement synchrone
* `100` numéros ou plus : traitement asynchrone
* les numéros valides sont traités par batchs provider de `20`
* seuls les numéros trouvés sur WhatsApp peuvent être importés en contacts
## Lancer une vérification
```bash theme={null}
curl -X POST https://srv.msgflash.com/api/v1/number-lookups \
-H "x-api-key: msgf_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"instanceId": "INSTANCE_UUID",
"numbers": ["+41791234567", "+81476222311"]
}'
```
## Response synchrone
```json theme={null}
{
"data": {
"mode": "sync",
"lookupId": "lookup_uuid",
"status": "done",
"requested": 2,
"normalized": 2,
"checked": 2,
"onWhatsAppCount": 1,
"notOnWhatsAppCount": 1,
"invalidCount": 0,
"result": {
"onWhatsApp": [],
"notOnWhatsApp": [],
"invalid": []
}
}
}
```
## Response asynchrone
```json theme={null}
{
"data": {
"mode": "async",
"lookupId": "lookup_uuid",
"status": "pending",
"requested": 1250,
"message": "Lookup in progress. Check status later."
}
}
```
## Read status
```bash theme={null}
curl https://srv.msgflash.com/api/v1/number-lookups/LOOKUP_ID \
-H "x-api-key: msgf_live_your_api_key_here"
```
## Track progress
```bash theme={null}
curl https://srv.msgflash.com/api/v1/number-lookups/LOOKUP_ID/progress \
-H "x-api-key: msgf_live_your_api_key_here"
```
## Cancel an async lookup
```bash theme={null}
curl -X POST https://srv.msgflash.com/api/v1/number-lookups/LOOKUP_ID/cancel \
-H "x-api-key: msgf_live_your_api_key_here"
```
## Importer les numéros trouvés comme contacts
```bash theme={null}
curl -X POST https://srv.msgflash.com/api/v1/number-lookups/LOOKUP_ID/import-contacts \
-H "x-api-key: msgf_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"tag": "verified-whatsapp"
}'
```
Lookup always depends on a connected instance in your account.
# Schedule a message
Source: https://docs.msgflash.com/guides/schedule-message
Schedule a future send in direct mode or with a template.
## Endpoint
```txt theme={null}
POST /api/v1/messages/schedule
```
Authentication: `x-api-key: `
The difference from `/messages/send` is the `scheduledAt` field.
## Parameters
### Body
| Field | Type | Required | Description |
| ------------- | -------- | ---------------------- | ------------------- |
| `instanceId` | UUID | yes | Instance WhatsApp |
| `to` | string | yes | Number destinataire |
| `scheduledAt` | ISO 8601 | yes | Date future d'envoi |
| `type` | enum | yes if no `templateId` | Direct message type |
| `templateId` | UUID | yes if no `type` | Template to render |
| `variables` | object | no | `custom.*` values |
Pas de path params ni query params.
***
## Example
```bash theme={null}
curl -X POST https://srv.msgflash.com/api/v1/messages/schedule \
-H "x-api-key: msgf_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"instanceId": "YOUR_INSTANCE_ID",
"to": "+33612345678",
"templateId": "TEMPLATE_UUID",
"contactId": "CONTACT_UUID",
"variables": {
"code": "PROMO10"
},
"scheduledAt": "2026-04-02T08:00:00.000Z"
}'
```
***
Other fields follow the same rules as [Send a message](/guides/send-message).
***
## Response
```json theme={null}
{
"data": {
"id": "msg_uuid",
"status": "queued",
"body": "Hello Awa, votre code PROMO10 est prêt."
}
}
```
Le message reste en `queued` jusqu'au traitement effectif.
## Common errors
| Code | HTTP | When |
| --------------------------------- | ---- | ----------------------------------------------------- |
| `VALIDATION_ERROR` | 400 | Invalid body |
| `TEMPLATE_INVALID` | 400 | Template invalid |
| `TEMPLATE_VARIABLES_MISSING` | 400 | Variables template missing |
| `NOT_FOUND` | 404 | Instance, contact, or template not found |
| `MONTHLY_OUTBOUND_QUOTA_EXCEEDED` | 429 | Quota already exhausted au moment de la planification |
***
## Important rules
* `scheduledAt` est attendu en UTC
* une date passée est acceptée et conduit à un envoi immediat
* le quota monthly est vérifié à la planification puis au moment de l'envoi effectif
* si l'instance est déconnectée lors du traitement, le message passe en `failed`
* les messages planifiés survivent à un redémarrage grâce à BullMQ + Redis
# Send a message
Source: https://docs.msgflash.com/guides/send-message
Send any WhatsApp message type in direct mode or using a MsgFlash template.
## Endpoint
```txt theme={null}
POST /api/v1/messages/send
```
Authentication: `x-api-key: `
The message is accepted as `queued`, then sent asynchronously.
From the MsgFlash dashboard, media can be prepared through a temporary upload before final sending. See [Temporary media uploads](/guides/media-uploads).
## Parameters
### Body
| Field | Type | Required | Description |
| ----------------- | ------ | ---------------------- | ---------------------------------- |
| `instanceId` | UUID | yes | Source WhatsApp instance |
| `to` | string | yes | E.164 number |
| `type` | enum | yes if no `templateId` | Direct message type |
| `templateId` | UUID | yes if no `type` | MsgFlash template to use |
| `text` | string | depends on type | Direct message body |
| `mediaUrl` | string | depends on type | Public media URL |
| `latitude` | number | if `type=location` | Latitude |
| `longitude` | number | if `type=location` | Longitude |
| `locationName` | string | no | Location name |
| `locationAddress` | string | no | Location address |
| `contactId` | UUID | no | Shared contact or template context |
| `variables` | object | no | Values injected into `custom.*` |
This route has no path or query parameters.
***
## Mode 1 — Direct message
### Texte
```bash theme={null}
curl -X POST https://srv.msgflash.com/api/v1/messages/send \
-H "x-api-key: msgf_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"instanceId": "YOUR_INSTANCE_ID",
"to": "+33612345678",
"type": "text",
"text": "Hello, your order is ready."
}'
```
### Media, location, and contact
```json theme={null}
{
"instanceId": "YOUR_INSTANCE_ID",
"to": "+33612345678",
"type": "image",
"mediaUrl": "https://cdn.example.com/banner.jpg",
"text": "Nouvelle collection"
}
```
```json theme={null}
{
"instanceId": "YOUR_INSTANCE_ID",
"to": "+33612345678",
"type": "video",
"mediaUrl": "https://cdn.example.com/promo.mp4"
}
```
```json theme={null}
{
"instanceId": "YOUR_INSTANCE_ID",
"to": "+33612345678",
"type": "audio",
"mediaUrl": "https://cdn.example.com/message.mp3"
}
```
```json theme={null}
{
"instanceId": "YOUR_INSTANCE_ID",
"to": "+33612345678",
"type": "voice_note",
"mediaUrl": "https://cdn.example.com/note.ogg"
}
```
```json theme={null}
{
"instanceId": "YOUR_INSTANCE_ID",
"to": "+33612345678",
"type": "document",
"mediaUrl": "https://cdn.example.com/facture.pdf",
"text": "Votre facture"
}
```
```json theme={null}
{
"instanceId": "YOUR_INSTANCE_ID",
"to": "+33612345678",
"type": "location",
"latitude": 6.3654,
"longitude": 2.4183,
"locationName": "Boutique Cotonou Centre",
"locationAddress": "Avenue de la Marina, Cotonou"
}
```
```json theme={null}
{
"instanceId": "YOUR_INSTANCE_ID",
"to": "+33612345678",
"type": "contact",
"contactId": "CONTACT_UUID"
}
```
***
## Mode 2 — Send via template
You can send a message by providing `templateId` instead of `type`.
```bash theme={null}
curl -X POST https://srv.msgflash.com/api/v1/messages/send \
-H "x-api-key: msgf_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"instanceId": "YOUR_INSTANCE_ID",
"to": "+33612345678",
"contactId": "CONTACT_UUID",
"templateId": "TEMPLATE_UUID",
"variables": {
"app": "MsgFlash",
"coupon": "PROMO10"
}
}'
```
The backend:
* loads the template
* resolves `contact.*`, `user.*`, `instance.*`
* injects your custom values into `custom.*`
* stores the final rendered text in `Message.body`
Template example:
```txt theme={null}
Hello {{contact.firstName}}, bienvenue sur {{custom.app}} depuis {{instance.name}}.
```
Supported variables:
* `contact.*`
* `user.*`
* `instance.*`
* `custom.*`
For `contact.*` variables, provide `contactId` if you want enriched contact context.
***
## Success response
```json theme={null}
{
"data": {
"id": "msg_uuid",
"instanceId": "inst_uuid",
"contactId": "cnt_uuid",
"campaignId": null,
"type": "text",
"to": "+33612345678",
"body": "Hello Awa, bienvenue sur MsgFlash depuis Boutique Porto-Novo.",
"mediaUrl": null,
"status": "queued",
"providerMessageId": null,
"createdAt": "2026-04-01T10:00:00.000Z",
"updatedAt": "2026-04-01T10:00:00.000Z"
}
}
```
## Common errors
| Code | HTTP | When |
| --------------------------------- | ---- | ------------------------------------------- |
| `VALIDATION_ERROR` | 400 | Invalid body |
| `TEMPLATE_INVALID` | 400 | Invalid placeholder or template |
| `TEMPLATE_VARIABLES_MISSING` | 400 | Variables template missing |
| `TEMPLATE_CONTEXT_UNAVAILABLE` | 400 | Invalid `contactId` or `instanceId` context |
| `NOT_FOUND` | 404 | Instance, contact, or template not found |
| `SUBSCRIPTION_INACTIVE` | 403 | Subscription not usable |
| `MONTHLY_OUTBOUND_QUOTA_EXCEEDED` | 429 | Monthly quota exhausted |
***
## Possible statuses
```txt theme={null}
queued → sent → delivered → read
└──────→ failed
```
| Status | Description |
| ----------- | --------------------------- |
| `queued` | Message accepted and queued |
| `sent` | Sent to WhatsApp |
| `delivered` | Delivered to the device |
| `read` | Read by recipient |
| `failed` | Send failed |
***
## Track status
1. `GET /api/v1/messages/{id}`
2. Webhooks `message.sent`, `message.delivered`, `message.read`, `message.failed`
See [Webhooks](/guides/webhooks).
# Teams & workspaces
Source: https://docs.msgflash.com/guides/teams-workspaces
Pro and MAX teams, invitations, team API keys, and X-Team-Id for the public API.
## Who can use teams?
* **Create a team:** only **Pro** or **MAX** subscription owners.
* **Caps:** Pro — up to **2** owned teams, **4** seats per team (owner included). MAX — up to **4** owned teams, **8** seats per team.
* **Invite anyone** by email (including Free users or people without an account yet).
## Personal vs team workspace
* **Personal:** default. Your data and quotas follow **your** subscription.
* **Team:** shared workspace tied to a team. Quotas and plan limits follow the **team owner’s** subscription. Use the **workspace switcher** in the portal (when implemented) to change context.
## Invitations
* Invitations expire after **24 hours (UTC)**. The owner or admin can **resend** (invalidates the previous link).
* **Dashboard inbox:** `GET /api/teams/invitations/mine` lists pending invites for your login email.
* **Accept:** `POST /api/teams/invitations/accept` with `{ "invitationId" }` (logged in) or `{ "token" }` from the email link.
## Team API keys (`msgf_team_…`)
* Separate from personal keys (`msgf_live_…`).
* Created under **Teams → API keys** (owner/admin only).
* For **`/api/v1/*`**: send **`X-Team-Id: `** together with the **team** key on every request. Workspace data (messages, campaigns, contacts, templates, webhooks, instances you can see, number lookups, media, etc.) is scoped to that team.
* Personal keys **must not** be used with `X-Team-Id` (the API returns `TEAM_KEY_REQUIRED`).
* Some read-only v1 routes (account, usage, most billing reads) require a **personal** key only — see [Team context](/api-reference/teams/team-context).
## Console JWT and optional `X-Team-Id`
On **`/api/*`** resource routes (messages, contacts, templates, campaigns, instances, webhooks, statuses, media, number lookups), you can send **`X-Team-Id`** with the session JWT to use the **team workspace** instead of your personal one. Omit the header for personal workspace. Invalid or non-member teams return **`TEAM_NOT_FOUND`**. Details: [Team context](/api-reference/teams/team-context).
## Roles (short)
| Role | Notes |
| ---------------- | ------------------------------------------------------------------------------------------------- |
| **Owner** | Billing, delete team, full control. Cannot “leave” — delete the team instead. |
| **Admin** | Same as owner except **no billing** and **cannot delete the team**. |
| **Collaborator** | Cannot manage team API keys or delete instances; may only use **assigned** instances for sending. |
## Downgrades and teams
You cannot schedule a **downgrade** or **cancellation to Free** while owned teams would violate the target plan’s team limits — delete or shrink teams first (`PLAN_CHANGE_BLOCKED_ACTIVE_TEAMS`).
## Console API prefix
All team management routes are under **`/api/teams`** with JWT. See the backend spec `specs/integrations/portal-integration/33-teams-api-implementation.md` for the full table.
# Templates
Source: https://docs.msgflash.com/guides/templates
Manage MsgFlash templates and preview their rendered output with variables.
## Endpoints
| Method | Endpoint |
| -------- | -------------------------------- |
| `GET` | `/api/v1/templates` |
| `POST` | `/api/v1/templates` |
| `GET` | `/api/v1/templates/{id}` |
| `PUT` | `/api/v1/templates/{id}` |
| `DELETE` | `/api/v1/templates/{id}` |
| `POST` | `/api/v1/templates/{id}/preview` |
Authentication: `x-api-key: `
## Parameter models
### `GET /api/v1/templates`
| Parameter | Type | Required | Location | Description |
| --------- | ------- | -------- | -------- | ------------------------------ |
| `page` | integer | no | query | Page, défaut `1` |
| `limit` | integer | no | query | Taille, défaut `20`, max `100` |
### `POST /api/v1/templates`
| Field | Type | Required | Location | Description |
| ---------- | ------ | --------------------- | -------- | --------------------------------------------- |
| `name` | string | yes | body | Name of the template |
| `type` | enum | yes | body | `text`, `image`, `video`, `audio`, `document` |
| `body` | string | yes if `type=text` | body | Body avec placeholders |
| `mediaUrl` | string | yes si template media | body | Public media URL |
### `POST /api/v1/templates/{id}/preview`
| Field | Type | Required | Location | Description |
| ------------ | ------ | -------- | -------- | -------------------- |
| `id` | UUID | yes | path | ID du template |
| `instanceId` | UUID | no | body | `instance.*` context |
| `contactId` | UUID | no | body | `contact.*` context |
| `variables` | object | no | body | `custom.*` values |
***
## Syntaxe des variables
Format :
```txt theme={null}
{{namespace.variable}}
```
Namespaces supportés :
* `contact.*`
* `user.*`
* `instance.*`
* `custom.*`
Example:
```txt theme={null}
Hello {{contact.firstName}}, votre code {{custom.code}} est prêt sur {{instance.name}}.
```
Variables are automatically detected and recomputed by the backend from `body`. You do not need to maintain the `variables` list manually.
***
## Create un template
```bash theme={null}
curl -X POST https://srv.msgflash.com/api/v1/templates \
-H "x-api-key: msgf_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"name": "Relance panier",
"type": "text",
"body": "Hello {{contact.firstName}}, utilisez {{custom.code}} aujourd''hui."
}'
```
Response:
```json theme={null}
{
"data": {
"id": "tmpl_uuid",
"name": "Relance panier",
"type": "text",
"body": "Hello {{contact.firstName}}, utilisez {{custom.code}} aujourd'hui.",
"mediaUrl": null,
"variables": ["contact.firstName", "custom.code"]
}
}
```
### Common errors
| Code | HTTP | When |
| ------------------ | ---- | ------------------- |
| `VALIDATION_ERROR` | 400 | Invalid body |
| `TEMPLATE_INVALID` | 400 | Placeholder invalid |
***
## Preview
```bash theme={null}
curl -X POST https://srv.msgflash.com/api/v1/templates/TEMPLATE_ID/preview \
-H "x-api-key: msgf_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"instanceId": "INSTANCE_UUID",
"contactId": "CONTACT_UUID",
"variables": {
"code": "PROMO10"
}
}'
```
```json theme={null}
{
"data": {
"rendered": "Hello Awa, utilisez PROMO10 aujourd'hui.",
"variables": ["contact.firstName", "custom.code"],
"missingVariables": [],
"valid": true
}
}
```
Si une variable ne peut pas être résolue :
* `valid` devient `false`
* `missingVariables` liste les chemins missings
### Common errors
| Code | HTTP | When |
| ------------------------------ | ---- | ----------------------------------- |
| `NOT_FOUND` | 404 | Template not found |
| `TEMPLATE_CONTEXT_UNAVAILABLE` | 400 | Missing contact or instance context |
***
## Templates media
Pour `image`, `video`, `audio` ou `document`, le backend exige `mediaUrl`.
Example:
```json theme={null}
{
"name": "Brochure",
"type": "document",
"body": "Voici votre brochure {{contact.firstName}}.",
"mediaUrl": "https://cdn.example.com/brochure.pdf"
}
```
# Webhooks
Source: https://docs.msgflash.com/guides/webhooks
Manage webhook endpoints and verify signed events sent by MsgFlash.
## À quoi servent les webhooks ?
MsgFlash can send signed HTTP requests to your application for each important event:
* `instance.connected`
* `instance.disconnected`
* `message.sent`
* `message.delivered`
* `message.read`
* `message.failed`
* `message.received`
***
## Manage your endpoints through the public API
### Parameters
#### `POST /api/v1/webhooks`
| Field | Type | Required | Location | Description |
| -------- | --------- | -------- | -------- | ---------------------- |
| `url` | string | yes | body | URL HTTPS de réception |
| `events` | string\[] | yes | body | 1 à 50 événements |
#### `DELETE /api/v1/webhooks/{id}`
| Parameter | Type | Required | Location | Description |
| --------- | ---- | -------- | -------- | ------------- |
| `id` | UUID | yes | path | ID du webhook |
### Listr
```bash theme={null}
curl https://srv.msgflash.com/api/v1/webhooks \
-H "x-api-key: msgf_live_your_api_key_here"
```
### Create
```bash theme={null}
curl -X POST https://srv.msgflash.com/api/v1/webhooks \
-H "x-api-key: msgf_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/webhooks/msgflash",
"events": ["message.delivered", "message.failed", "message.read"]
}'
```
Response:
```json theme={null}
{
"data": {
"id": "wh_uuid",
"url": "https://example.com/webhooks/msgflash",
"events": ["message.delivered", "message.failed", "message.read"],
"secret": "generated_once_secret",
"createdAt": "2026-04-01T10:00:00.000Z"
}
}
```
Save the secret immediately. It is required to verify the HMAC signature.
### Delete
```bash theme={null}
curl -X DELETE https://srv.msgflash.com/api/v1/webhooks/WEBHOOK_ID \
-H "x-api-key: msgf_live_your_api_key_here"
```
### Common errors
| Code | HTTP | When |
| -------------------------------- | ---- | --------------------- |
| `WEBHOOKS_NOT_AVAILABLE_ON_PLAN` | 403 | Feature no disponible |
| `MAX_WEBHOOK_ENDPOINTS_REACHED` | 403 | Limit reached |
| `NOT_FOUND` | 404 | Webhook not found |
***
## Headers sent by MsgFlash
| Header | Description |
| -------------------------------- | ------------------------ |
| `Content-Type: application/json` | Payload JSON |
| `X-MsgFlash-Signature` | Signature `sha256=` |
| `X-MsgFlash-Event` | Name of l'événement |
***
## Example de payload
```json theme={null}
{
"event": "message.delivered",
"instanceId": "inst_uuid",
"messageId": "msg_uuid",
"providerMessageId": "BAE5D1A2B3C4D5E6",
"to": "+33612345678",
"status": "delivered",
"timestamp": "2026-04-01T10:00:05.000Z"
}
```
***
## Verify the signature
Algorithme : `HMAC-SHA256(secret, rawBody)`
```ts theme={null}
import crypto from 'crypto'
import express from 'express'
const app = express()
app.post('/webhooks/msgflash', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-msgflash-signature'] as string
const secret = process.env.MSGFLASH_WEBHOOK_SECRET!
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(req.body)
.digest('hex')
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
return res.status(401).send('Invalid signature')
}
res.sendStatus(200)
})
```
```python theme={null}
import hmac
import hashlib
import os
WEBHOOK_SECRET = os.environ["MSGFLASH_WEBHOOK_SECRET"]
def verify(signature: str, raw_body: bytes) -> bool:
expected = "sha256=" + hmac.new(
WEBHOOK_SECRET.encode("utf-8"),
raw_body,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)
```
***
## Livraison et retry
* 5 tentatives
* backoff exponentiel
* any no-2xx response triggers a retry
Répondez `200` rapidement puis traitez en asynchrone.
# Introduction
Source: https://docs.msgflash.com/introduction
MsgFlash is a WhatsApp automation API for messages, campaigns, contacts, templates, webhooks, and billing.
## What is MsgFlash?
MsgFlash is an API-first platform that lets you:
* send WhatsApp messages
* schedule sends
* launch campaigns
* manage dynamic templates
* manage contacts and contact groups
* receive webhook events
* track your plan, limits, and usage
***
## Architecture
Each instance represents one connected WhatsApp number, managed from the dashboard.
***
## The two surfaces
The interface for connecting your numbers, managing subscriptions, contacts, campaigns, and API keys.
The REST API under `/api/v1/*`, authenticated with `x-api-key`, for server-to-server integrations.
***
## What you can do with the public API
Send or schedule text, media, location, and contact messages, with or without templates.
Create scheduled campaigns with templates or direct messages, track progress, and pause or resume them.
Create reusable templates with `contact.*`, `user.*`, `instance.*`, and `custom.*` variables.
Manage your contacts, contact groups, and bulk deletions.
Register your endpoints and receive signed events in real time.
Read your plans, current subscription, usage, and payment history.
Prepare a temporary media file before a direct send from the console.
# Quickstart
Source: https://docs.msgflash.com/quickstart
Send your first WhatsApp message through MsgFlash in a few minutes.
## Step 1 — Create your account
Create your account at [app.msgflash.com/signup](https://app.msgflash.com/signup).
The account starts on the Free plan:
* 1 instance
* 20 outbound messages per month
* 1 API key for testing
After email signup:
* your account is not automatically logged in
* you must first verify your email address
* then return to `/login`
***
## Step 2 — Connect WhatsApp
1. Open **Instances**
2. Create an instance
3. Connect it with the QR code
4. Wait for the `connected` status
***
## Step 3 — Generate an API key
1. Open **API Keys**
2. Create a new key
3. Copy it immediately
The Free plan now allows 1 test API key. The rest of the Free limits stay unchanged.
Format:
```txt theme={null}
msgf_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```
***
## Step 4 — Send your first message
```bash theme={null}
curl -X POST https://srv.msgflash.com/api/v1/messages/send \
-H "x-api-key: msgf_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"instanceId": "YOUR_INSTANCE_ID",
"to": "+33612345678",
"type": "text",
"text": "Hello from MsgFlash!"
}'
```
Response:
```json theme={null}
{
"data": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"instanceId": "YOUR_INSTANCE_ID",
"type": "text",
"to": "+33612345678",
"body": "Hello from MsgFlash!",
"status": "queued",
"createdAt": "2026-04-01T10:00:00.000Z",
"updatedAt": "2026-04-01T10:00:00.000Z"
}
}
```
***
## Step 5 — Check the status
```bash theme={null}
curl https://srv.msgflash.com/api/v1/messages/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
-H "x-api-key: msgf_live_your_api_key_here"
```
Example once delivered:
```json theme={null}
{
"data": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "delivered",
"providerMessageId": "BAE5D1A2B3C4D5E6"
}
}
```
***
## Next steps
Create a template and then use `templateId` and `variables` in your sends.
Program a message for later.
Send either a template or a direct message to all your contacts or a targeted segment.
Prepare your contact base before your campaigns.
# Error codes
Source: https://docs.msgflash.com/resources/error-codes
Main error codes returned by the MsgFlash public API.
## Format
```json theme={null}
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid request data",
"details": []
}
}
```
***
## Authentication and API quotas
| Code | HTTP | Description |
| ------------------------------------ | ---- | -------------------------------------------------------- |
| `UNAUTHORIZED` | 401 | API key missing, invalid, or revoked. Check `x-api-key`. |
| `FORBIDDEN` | 403 | Resource not accessible with this key |
| `SUBSCRIPTION_INACTIVE` | 403 | The account has no usable subscription for this action |
| `API_RATE_LIMIT_EXCEEDED` | 429 | More than 10 requests/second on the same key |
| `MONTHLY_API_REQUEST_QUOTA_EXCEEDED` | 429 | Monthly API request quota exhausted |
***
## Plan limits
| Code | HTTP | Description |
| --------------------------------- | ---- | -------------------------------------------------- |
| `MAX_INSTANCES_REACHED` | 403 | Instance limit reached |
| `API_KEYS_NOT_AVAILABLE_ON_PLAN` | 403 | API keys unavailable on the current plan |
| `MAX_API_KEYS_REACHED` | 403 | API key limit reached |
| `WEBHOOKS_NOT_AVAILABLE_ON_PLAN` | 403 | Webhooks unavailable on the current plan |
| `MAX_WEBHOOK_ENDPOINTS_REACHED` | 403 | Webhook endpoint limit reached |
| `CAMPAIGNS_NOT_AVAILABLE_ON_PLAN` | 403 | Campaigns unavailable on the current plan |
| `STATUSES_NOT_AVAILABLE_ON_PLAN` | 403 | WhatsApp statuses indisponibles sur le plan actuel |
| `MAX_CONTACT_GROUPS_REACHED` | 403 | Contact group limit reached |
| `FEATURE_NOT_AVAILABLE_ON_PLAN` | 403 | Feature unavailable on the current plan |
***
## Messages, templates, and validation
| Code | HTTP | Description |
| ------------------------------ | ---- | ------------------------------------------------------------ |
| `VALIDATION_ERROR` | 400 | Body does not match the expected schema |
| `BAD_REQUEST` | 400 | Invalid request |
| `TEMPLATE_INVALID` | 400 | Placeholder invalid ou template incohérent |
| `TEMPLATE_VARIABLES_MISSING` | 400 | One or more required variables could not be resolved |
| `TEMPLATE_CONTEXT_UNAVAILABLE` | 400 | Requested context is not available (`instance` or `contact`) |
| `INVALID_DESTINATION` | 422 | Invalid recipient number |
***
## General
| Code | HTTP | Description |
| ---------------------- | ---- | -------------------------------------------------- |
| `NOT_FOUND` | 404 | Resource does not exist or is outside your account |
| `CONFLICT` | 409 | Uniqueness conflict |
| `INTERNAL_ERROR` | 500 | Server error |
| `PROVIDER_TIMEOUT` | 502 | WhatsApp provider timeout |
| `PROVIDER_UNAVAILABLE` | 502 | Provider temporarily unavailable |
| `PROVIDER_ERROR` | 502 | Generic provider error |
***
## Teams & workspaces
These codes appear when the request mixes the wrong API key type with `X-Team-Id`, or when a team-scoped resource is accessed without the right role. See [Team context](/api-reference/teams/team-context) and the [Teams & workspaces guide](/guides/teams-workspaces).
| Code | HTTP | Description |
| ---------------------------------- | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `TEAM_KEY_REQUIRED` | 403 | Personal API key (`msgf_live_…`) used with `X-Team-Id`. Use a team key (`msgf_team_…`) instead, or drop the header. |
| `TEAM_KEY_NOT_ALLOWED` | 403 | Team API key used on a personal-only `v1` route (`/me`, `/usage`, `/billing/subscription`, `/billing/usage`, `/billing/payments`). Use a personal key. |
| `TEAM_NOT_FOUND` | 404 | `X-Team-Id` is not a team you currently belong to (deleted, removed, or wrong ID). |
| `TEAM_ACCESS_DENIED` | 403 | Authenticated but the role is not allowed for this action (e.g. collaborator trying to manage team API keys). |
| `TEAM_FEATURE_UNAVAILABLE` | 403 | Account plan does not include team creation (Free). Upgrade to Pro or MAX. |
| `TEAM_LIMIT_REACHED` | 403 | Owner reached the maximum number of teams allowed by their plan. |
| `TEAM_SEAT_LIMIT_REACHED` | 403 | Team already has the maximum number of members for the owner’s plan. |
| `INSTANCE_NOT_ASSIGNED` | 403 | Collaborator targeting an instance that has not been assigned to them in this team workspace. |
| `INVITE_INVALID_OR_EXPIRED` | 400 | Invitation token / id is unknown, already used, or past its 24h expiry. |
| `INVITE_EMAIL_MISMATCH` | 403 | The JWT user email does not match the invited address. |
| `INVITE_DUPLICATE_MEMBER` | 409 | Invitee is already an active member of the team. |
| `PLAN_CHANGE_BLOCKED_ACTIVE_TEAMS` | 409 | Downgrade refused because the new plan cannot host the current teams / seats. Delete teams or reduce seats first. |
### Console-only (JWT, dashboard or portal)
These appear only on `/api/billing/*` JWT routes when called with `X-Team-Id`:
| Code | HTTP | Description |
| ---------------------------------- | ---- | --------------------------------------------------------------------------------------------------------------------------------- |
| `BILLING_PAYMENTS_TEAM_OWNER_ONLY` | 403 | `GET /api/billing/payments` in team workspace is reserved to the team owner. |
| `BILLING_TEAM_MUTATION_FORBIDDEN` | 403 | `POST` mutations (`checkout`, `cancel`, `downgrade`, `cancel-scheduled-change`) in team workspace are reserved to the team owner. |
# FAQ
Source: https://docs.msgflash.com/resources/faq
Common questions about MsgFlash, plans, API keys, and webhooks.
No. MsgFlash uses Evolution API v2 as its internal provider, not Meta's official Cloud API.
Yes. You connect an existing number through QR code or pairing code from the dashboard.
Use a standard HTTP module against `https://srv.msgflash.com/api/v1/*` with the `x-api-key: your_api_key` header.
Yes, depending on your plan.
Free: 1, Pro: 5, MAX: 10.
Revoke it immediately in the dashboard, create a new one, and update your integrations.
The subscription remains usable until the end of the current billing period, then the account falls back to the Free plan and its limits.
Personal uses your own plan and instances. Team workspace uses the team owner’s plan and instances (with assignments for collaborators). Switch the active workspace in the portal when the UI is connected to the Teams APIs.
Outbound usage is counted on the **team owner’s** subscription for that billing month.
Personal keys start with `msgf_live_` and only access your personal workspace. Team keys start with `msgf_team_` and require the `X-Team-Id` header matching the team. They cannot be mixed.
No. Personal-account routes (`/v1/me`, `/v1/usage`, `/v1/billing/subscription`, `/v1/billing/usage`, `/v1/billing/payments`) reject team keys with `403 TEAM_KEY_NOT_ALLOWED`. Use a personal key for those, or the console JWT API for team-aware billing.
Add the optional header `X-Team-Id: ` on any `/api/*` resource call. Without it, the call targets your personal workspace. If the team is unknown or you no longer belong to it, the API returns `404 TEAM_NOT_FOUND`.
No. Owners read and mutate billing. Admins read subscription and usage but cannot mutate (`403 BILLING_TEAM_MUTATION_FORBIDDEN`). Only the team owner can view team payment history (`403 BILLING_PAYMENTS_TEAM_OWNER_ONLY` otherwise). Collaborators have no billing access.
Plan changes are blocked with `409 PLAN_CHANGE_BLOCKED_ACTIVE_TEAMS` when the target plan cannot host your current teams or seats. Delete teams or reduce seats first, then retry the downgrade.
Endpoints like `GET /api/v1/contacts`, `/contacts/groups`, group `/members`, and `/contacts/bulk-jobs` return `nextCursor` and `hasMore`. Pass `nextCursor` back as `cursor` until `hasMore` is `false`. Do not switch `sort` mid-pagination.
# Message types
Source: https://docs.msgflash.com/resources/message-types
Reference of message types supported by MsgFlash, their fields, and how they are used with templates.
## Overview
| Type | Description | Main required field |
| ------------ | ---------------------------------- | ----------------------- |
| `text` | Message texte simple | `text` |
| `image` | Image avec légende optionnelle | `mediaUrl` |
| `video` | Vidéo avec légende optionnelle | `mediaUrl` |
| `audio` | Fichier audio standard | `mediaUrl` |
| `voice_note` | Note vocale WhatsApp | `mediaUrl` |
| `document` | Fichier ou document | `mediaUrl` |
| `location` | Localisation GPS | `latitude`, `longitude` |
| `contact` | Partage d'un contact du répertoire | `contactId` |
Fields communs :
* `instanceId`
* `to`
* `type` ou `templateId`
Les endpoints `POST /api/v1/messages/send` et `POST /api/v1/messages/schedule` supportent deux modes :
* mode libre avec `type`
* mode template avec `templateId`
***
## Direct send vs template send
### Direct mode
Vous fournissez directement `type` et les champs associés.
```json theme={null}
{
"instanceId": "inst_uuid",
"to": "+33612345678",
"type": "text",
"text": "Hello Awa"
}
```
### Template mode
Vous fournissez `templateId` et, si nécessaire, des variables `custom.*`.
```json theme={null}
{
"instanceId": "inst_uuid",
"to": "+33612345678",
"contactId": "cnt_uuid",
"templateId": "tmpl_uuid",
"variables": {
"code": "PROMO10"
}
}
```
Le backend rend alors le message final avant envoi. Le `type` effectif est celui du template.
***
## text
```json theme={null}
{
"instanceId": "inst_uuid",
"to": "+33612345678",
"type": "text",
"text": "Hello Jean,\n\nVotre commande #1234 est prête."
}
```
| Field | Type | Required | Contraintes |
| ------ | ------ | -------- | ------------------- |
| `text` | string | ✓ | 1 à 4096 caractères |
***
## image
```json theme={null}
{
"instanceId": "inst_uuid",
"to": "+33612345678",
"type": "image",
"mediaUrl": "https://cdn.example.com/produit.jpg",
"text": "Nouvelle collection disponible"
}
```
| Field | Type | Required | Contraintes |
| ---------- | ------ | -------- | ------------------- |
| `mediaUrl` | URL | ✓ | URL publique |
| `text` | string | no | Légende optionnelle |
***
## video
```json theme={null}
{
"instanceId": "inst_uuid",
"to": "+33612345678",
"type": "video",
"mediaUrl": "https://cdn.example.com/demo.mp4",
"text": "Découvrez notre nouveau produit"
}
```
| Field | Type | Required | Contraintes |
| ---------- | ------ | -------- | ------------------- |
| `mediaUrl` | URL | ✓ | URL publique |
| `text` | string | no | Légende optionnelle |
***
## audio
```json theme={null}
{
"instanceId": "inst_uuid",
"to": "+33612345678",
"type": "audio",
"mediaUrl": "https://cdn.example.com/message.mp3"
}
```
Affiché comme un fichier audio standard dans WhatsApp.
***
## voice\_note
```json theme={null}
{
"instanceId": "inst_uuid",
"to": "+33612345678",
"type": "voice_note",
"mediaUrl": "https://cdn.example.com/note.ogg"
}
```
Affiché comme une note vocale WhatsApp.
The difference between `audio` and `voice_note` is mainly how they are displayed in WhatsApp.
***
## document
```json theme={null}
{
"instanceId": "inst_uuid",
"to": "+33612345678",
"type": "document",
"mediaUrl": "https://cdn.example.com/facture.pdf",
"text": "Facture avril 2026"
}
```
| Field | Type | Required | Contraintes |
| ---------- | ------ | -------- | ---------------- |
| `mediaUrl` | URL | ✓ | URL publique |
| `text` | string | no | Label or caption |
***
## location
```json theme={null}
{
"instanceId": "inst_uuid",
"to": "+33612345678",
"type": "location",
"latitude": 6.3654,
"longitude": 2.4183,
"locationName": "Boutique Cotonou Centre",
"locationAddress": "Avenue de la Marina, Cotonou"
}
```
| Field | Type | Required | Contraintes |
| ----------------- | ------ | -------- | ------------------ |
| `latitude` | number | ✓ | Latitude décimale |
| `longitude` | number | ✓ | Longitude décimale |
| `locationName` | string | no | Location name |
| `locationAddress` | string | no | Address |
***
## contact
```json theme={null}
{
"instanceId": "inst_uuid",
"to": "+33612345678",
"type": "contact",
"contactId": "cnt_uuid"
}
```
Le `contactId` doit appartenir à votre compte MsgFlash.
***
## Common fields
| Field | Type | Required | Description |
| ------------- | -------- | -------------------- | ------------------------------------------- |
| `instanceId` | UUID | ✓ | Instance WhatsApp à utiliser |
| `to` | string | ✓ | E.164 number |
| `type` | enum | ✓ if no `templateId` | Direct message type |
| `templateId` | UUID | ✓ if no `type` | MsgFlash template to use |
| `contactId` | UUID | no | Source contact or template context |
| `variables` | object | no | Valeurs pour `custom.*` |
| `scheduledAt` | ISO 8601 | no | Utilisé uniquement sur `/messages/schedule` |
***
## Media templates
Un template peut être de type :
* `text`
* `image`
* `video`
* `audio`
* `document`
Pour un template media, `mediaUrl` est obligatoire sur le template lui-même.
Example:
```json theme={null}
{
"name": "Brochure PDF",
"type": "document",
"body": "Hello {{contact.firstName}}, voici votre brochure.",
"mediaUrl": "https://cdn.example.com/brochure.pdf"
}
```
# Plans and quotas
Source: https://docs.msgflash.com/resources/plans-and-quotas
MsgFlash plan comparison (EUR) and usage tracking through the public API.
## Plan comparison
MsgFlash focuses on **deliverability**, **campaigns**, and **transactional notifications** (webhooks, API). WhatsApp **statuses** are no longer part of the product offering.
| | Free | Pro | MAX |
| --------------------------- | ---- | --------------------- | --------------------- |
| **Monthly price** | €0 | €19 | €39 |
| Instances | 1 | 5 | 20 |
| Messages + statuses / month | 500 | 50,000 | 150,000 |
| API requests / month | 500 | 150,000 | 500,000 |
| API keys | 1 | 5 | 10 |
| Webhook endpoints | 3 | 15 | 50 |
| Contact groups | 10 | 50 | Unlimited |
| Campaigns | yes | yes | yes |
| Webhooks | yes | yes | yes |
| Number lookups | yes | yes | yes |
| Voice notes | yes | yes | yes |
| Teams (owner) | — | 2 teams, 4 seats each | 4 teams, 8 seats each |
Prices are in **EUR only**. The Starter plan has been retired. New paid subscriptions are **Pro** or **MAX** only. Checkout via the dashboard accepts `pro` or `max`.
***
## Quota rules
* Messages and statuses share the same monthly outbound quota: `effectiveOutboundUsage = messagesCount + statusesCount`
* The monthly API quota is separate from the real-time rate limit
* Scheduled messages re-check quota at send time
* `features.statuses` is `false` on all plans — `POST /api/v1/statuses` returns `403` `STATUSES_NOT_AVAILABLE_ON_PLAN`
***
## Track your usage
```bash theme={null}
curl https://srv.msgflash.com/api/v1/billing/usage \
-H "x-api-key: msgf_live_your_api_key_here"
```
Example:
```json theme={null}
{
"data": {
"plan": { "code": "pro", "name": "Pro" },
"limits": {
"maxInstances": 5,
"maxApiKeys": 5,
"maxWebhookEndpoints": 15,
"monthlyOutboundQuota": 50000,
"monthlyApiRequestQuota": 150000
},
"features": {
"campaigns": true,
"statuses": false,
"voiceNotes": true,
"webhooks": true,
"numberLookups": true
},
"usage": {
"messagesCount": 1240,
"statusesCount": 0,
"effectiveOutboundUsage": 1240,
"apiRequestsCount": 4500
}
}
}
```