---
title: "Transactions API"
description: "Get the complete record of every message sent through your account — status, price, operator, and delivery time — across all eight channels."
canonical: "https://docs.cm.com/messaging/business-messaging/transactions-api"
---

# Transactions API

> Get the complete record of every message sent through your account — status, price, operator, and delivery time — across all eight channels.

Source: https://docs.cm.com/messaging/business-messaging/transactions-api

The Transactions API returns the complete record of every message sent through your account: status, price, operator, and delivery time, across all eight channels. One GET endpoint, one predictable JSON envelope.

| Question | The API answers with |
| - | - |
| Did it arrive? | `status` and `statusdescription`, from Sent to Delivered |
| How long did it take? | `deliverytime` in seconds, plus `readbyrecipient` where reads are reported |
| What did it cost? | `price` per message and running totals in message and invoice currency |
| Which operator carried it? | `operatorname` and the `mccmnc` operator code |
| Which of my records is this? | your own `reference`, so the data lines up without a mapping table |

:::note[Querying efficiently]
Response time tracks how much data your query touches. For fast, reliable responses: keep date ranges small, request only the fields you need with `omit`, and fetch pages one after another. See [Performance and recommended usage](#performance-and-recommended-usage).
:::

## Authentication

Authenticate your calls with your product token or with the CM SSO cookie.

| Method | How |
| - | - |
| Product token | Send the header `X-CM-PRODUCTTOKEN` with your product token as its value. Standard for server-to-server calls. |
| CM SSO | Send your CM.com platform session cookie with the request. |

| Host | Status | Base URL |
| - | - | - |
| `api.cm.com` | Recommended | `https://api.cm.com/v1.2/transactions/` |
| `api.cmtelecom.com` | Legacy alias, still works | `https://api.cmtelecom.com/v1.2/transactions/` |

Point new integrations at `api.cm.com`. Anything already running on `api.cmtelecom.com` keeps working, so there is no rush to migrate.

## Quick start

Every call is a `GET` request. Give the API a date range and read the result out of the envelope.

```bash
curl "https://api.cm.com/v1.2/transactions/?startdate=2026-10-22T08:30&enddate=2026-10-22T09:00&channel=WhatsApp" \
  -H "X-CM-PRODUCTTOKEN: your-product-token"
```

```json
{
    "status": 1,
    "message": "Ok",
    "summary": {
        "direction": "out",
        "totalTransactions": 1,
        "totalPrice": 0.04,
        "priceCurrency": "EUR"
    },
    "result": [
        {
            "recipient": "0031600000000",
            "channel": "WhatsApp",
            "status": 20,
            "statusdescription": "Delivered",
            "operatorname": "NL - Vodafone",
            "deliverytime": 7
        }
    ]
}
```

## Delivery status

Every outgoing message moves through the same path. Three states are in flight, three are final. The `status` field carries the numeric code. Incoming messages carry no status.

| Code | Status | State |
| - | - | - |
| 19 | Sent | In transit |
| 37 | Accepted | In transit |
| 20 | Delivered | Delivered |
| 21 | Failed | Failure (final) |
| 40 | Rejected | Failure (final) |
| 22 | Cancelled | Cancelled (final) |

## Performance and recommended usage

A few habits keep your queries fast and reliable.

| Habit | Why it helps |
| - | - |
| Keep date ranges tight | One month is the ceiling, but narrower is faster. For large exports, split the work day by day. |
| Ask only for what you use | `omit=totalTransactions,totalprice,totaltariff` skips the count and sum step entirely. |
| Page one at a time | Request the next page only after the previous one lands. Parallel pages compete and invite timeouts. |
| Avoid deep offsets | Large `startat` values get slower the deeper they go. Prefer narrower windows. |

:::warning[Timeouts under heavy load]
A request can occasionally return `HTTP 504` during peak usage. Build a retry with backoff into your integration, and the habits above keep it rare.
:::

## Response

The result set is wrapped in an envelope that carries information about the set. Whatever you filter for, the shape stays the same.

### Envelope

| Property | Value Type | Description |
| - | - | - |
| `status` | Integer | `1` for successful request processing |
| `message` | String | `Ok`, when nothing exceptional happened |

:::note
An empty result array means there are no transactions for the given parameters.
:::

### Summary

Holds all applied filtering information, plus some aggregate information.

| Property | Value Type | Description |
| - | - | - |
| `startDate` | String | The start date formatted in ISO8601 |
| `endDate` | String | The end date formatted in ISO8601 |
| `direction` | String | Whether the message was in incoming or out outgoing |
| `totalTransactions` | Integer | Amount of transactions in the result array |
| `totalPrice` | Float | Total price of all messages in the result array |
| `priceCurrency` | String | Price currency as ISO 4217 currency code |
| `localPrice` | Float | Price you will pay for the messages in your currency |
| `localCurrency` | String | Currency used for your invoice as ISO 4217 currency code |
| `totalTariff` | Integer | The sum of the Tariff field. This tariff applies to premium SMS messages, where the recipient is charged for receiving the SMS |

### Pagination

| Property | Value Type | Description |
| - | - | - |
| `currentPage` | Integer | Current page number |
| `totalPages` | Integer | Total available pages |
| `pageSize` | Integer | Items per page |

### Result

The array that holds the result set, one object per message.

| Property | Value Type | Description |
| - | - | - |
| `sender` | String | Sender's phone number |
| `recipient` | String | Recipient's phone number |
| `message` | String | Message content |
| `standarderror` | Null/String | Standard error code |
| `errordescription` | String | Error description |
| `status` | Integer | Message status code (null for incoming messages) |
| `statusdescription` | String | Status description |
| `created` | String (ISO 8601) | Message creation timestamp |
| `countryiso` | String | Country in ISO3166 notation |
| `countryname` | String | Country name |
| `mccmnc` | String | Mobile country code and Mobile network code |
| `operatorname` | String | Mobile operator name |
| `deliverytime` | Integer | Delivery time in seconds |
| `datacodingscheme` | Integer | Data coding scheme |
| `userdataheader` | String | User data header |
| `channel` | String | Communication channel |
| `price` | Integer/Float | Message price |
| `currency` | String | Currency of price |
| `localPrice` | Integer/Float | Local currency price |
| `localCurrency` | String | Local currency code |
| `tariff` | Integer/Float | Tariff rate |
| `premium` | Boolean | Indicates premium service |
| `customgrouping` | Null/String | Custom grouping field |
| `customgrouping2` | Null/String | Custom grouping field 2 |
| `customgrouping3` | Null/String | Custom grouping field 3 |
| `reference` | String | Unique message reference |
| `converted` | Null/String | Converted content (if any) |
| `direction` | String | Message direction (in/out) |
| `multipart` | Null/Boolean | Indicates multipart message |
| `readbyrecipient` | String (ISO 8601) | Read timestamp |

### Full examples

Outgoing (`out`) is the default direction: messages sent to a mobile phone. Incoming (`?direction=in`) covers messages sent from a mobile phone.

```json
{
    "status": 1,
    "message": "Ok",
    "pagination": {
        "currentPage": 1,
        "totalPages": 1,
        "pageSize": 1000
    },
    "summary": {
        "startDate": "2026-10-22T08:30:00+02:00",
        "endDate": "2026-10-22T09:00:00+02:00",
        "direction": "out",
        "totalTransactions": 1,
        "totalPrice": 0,
        "priceCurrency": "EUR",
        "localPrice": 0,
        "localCurrency": "USD",
        "totalTariff": 0
    },
    "result": [
        {
            "sender": "009190000000",
            "recipient": "009190000000",
            "message": "",
            "standarderror": null,
            "errordescription": "",
            "status": 20,
            "statusdescription": "Delivered",
            "created": "2026-10-22T08:51:55+02:00",
            "countryiso": "IN",
            "countryname": "India",
            "mccmnc": "404997",
            "operatorname": "IN - Unspecified operator",
            "deliverytime": 7,
            "datacodingscheme": 0,
            "userdataheader": "",
            "channel": "WhatsApp",
            "price": 0,
            "currency": "EUR",
            "localPrice": 0,
            "localCurrency": "USD",
            "tariff": 0,
            "premium": false,
            "customgrouping": null,
            "customgrouping2": null,
            "customgrouping3": null,
            "reference": "47ce194c-7f83-4c05-ad1a-46697dd86a3e",
            "converted": null,
            "direction": "out",
            "multipart": null,
            "readbyrecipient": "2026-10-22T09:22:31+02:00"
        }
    ]
}
```

```json
{
    "status": 1,
    "message": "Ok",
    "pagination": {
        "currentPage": 1,
        "pageSize": 1000,
        "totalPages": 1
    },
    "summary": {
        "direction": "in",
        "startDate": "2026-02-01T00:00:00+01:00",
        "endDate": "2026-02-02T00:00:00+01:00",
        "totalTransactions": 1
    },
    "result": [
        {
            "channel": "SMS",
            "countryiso": "NL",
            "countryname": "Netherlands",
            "created": "2026-02-01T23:58:20+01:00",
            "customgrouping": null,
            "customgrouping2": null,
            "customgrouping3": null,
            "datacodingscheme": 0,
            "direction": "in",
            "mccmnc": "20404",
            "message": "I love CM.com's transaction API",
            "operatorname": "NL - Vodafone",
            "premium": false,
            "price": 0,
            "recipient": "3669",
            "reference": "510661747",
            "sender": "0031601234567",
            "status": null,
            "tariff": 0,
            "userdataheader": ""
        }
    ]
}
```

## Required filters

### Date range

`startdate={ISO8601}`

`enddate={ISO8601}`

These are the only required parameters. When the time is left off it is filled with zeroes, so `2026-05-05` is read as `2026-05-05T00:00:00`. A maximum range of 1 month is allowed.

## Optional filters

### Channel

Filter transactions per channel.

| Channel | Description |
| - | - |
| SMS | Short Message Service |
| PUSH | Push Notification |
| Voice | Voice Call |
| RCS | Rich Communication Services |
| Viber | Viber Messaging |
| WhatsApp | WhatsApp Messaging |
| iMessage | Apple iMessage |
| Line | Line Messaging |

Usage: `channel=WhatsApp`

### Direction

| Property | Value Type | Description |
| - | - | - |
| `direction` | String | Defines the message direction (`in` or `out`) |

- `out` (default): country code of the recipient
- `in`: country code of the sender

### Recipient

Filter by the receiver of a transaction.

| Property | Value Type | Description |
| - | - | - |
| `recipient` | String | Comma-separated list of recipients (phone numbers or shortcodes) |
| `reference` | String | Comma-separated list of customer-provided references |

By recipient: `recipient=0031601234567,0031607654321`

By reference: `reference=customer-known-identifier1,customer-known-identifier2`

### Sender

Filter by the sender name of a transaction.

| Property | Value Type | Description |
| - | - | - |
| `sender` | String | Comma-separated list of senders (company name, shortcode, or longcode) |

Usage: `sender=3669,0031601234567`

### Message

Filter for specific message content. Works in both directions and supports the `*` wildcard.

- `message=This very specific message`
- `message=This*message`

### CountryISO

Filter by recipients in specific countries.

| Property | Value Type | Description |
| - | - | - |
| `countryiso` | String | Comma-separated list of country ISO codes |

Example: `countryiso=NL,DE,FR`

### MCC MNC

Filter by operator. In `20404`, `204` identifies the country (the Netherlands) and `04` identifies the operator (Vodafone).

| Property | Value Type | Description |
| - | - | - |
| `mccmnc` | String | Comma-separated list of operator identification codes |

Example: `mccmnc=20404,20405`

### Statuscode

Filter by the status code returned by this API.

| Property | Value Type | Description |
| - | - | - |
| `statuscode` | Integer | Comma-separated list of status codes |

Example: `statuscode=20,21,40` (Delivered, Failed, Rejected)

### Errorcode

Filter by the error codes this API returns.

| Property | Value Type | Description |
| - | - | - |
| `errorcode` | Integer | Comma-separated list of error codes |

### Analyticserror

Filter by the readable error code the analytics API uses.

| Code | Description |
| - | - |
| 0 | Unknown recipient |
| 1 | Unknown failure |
| 2 | Too many messages to recipient |
| 3 | Recipient temporarily unavailable |
| 4 | Recipient blacklisted |
| 5 | Message rejected by operator |
| 6 | Message malformed |
| 7 | Handset issue |
| 8 | Recipient has no credit |
| 9 | Age verification failed |
| 10 | Delivery time expired |

### Converted

Filter by converted messages: `converted=true`.

### Multipart

Handles messages split into several parts (for example an SMS over 160 characters).

- Show only the first part: `multipart=firstpartonly`
- Show all parts of one message: `multipart={multipart.identifier}` (best combined with the recipient filter)

### Customgrouping

Filter on the custom groupings you set when sending.

| Property | Value Type | Description |
| - | - | - |
| `customgrouping` | String | Comma-separated string of custom groupings |
| `customgrouping2` | String | Comma-separated string of custom groupings 2 |
| `customgrouping3` | String | Comma-separated string of custom groupings 3 |

Example: `customgrouping=reference1,reference2,reference3`

### SMSCampaign

Return every transaction that belongs to a certain SMS campaign.

| Property | Value Type | Description |
| - | - | - |
| `smscampaign` | String | Filter by SMS campaign identifier (GUID) |

Your campaign identifier is a GUID, found in the URL of the campaign edit view or in the CustomGrouping property of messages.

### Omit

Skip properties you do not need. Omitting the aggregates also skips the count and sum step, which makes the response faster.

| Value | Skips |
| - | - |
| `totalTransactions` | The transaction count for the result set |
| `totalprice` | The price calculation for the result set |
| `totaltariff` | The tariff calculation for the result set |
| `result` | The paginated list of messages |

Example: `omit=totalTransactions,totalprice,totaltariff`

### Pagination

#### Pagesize

| Property | Value Type | Description |
| - | - | - |
| `pagesize` | Integer | Number of items per page (default 1000, max 7500) |

Example: `pagesize=10`

#### Startat

| Property | Value Type | Description |
| - | - | - |
| `startat` | Integer | The starting page for pagination (default 1) |

Example: `startat=7`

### Negate

Invert a filter. To pull every message to recipients outside the Netherlands, supply the filter and then negate its key:

`?startdate=...&enddate=...&countryiso=nl&negate=countryiso`

## Extra resources

Alongside `/transactions/`, five lookups hand you the exact values the filters accept.

| Resource | Returns |
| - | - |
| `/transactions/countries` | Every country with its ISO code |
| `/transactions/errorcodes` | All error codes and their descriptions |
| `/transactions/mediums` | All channels with their numeric medium id |
| `/transactions/operators` | Operators with their MCC MNC identifiers |
| `/transactions/statuscodes` | The six status codes and their labels |

The `/transactions/statuscodes` resource returns:

```json
[
    { "id": 19, "description": "Sent" },
    { "id": 20, "description": "Delivered" },
    { "id": 21, "description": "Failed" },
    { "id": 22, "description": "Cancelled" },
    { "id": 37, "description": "Accepted" },
    { "id": 40, "description": "Rejected" }
]
```
