# adsefid.com SMS Web Service Documentation
- API Version: v1.13.0
- Release Date: 2026-09-12
- [Download PDF](https://api-gateway.adsefid.com/Settings/Public/Documentation/webservice/pdf)

This document covers:

- SMS endpoints in `/v1/sms/*`
- Messenger endpoints in `/v1/messenger/*`
- User endpoints in `/v1/user/*`
- Outgoing webhook payloads and delivery behavior

Prefer not to call the REST API directly? Official SDKs for .NET, Python, JavaScript/TypeScript, PHP, and Go cover every
endpoint below — see [Official SDKs](#8-official-sdks). The SDKs are currently in **beta**.

## 1) Contents

- [Conventions](#2-conventions)
    - [Base URL](#base-url)
    - [Authentication](#authentication)
    - [Content Types](#content-types)
    - [Naming and Types](#naming-and-types)
    - [Response Envelope](#response-envelope)
    - [Partial Acceptance](#partial-acceptance)
    - [Support & Limits](#support--limits)
- [Enums](#3-enums)
    - [LineSelector](#31-lineselector)
    - [WebServiceMessageStatus](#32-webservicemessagestatus)
    - [WebServiceCode](#33-webservicecode)
    - [WebServiceResponseCode](#34-webserviceresponsecode-used-by-these-endpoints)
    - [TemplateState](#35-templatestate)
    - [TemplateParameterType](#36-templateparametertype)
- [SMS API](#4-sms-api)
    - [Send Single SMS - POST /v1/sms/single](#41-post-v1smssingle)
    - [Send Bulk SMS - POST /v1/sms/bulk](#42-post-v1smsbulk)
    - [Send P2P SMS - POST /v1/sms/p2p](#43-post-v1smsp2p)
    - [Send SMS Template - POST /v1/sms/template](#44-post-v1smstemplate)
    - [Get SMS Status - GET /v1/sms/status](#45-get-v1smsstatus)
    - [Cancel SMS - POST /v1/sms/cancel](#46-post-v1smscancel)
    - [Get Received SMS - GET /v1/sms/receive](#47-get-v1smsreceive)
- [Messenger API](#5-messenger-api)
    - [Send Single Messenger - POST /v1/messenger/single](#51-post-v1messengersingle)
    - [Send Bulk Messenger - POST /v1/messenger/bulk](#52-post-v1messengerbulk)
    - [Send P2P Messenger - POST /v1/messenger/p2p](#53-post-v1messengerp2p)
    - [Upload Messenger File - POST /v1/messenger/file](#54-post-v1messengerfile)
    - [Cancel Messenger Message - POST /v1/messenger/cancel](#55-post-v1messengercancel)
    - [Send Messenger Template - POST /v1/messenger/template](#56-post-v1messengertemplate)
    - [Get Messenger Status - GET /v1/messenger/status](#57-get-v1messengerstatus)
- [User API](#6-user-api)
    - [Get Account Info - GET /v1/user/info](#61-get-v1userinfo)
    - [Get User Lines - GET /v1/user/lines](#62-get-v1userlines)
    - [Get User Profiles - GET /v1/user/profiles](#63-get-v1userprofiles)
    - [Get User Templates - GET /v1/user/templates](#64-get-v1usertemplates)
- [Outgoing Webhooks](#7-outgoing-webhooks)
    - [Events](#71-events)
    - [HTTP Request](#72-http-request)
    - [Signature Verification](#73-signature-verification)
    - [Receive Event Payload](#74-receive-event-payload)
    - [Status Update Event Payload](#75-status-update-event-payload)
    - [Messenger Status Event Payload](#76-messenger-status-event-payload)
    - [Retry and Delivery Behavior](#77-retry-and-delivery-behavior)
- [Official SDKs](#8-official-sdks)
    - [Packages](#81-packages)
    - [Shared Behavior](#82-shared-behavior)
    - [Webhook Verification Helpers](#83-webhook-verification-helpers)
- [Integration Notes](#9-integration-notes)

## 2) Conventions

### Base URL

Use your environment base URL, for example:

- `https://api.adsefid.com`

### Authentication

All endpoints in this document require:

- Header: `X-API-KEY: <your_api_key>`

If missing/invalid, API returns an error response with code `2018 (UNAUTHORIZED)` or `2005 (INVALID_API_KEY)`.

### Content Types

- Request JSON: `application/json`
- File upload: `multipart/form-data`
- Response JSON: `application/json`

### Naming and Types

- JSON field names are `snake_case`.
- IDs are UUID strings unless explicitly noted.
- Date/time fields are ISO-8601 timestamps.
- `segment_count` is encoding-sensitive.

### Response Envelope

Success:

```json
{
    "status": "success",
    "data": {}
}
```

Error:

```json
{
    "status": "error",
    "error": {
        "code": 2024,
        "name": "INVALID_PARAMETER",
        "details": {
            "errors": {
                "line_number": {
                    "code": 2012,
                    "name": "EMPTY_LINE"
                }
            }
        }
    }
}
```

Notes:

- `error.code` is a numeric `WebServiceResponseCode`.
- `error.name` is the uppercase symbolic name.
- `error.details` has the same shape on every endpoint:
    - `errors` — a map of field name to `{ "code", "name" }`. Keys use request field names in snake_case; nested
      collection fields use flat paths such as `messages[0].local_id`.
    - `items` — per-item errors on the multi-recipient endpoints, each `{ "index": n, "errors": { … } }`.
- Each entry carries the same pair as the envelope: `code` is a numeric `WebServiceResponseCode` and `name` is its
  symbolic name. Neither is localized, so both are safe to branch on.
- `errors`, `items` and `details` itself are omitted when empty. An error with nothing to add carries no `details` key.
- A body that cannot be parsed is reported as `2024` with `details.errors.request` set to `INVALID_PARAMETER`.

### Partial Acceptance

`POST /v1/sms/bulk`, `POST /v1/sms/p2p`, `POST /v1/messenger/bulk` and `POST /v1/messenger/p2p` send every item they can
and report the rest per item.

- Rejected items come back in the same `200` response with their own `status` and `cost: 0`, and are counted in
  `counts`.
- Per-item codes: `2014 (INVALID_RECEPTOR)`, `2029 (INVALID_LOCAL_IDS)`, `2007 (DUPLICATE_LOCAL_ID)`,
  `2013 (EMPTY_MESSAGE)`, `2016 (MESSAGE_TOO_LARGE)`, `2025 (RECEPTOR_BLACKLISTED)`.
- A `local_id` repeated inside one request sends the first item and rejects the later ones. A `local_id` already used by
  an earlier send rejects the item carrying it.
- If every item is rejected, the request fails with `2024 (INVALID_PARAMETER)` and `details.items` lists one entry per
  rejected index:

```json
{
    "status": "error",
    "error": {
        "code": 2024,
        "name": "INVALID_PARAMETER",
        "details": {
            "items": [
                {
                    "index": 0,
                    "errors": {
                        "receptor": {
                            "code": 2014,
                            "name": "INVALID_RECEPTOR"
                        }
                    }
                },
                {
                    "index": 2,
                    "errors": {
                        "local_id": {
                            "code": 2007,
                            "name": "DUPLICATE_LOCAL_ID"
                        }
                    }
                }
            ]
        }
    }
}
```

- When every item was rejected for the same reason, that reason's own code replaces `2024`:
  `2020 (ALL_RECEPTORS_BLACKLISTED)` for a wholly blacklisted request, `2026 (INVALID_LINK_IN_MESSAGE)` when
  the line or profile forbids links. `details.items` is present either way.
- Request-level problems still reject the whole request with `2024` and `details.errors`, as do `single` and `template`.

### Support & Limits

For information about your API request quotas (including maximum requests per minute, hour, and day) and message sending
limits, please contact our support team. They can provide your account-specific thresholds, help with limit adjustments,
and answer any related questions.

By default, the sending limit is 500 units per second. This limit is shared across SMS and Messenger traffic. SMS
traffic is counted by segment, so a multi-segment SMS consumes one unit per segment.

Support channels:

- Website: `adsefid.com`
- Ticket: through your panel

## 3) Enums

### 3.1 `LineSelector`

| Value | Name                              |
|------:|-----------------------------------|
|     0 | `PromotionalSendBased`            |
|     1 | `PromotionalDeliverBased`         |
|     2 | `BulkServiceSendBased`            |
|     3 | `BulkServiceDeliverBased`         |
|     4 | `CustomerClubServiceSendBased`    |
|     5 | `CustomerClubServiceDeliverBased` |

### 3.2 `WebServiceMessageStatus`

| Value | Name                   | Final         |
|------:|------------------------|---------------|
|  1000 | `SCHEDULED`            | No            |
|  1001 | `SENDING`              | No            |
|  1002 | `DELIVERED`            | Yes           |
|  1003 | `UNDELIVERED`          | After 3 hours |
|  1004 | `CANCELED`             | Yes           |
|  1005 | `SENT_TO_OPERATOR`     | No            |
|  1006 | `BLACKLISTED`          | Yes           |
|  1007 | `PROVIDER_ERROR`       | After 3 hours |
|  1008 | `PENDING_APPROVAL`     | No            |
|  1009 | `REJECTED`             | Yes           |
|  1010 | `INVALID_SENDER`       | Yes           |
|  1011 | `INVALID_ATTACHMENT`   | Yes           |
|  1012 | `FORBIDDEN_WORD`       | Yes           |
|  1013 | `LINK_NOT_ALLOWED`     | Yes           |
|  1014 | `INVALID_RECEIVER`     | Yes           |
|  1015 | `UNDELIVERABLE`        | After 6 hours |
|  1016 | `SENDER_LIMIT_REACHED` | No            |
|  1999 | `UNKNOWN`              | No            |

### 3.3 `WebServiceCode`

Used in `status` fields and `counts` keys.

- `1000-1999`: message status values
- `2000+`: error code values

In JSON `counts`, keys are serialized as strings:

```json
{
    "counts": {
        "1000": 120,
        "2025": 8
    }
}
```

### 3.4 `WebServiceResponseCode` (Used by These Endpoints)

| Code | Name                               | HTTP Status |
|-----:|------------------------------------|------------:|
| 2000 | `INTERNAL_ERROR`                   |         500 |
| 2001 | `INVALID_PLAN`                     |         400 |
| 2002 | `LINE_NOT_FOUND`                   |         404 |
| 2003 | `TOO_MANY_RECEPTORS`               |         400 |
| 2004 | `INVALID_LINE`                     |         400 |
| 2005 | `INVALID_API_KEY`                  |         401 |
| 2006 | `IP_NOT_ALLOWED`                   |         403 |
| 2007 | `DUPLICATE_LOCAL_ID`               |         409 |
| 2008 | `USER_INFORMATION_NOT_FOUND`       |         404 |
| 2009 | `EMPTY_RECEPTORS`                  |         400 |
| 2010 | `INVALID_RECEPTORS`                |         400 |
| 2011 | `EMPTY_BODY`                       |         400 |
| 2012 | `EMPTY_LINE`                       |         400 |
| 2013 | `EMPTY_MESSAGE`                    |         400 |
| 2014 | `INVALID_RECEPTOR`                 |         400 |
| 2015 | `EMPTY_RECEPTOR`                   |         400 |
| 2016 | `MESSAGE_TOO_LARGE`                |         413 |
| 2017 | `INVALID_LINE_SELECTOR`            |         400 |
| 2018 | `UNAUTHORIZED`                     |         401 |
| 2019 | `INVALID_SEND_RANGE`               |         400 |
| 2020 | `ALL_RECEPTORS_BLACKLISTED`        |         403 |
| 2021 | `MESSAGE_CONTAINS_FORBIDDEN_WORDS` |         400 |
| 2022 | `NOT_ENOUGH_CREDIT`                |         402 |
| 2023 | `DUPLICATE_TAG`                    |         409 |
| 2024 | `INVALID_PARAMETER`                |         400 |
| 2025 | `RECEPTOR_BLACKLISTED`             |         403 |
| 2026 | `INVALID_LINK_IN_MESSAGE`          |         400 |
| 2027 | `TEMPLATE_NOT_APPROVED`            |         400 |
| 2028 | `INVALID_TEMPLATE_PARAMETER`       |         400 |
| 2029 | `INVALID_LOCAL_IDS`                |         400 |
| 2030 | `EMPTY_LOCAL_IDS`                  |         400 |
| 2031 | `EMPTY_MESSAGE_IDS`                |         400 |
| 2032 | `INVALID_SMS_TYPE`                 |         400 |
| 2033 | `LINE_NOT_ACTIVE`                  |         400 |
| 2034 | `LINE_EXPIRED`                     |         410 |
| 2035 | `MESSAGE_LIMIT_REACHED`            |         429 |
| 2036 | `REQUEST_LIMIT_REACHED`            |         429 |
| 2037 | `INVALID_SEND_TIME`                |         400 |
| 2038 | `INVALID_EXPIRY`                   |         400 |
| 2039 | `INVALID_TEMPLATE_ID`              |         400 |
| 2040 | `PROFILE_NOT_FOUND`                |         400 |
| 2041 | `PROFILE_EXPIRED`                  |         400 |
| 2042 | `FILE_NOT_FOUND`                   |         400 |
| 2043 | `INVALID_FILE`                     |         400 |
| 2044 | `ACCESS_DENIED`                    |         403 |
| 2045 | `REJECTED`                         |         400 |
| 2046 | `INVALID_MESSAGE_IDS`              |         400 |
| 2047 | `FILE_TOO_LARGE`                   |         413 |

### 3.5 `TemplateState`

Template states are lowercase strings. Complete supported set:

- `pendingapproval`
- `approved`
- `rejected`

### 3.6 `TemplateParameterType`

Template parameter types are lowercase strings. Complete supported set:

- `string`
- `number`

## 4) SMS API

### 4.1 `POST /v1/sms/single`

Send one SMS message.

Request body:

| Field           | Type     | Required | Rules                                                             |
|-----------------|----------|---------:|-------------------------------------------------------------------|
| `receptor`      | string   |      Yes | Valid phone number                                                |
| `line_number`   | string   |      Yes | Must belong to authenticated user                                 |
| `line_selector` | int      |       No | See `LineSelector` enum                                           |
| `message`       | string   |      Yes | Max length 900                                                    |
| `send_time`     | datetime |       No | Must be >= now - 1 minute                                         |
| `local_id`      | string   |       No | 1-36 ASCII letters/digits; `- _ . :` only inside; unique per user |
| `hide`          | bool     |       No | Defaults to `false`                                               |

Example:

```bash
curl -X POST "https://api.adsefid.com/v1/sms/single" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: <API_KEY>" \
  -d '{
    "receptor": "98912****567",
    "line_number": "983000XXX",
    "line_selector": 0,
    "message": "سلام، پیام تست",
    "send_time": "2026-04-04T10:30:00+03:30",
    "local_id": "order-10001",
    "hide": false
  }'
```

Success example:

```json
{
    "status": "success",
    "data": {
        "group_id": "0195ff8c-5423-7aa2-9c2b-d9c3b3f84d12",
        "local_id": "order-10001",
        "status": 1000,
        "line_number": "983000XXX",
        "line_selector": 0,
        "cost": 120,
        "receptor": "98912****567",
        "send_time": "2026-04-04T10:30:00+03:30",
        "message_id": "0195ff8c-55aa-7e74-a337-c2fd4f6e85c5",
        "segment_count": 1,
        "hide": false
    }
}
```

Common errors: `2014`, `2002`, `2033`, `2034`, `2007`, `2035`, `2020`, `2022`, `2026`.

---

### 4.2 `POST /v1/sms/bulk`

Send one shared message to multiple recipients.

Request body:

| Field                  | Type     | Required | Rules                                                                     |
|------------------------|----------|---------:|---------------------------------------------------------------------------|
| `receptors`            | array    |      Yes | At least 1 item                                                           |
| `receptors[].receptor` | string   |      Yes | Valid phone number                                                        |
| `receptors[].local_id` | string   |       No | 1-36 ASCII letters/digits; `- _ . :` only inside; unique per user/request |
| `receptors[].hide`     | bool     |       No | Defaults to `false`                                                       |
| `message`              | string   |      Yes | Max length 900                                                            |
| `send_time`            | datetime |       No | Must be >= now - 1 minute and inside provider send window                 |
| `line_number`          | string   |      Yes | Sender line                                                               |
| `line_selector`        | int      |       No | See `LineSelector` enum                                                   |

Item-level rule violations do not fail the request — see [Partial Acceptance](#partial-acceptance).

Example:

```json
{
    "receptors": [
        {
            "receptor": "98912****567",
            "local_id": "b-1",
            "hide": false
        },
        {
            "receptor": "98993****111",
            "local_id": "b-2",
            "hide": true
        }
    ],
    "message": "اطلاع رسانی سرویس",
    "send_time": "2026-04-04T11:00:00+03:30",
    "line_number": "983000XXX",
    "line_selector": 2
}
```

Success example (partial accepted is possible):

```json
{
    "status": "success",
    "data": {
        "group_id": "0195ff8f-cf67-7c51-8a2a-9d2ea64eb150",
        "receptors": [
            {
                "message_id": "0195ff8f-d045-7598-86db-a4f9dff5328f",
                "receptor": "98912****567",
                "local_id": "b-1",
                "status": 1000,
                "hide": false,
                "cost": 120
            },
            {
                "message_id": null,
                "receptor": "98993****111",
                "local_id": "b-2",
                "status": 2025,
                "hide": true,
                "cost": 0
            }
        ],
        "message": "اطلاع رسانی سرویس",
        "segment_count": 1,
        "send_time": "2026-04-04T11:00:00+03:30",
        "line_number": "983000XXX",
        "line_selector": 2,
        "counts": {
            "1000": 1,
            "2025": 1
        },
        "total_count": 2,
        "total_cost": 120
    }
}
```

Common errors: `2024` (all items rejected), `2020`, `2019`, `2035`, `2022`. Per-item: `2014`, `2029`, `2007`, `2025`.

---

### 4.3 `POST /v1/sms/p2p`

Send per-recipient custom messages.

Request body:

| Field                 | Type     | Required | Rules                                            |
|-----------------------|----------|---------:|--------------------------------------------------|
| `messages`            | array    |      Yes | At least 1 item                                  |
| `messages[].receptor` | string   |      Yes | Valid phone number                               |
| `messages[].message`  | string   |      Yes | Max length 900                                   |
| `messages[].local_id` | string   |       No | 1-36 ASCII letters/digits; `- _ . :` only inside |
| `messages[].hide`     | bool     |       No | Defaults to `false`                              |
| `send_time`           | datetime |       No | Must be >= now - 1 minute                        |
| `line_number`         | string   |      Yes | Sender line                                      |
| `line_selector`       | int      |       No | See `LineSelector` enum                          |

Item-level rule violations do not fail the request — see [Partial Acceptance](#partial-acceptance).

Example:

```json
{
    "messages": [
        {
            "receptor": "98912****567",
            "message": "سلام علی",
            "local_id": "p2p-1"
        },
        {
            "receptor": "98993****111",
            "message": "سلام رضا",
            "local_id": "p2p-2"
        }
    ],
    "send_time": "2026-04-04T11:10:00+03:30",
    "line_number": "983000XXX",
    "line_selector": 0
}
```

Success example:

```json
{
    "status": "success",
    "data": {
        "group_id": "0195ff95-79bf-7b1a-bfa1-a696a9f5e40a",
        "messages": [
            {
                "message_id": "0195ff95-7a2b-79ac-b993-ac37b6dd63b0",
                "receptor": "98912****567",
                "status": 1000,
                "local_id": "p2p-1",
                "message": "سلام علی",
                "hide": false,
                "segment_count": 1,
                "cost": 120
            },
            {
                "message_id": null,
                "receptor": "98993****111",
                "status": 2014,
                "local_id": "p2p-2",
                "message": "سلام رضا",
                "hide": false,
                "segment_count": 0,
                "cost": 0
            }
        ],
        "send_time": "2026-04-04T11:10:00+03:30",
        "line_number": "983000XXX",
        "line_selector": 0,
        "total_cost": 120,
        "counts": {
            "1000": 1,
            "2014": 1
        }
    }
}
```

Common errors: `2024` (all items rejected), `2020`, `2035`, `2022`. Per-item: `2014`, `2029`, `2007`, `2013`, `2016`,
`2025`.

---

### 4.4 `POST /v1/sms/template`

Send an approved SMS template.

Request body:

| Field           | Type     | Required | Rules                                                             |
|-----------------|----------|---------:|-------------------------------------------------------------------|
| `template_id`   | string   |      Yes | Must exist and be approved for user                               |
| `parameters`    | object   |      Yes | Must satisfy template placeholders                                |
| `receptor`      | string   |      Yes | Valid phone number                                                |
| `local_id`      | string   |       No | 1-36 ASCII letters/digits; `- _ . :` only inside; unique per user |
| `line_number`   | string   |      Yes | Sender line                                                       |
| `line_selector` | int      |       No | See `LineSelector` enum                                           |
| `expiry_date`   | datetime |       No | Must be >= now + 1 minute                                         |

Example:

```json
{
    "template_id": "otp_login",
    "parameters": {
        "code": "459122",
        "minutes": 2
    },
    "receptor": "98912****567",
    "local_id": "otp-90001",
    "line_number": "983000XXX",
    "line_selector": 2,
    "expiry_date": "2026-04-04T12:00:00+03:30"
}
```

Success example:

```json
{
    "status": "success",
    "data": {
        "group_id": "0195ff9f-f8db-747a-8c31-4d6a954f4a8d",
        "message_id": "0195ff9f-f9cf-7d62-a49e-2ca4db9a90f2",
        "status": 1000,
        "local_id": "otp-90001",
        "line_number": "983000XXX",
        "template_id": "otp_login",
        "send_time": "2026-04-04T11:40:00+03:30",
        "expiry_date": "2026-04-04T12:00:00+03:30",
        "line_selector": 2,
        "cost": 120,
        "receptor": "98912****567",
        "message": "کد ورود شما: ۴۵۹۱۲۲ - اعتبار: ۲ دقیقه",
        "segment_count": 1,
        "parameters": {
            "code": "459122",
            "minutes": 2
        }
    }
}
```

Common errors: `2027`, `2028`, `2039`, `2007`, `2026`, `2021`.

---

### 4.5 `GET /v1/sms/status`

Get SMS delivery status by message IDs and/or local IDs.

Query params:

| Param         | Type            | Required | Rules                                              |
|---------------|-----------------|---------:|----------------------------------------------------|
| `message_ids` | CSV UUID list   |       No | At least one of `message_ids`/`local_ids` required |
| `local_ids`   | CSV string list |       No | At least one of `message_ids`/`local_ids` required |

Rules:

- Combined count of distinct `message_ids` + `local_ids` must be <= 2000.
- Parameters accept comma-separated values.

Example:

```bash
curl "https://api.adsefid.com/v1/sms/status?message_ids=0195ff8c-55aa-7e74-a337-c2fd4f6e85c5,0195ff9f-f9cf-7d62-a49e-2ca4db9a90f2&local_ids=order-10001,otp-90001" \
  -H "X-API-KEY: <API_KEY>"
```

Success example:

```json
{
    "status": "success",
    "data": {
        "receptors": [
            {
                "message_id": "0195ff8c-55aa-7e74-a337-c2fd4f6e85c5",
                "local_id": "order-10001",
                "status": 1001,
                "receptor": "98912****567",
                "send_time": "2026-04-04T10:30:00+03:30",
                "delivery_time": null
            }
        ]
    }
}
```

Common errors: `2030`, `2031`, `2003`, `2005`.
A malformed or empty `message_ids` entry is reported per index as `2046 INVALID_MESSAGE_IDS`.

---

### 4.6 `POST /v1/sms/cancel`

Cancel queued SMS messages by IDs.

Request body:

| Field         | Type          |    Required | Rules                                                       |
|---------------|---------------|------------:|-------------------------------------------------------------|
| `message_ids` | array(UUID)   | Conditional | At least one of `message_ids`/`local_ids` must be non-empty |
| `local_ids`   | array(string) | Conditional | At least one of `message_ids`/`local_ids` must be non-empty |

Example:

```json
{
    "message_ids": [
        "0195ff8c-55aa-7e74-a337-c2fd4f6e85c5"
    ],
    "local_ids": [
        "order-10001"
    ]
}
```

Success example:

```json
{
    "status": "success",
    "data": {
        "cancelled_messages": [
            {
                "message_id": "0195ff8c-55aa-7e74-a337-c2fd4f6e85c5",
                "local_id": "order-10001",
                "status": 1004
            }
        ],
        "failed_to_cancel": []
    }
}
```

Notes:

- Only messages currently in queue can be canceled.
- Non-queued messages are returned in `failed_to_cancel` with their current status.

Common errors: `2029` (invalid ids not found for current user), `2022`. On `2029`, `details.errors` is keyed by each
rejected id, each carrying `2029 INVALID_LOCAL_IDS`.

---

### 4.7 `GET /v1/sms/receive`

Fetch received inbound SMS for a line.

Query params:

| Param         | Type     | Required | Rules                           |
|---------------|----------|---------:|---------------------------------|
| `line_number` | string   |      Yes | User-accessible line            |
| `count`       | int      |       No | Must be `< 500` (default `100`) |
| `since`       | datetime |       No | Must be in past                 |

Example:

```bash
curl "https://api.adsefid.com/v1/sms/receive?line_number=983000XXX&count=50&since=2026-04-04T00:00:00+03:30" \
  -H "X-API-KEY: <API_KEY>"
```

Success example:

```json
{
    "status": "success",
    "data": {
        "messages": [
            {
                "message": "سلام",
                "line_number": "983000XXX",
                "receive_date": "2026-04-04T09:32:01+03:30",
                "sender": "98912****567"
            }
        ]
    }
}
```

Common errors: `2024`, `2004`, `2005`.

## 5) Messenger API

### 5.1 `POST /v1/messenger/single`

Send one messenger message.

Request body:

| Field       | Type        | Required | Rules                                                             |
|-------------|-------------|---------:|-------------------------------------------------------------------|
| `message`   | string      |      Yes | Max length 4000                                                   |
| `receptor`  | string      |      Yes | Valid phone number                                                |
| `profile`   | UUID string |      Yes | Must exist for current user                                       |
| `hide`      | bool        |       No | Default `false`                                                   |
| `file_id`   | UUID string |       No | Must be uploaded by current user                                  |
| `send_time` | datetime    |       No | Must be >= now - 1 minute and in provider send window             |
| `local_id`  | string      |       No | 1-36 ASCII letters/digits; `- _ . :` only inside; unique per user |

Example:

```json
{
    "message": "سلام از پیام رسان",
    "receptor": "98912****567",
    "profile": "b72ea7f4-44b4-4f5e-91ef-91816ae5f88a",
    "hide": false,
    "file_id": "18dc580b-c598-4649-b2f4-dde1e5e35a57",
    "send_time": "2026-04-04T11:45:00+03:30",
    "local_id": "m-single-1"
}
```

Success example:

```json
{
    "status": "success",
    "data": {
        "group_id": "0195ffa8-3420-73fd-b59d-ec8c5d32c14d",
        "message_id": "0195ffa8-34da-7397-a091-9f36b1d2f7ca",
        "status": 1000,
        "receptor": "98912****567",
        "local_id": "m-single-1",
        "hide": false,
        "cost": 80,
        "send_time": "2026-04-04T11:45:00+03:30",
        "profile": "b72ea7f4-44b4-4f5e-91ef-91816ae5f88a",
        "messenger": "rubika"
    }
}
```

Common errors: `2040`, `2041`, `2042`, `2007`, `2014`, `2035`, `2022`, `2026`.

---

### 5.2 `POST /v1/messenger/bulk`

Send same messenger message to multiple recipients.

Request body:

| Field                  | Type        | Required | Rules                                                                     |
|------------------------|-------------|---------:|---------------------------------------------------------------------------|
| `receptors`            | array       |      Yes | At least 1 item                                                           |
| `receptors[].receptor` | string      |      Yes | Valid phone number                                                        |
| `receptors[].local_id` | string      |       No | 1-36 ASCII letters/digits; `- _ . :` only inside; unique per user/request |
| `receptors[].hide`     | bool        |       No | Default `false`                                                           |
| `message`              | string      |      Yes | Max length 4000                                                           |
| `send_time`            | datetime    |       No | Must be valid time range                                                  |
| `profile`              | UUID string |      Yes | Active profile for user                                                   |
| `file_id`              | UUID string |       No | Existing uploaded file                                                    |

Item-level rule violations do not fail the request — see [Partial Acceptance](#partial-acceptance).

Example:

```json
{
    "receptors": [
        {
            "receptor": "98912****567",
            "local_id": "mb-1"
        },
        {
            "receptor": "98993****111",
            "local_id": "mb-2"
        }
    ],
    "message": "ارسال گروهی پیام رسان",
    "send_time": "2026-04-04T12:10:00+03:30",
    "profile": "b72ea7f4-44b4-4f5e-91ef-91816ae5f88a"
}
```

Success example:

```json
{
    "status": "success",
    "data": {
        "group_id": "0195ffac-cb9d-79e0-8ab6-f451afafdf2d",
        "receptors": [
            {
                "message_id": "0195ffac-cc70-72d7-88a6-9f2347305b1b",
                "receptor": "98912****567",
                "local_id": "mb-1",
                "hide": false,
                "status": 1000,
                "cost": 80
            }
        ],
        "message": "ارسال گروهی پیام رسان",
        "send_time": "2026-04-04T12:10:00+03:30",
        "total_count": 1,
        "total_cost": 80,
        "counts": {
            "1000": 1
        },
        "profile": "b72ea7f4-44b4-4f5e-91ef-91816ae5f88a",
        "messenger": "rubika"
    }
}
```

Common errors: `2040`, `2041`, `2042`, `2024` (all items rejected), `2020`, `2035`, `2022`. Per-item: `2014`, `2029`,
`2007`, `2025`.

---

### 5.3 `POST /v1/messenger/p2p`

Send per-recipient custom messenger messages.

Request body:

| Field                  | Type        | Required | Rules                                            |
|------------------------|-------------|---------:|--------------------------------------------------|
| `receptors`            | array       |      Yes | At least 1 item                                  |
| `receptors[].receptor` | string      |      Yes | Valid phone number                               |
| `receptors[].message`  | string      |      Yes | Max length 4000                                  |
| `receptors[].local_id` | string      |       No | 1-36 ASCII letters/digits; `- _ . :` only inside |
| `receptors[].hide`     | bool        |       No | Default `false`                                  |
| `send_time`            | datetime    |       No | Valid send range                                 |
| `profile`              | UUID string |      Yes | Active profile                                   |
| `file_id`              | UUID string |       No | Existing uploaded file                           |

Item-level rule violations do not fail the request — see [Partial Acceptance](#partial-acceptance).

Example:

```json
{
    "receptors": [
        {
            "receptor": "98912****567",
            "message": "سلام علی",
            "local_id": "mp2p-1"
        },
        {
            "receptor": "98993****111",
            "message": "سلام رضا",
            "local_id": "mp2p-2"
        }
    ],
    "send_time": "2026-04-04T12:30:00+03:30",
    "profile": "b72ea7f4-44b4-4f5e-91ef-91816ae5f88a"
}
```

Success example:

```json
{
    "status": "success",
    "data": {
        "group_id": "0195ffb2-aa5e-7d42-953a-18f34d2f71e1",
        "receptors": [
            {
                "message_id": "0195ffb2-ab31-73f5-b8d7-afe7b7d2f1fd",
                "receptor": "98912****567",
                "message": "سلام علی",
                "local_id": "mp2p-1",
                "hide": false,
                "status": 1000,
                "cost": 80
            }
        ],
        "send_time": "2026-04-04T12:30:00+03:30",
        "total_count": 1,
        "total_cost": 80,
        "counts": {
            "1000": 1
        },
        "profile": "b72ea7f4-44b4-4f5e-91ef-91816ae5f88a",
        "messenger": "bale"
    }
}
```

Common errors: `2040`, `2041`, `2042`, `2024` (all items rejected), `2020`, `2035`, `2022`. Per-item: `2014`, `2029`,
`2007`, `2013`, `2016`, `2025`, `2026`.

---

### 5.4 `POST /v1/messenger/file`

Upload a messenger media file and get a reusable `file_id`.

Request:

- Content type: `multipart/form-data`
- Field name: `file`

Constraints:

- Max size: 15 MB
- Allowed MIME types include:
    - Images: `image/jpeg`, `image/png`, `image/gif`
    - PDF: `application/pdf`
    - Audio: `audio/mpeg`, `audio/mp4`, `audio/aac`, `audio/ogg`, `audio/webm`, `audio/wav`, `audio/x-wav`,`audio/flac`,
      `audio/opus`
    - Video: `video/mp4`, `video/webm`, `video/ogg`, `video/quicktime`, `video/x-msvideo`, `video/x-matroska`

Example:

```bash
curl -X POST "https://api.adsefid.com/v1/messenger/file" \
  -H "X-API-KEY: <API_KEY>" \
  -F "file=@./brochure.pdf"
```

Success example:

```json
{
    "status": "success",
    "data": {
        "file_id": "18dc580b-c598-4649-b2f4-dde1e5e35a57"
    }
}
```

Common errors: `2043`, `2047` (over 15 MB), `2001`, `2018`.

---

### 5.5 `POST /v1/messenger/cancel`

Cancel queued messenger messages.

Request body:

| Field         | Type          |    Required | Rules                                     |
|---------------|---------------|------------:|-------------------------------------------|
| `message_ids` | array(UUID)   | Conditional | At least one of `message_ids`/`local_ids` |
| `local_ids`   | array(string) | Conditional | At least one of `message_ids`/`local_ids` |

Success example:

```json
{
    "status": "success",
    "data": {
        "cancelled_messages": [
            {
                "message_id": "0195ffa8-34da-7397-a091-9f36b1d2f7ca",
                "local_id": "m-single-1",
                "status": 1004
            }
        ],
        "failed_to_cancel": []
    }
}
```

Common errors: `2029`, `2022`. On `2029`, `details.errors` is keyed by each rejected id, each carrying
`2029 INVALID_LOCAL_IDS`.

---

### 5.6 `POST /v1/messenger/template`

Send an approved template via messenger profile.

Request body:

| Field         | Type        | Required | Rules                                                             |
|---------------|-------------|---------:|-------------------------------------------------------------------|
| `template_id` | string      |      Yes | Must be approved for user                                         |
| `parameters`  | object      |      Yes | Must satisfy template placeholders                                |
| `receptor`    | string      |      Yes | Valid phone number                                                |
| `local_id`    | string      |       No | 1-36 ASCII letters/digits; `- _ . :` only inside; unique per user |
| `profile`     | UUID string |      Yes | Active profile                                                    |
| `expiry_date` | datetime    |       No | Must be >= now + 1 minute                                         |

Example:

```json
{
    "template_id": "invoice_notice",
    "parameters": {
        "invoice_id": "INV-1043",
        "amount": "250000"
    },
    "receptor": "98912****567",
    "local_id": "mt-1",
    "profile": "b72ea7f4-44b4-4f5e-91ef-91816ae5f88a",
    "expiry_date": "2026-04-04T13:00:00+03:30"
}
```

Success example:

```json
{
    "status": "success",
    "data": {
        "group_id": "0195ffb8-ef3d-71d8-ba37-a5f6bd8fc603",
        "message_id": "0195ffb8-f03f-74d0-a97d-e90ae2f2b2d0",
        "status": 1000,
        "local_id": "mt-1",
        "template_id": "invoice_notice",
        "send_time": "2026-04-04T12:50:00+03:30",
        "expiry_date": "2026-04-04T13:00:00+03:30",
        "cost": 80,
        "receptor": "98912****567",
        "message": "فاکتور INV-1043 به مبلغ 250000 آماده است",
        "profile": "b72ea7f4-44b4-4f5e-91ef-91816ae5f88a",
        "messenger": "rubika",
        "parameters": {
            "invoice_id": "INV-1043",
            "amount": "250000"
        }
    }
}
```

Common errors: `2027`, `2028`, `2040`, `2041`, `2007`, `2026`, `2021`.

---

### 5.7 `GET /v1/messenger/status`

Get messenger message status by `message_ids` and/or `local_ids`.

Query params:

- `message_ids`: comma-separated UUID list
- `local_ids`: comma-separated string list

Rules:

- At least one of the two params is required.
- Max total distinct IDs per request: `2000`.

Success example:

```json
{
    "status": "success",
    "data": {
        "receptors": [
            {
                "message_id": "0195ffa8-34da-7397-a091-9f36b1d2f7ca",
                "local_id": "m-single-1",
                "status": 1002,
                "receptor": "98912****567",
                "send_time": "2026-04-04T11:45:00+03:30",
                "delivery_time": "2026-04-04T11:45:08+03:30"
            }
        ]
    }
}
```

Common errors: `2030`, `2031`, `2003`, `2005`.
A malformed or empty `message_ids` entry is reported per index as `2046 INVALID_MESSAGE_IDS`.

## 6) User API

### 6.1 `GET /v1/user/info`

Get authenticated user account information.

Success example:

```json
{
    "status": "success",
    "data": {
        "name": "علی رضایی",
        "company_name": "شرکت نمونه",
        "credit_left": 1250000,
        "email": "john@example.com",
        "phone": "98912****567",
        "account_status": "active"
    }
}
```

Common errors: `2018`.

---

### 6.2 `GET /v1/user/lines`

Get all sender lines available to the authenticated user.

Success example:

```json
{
    "status": "success",
    "data": [
        {
            "line_number": "983000XXX",
            "line_selector": 0,
            "line_name": "Main Promotional",
            "enabled": true
        },
        {
            "line_number": "985000XXX",
            "line_selector": 2,
            "line_name": "Service Bulk",
            "enabled": false
        }
    ]
}
```

Common errors: `2005`, `2000`.

---

### 6.3 `GET /v1/user/profiles`

Get messenger profiles available to the authenticated user.

Success example:

```json
{
    "status": "success",
    "data": [
        {
            "id": "b72ea7f4-44b4-4f5e-91ef-91816ae5f88a",
            "name": "پشتیبانی روبیکا",
            "messenger": "rubika"
        }
    ]
}
```

Common errors: `2005`, `2000`.

---

### 6.4 `GET /v1/user/templates`

Get non-deleted SMS and messenger templates owned by the authenticated API-key user. Results include all states by
default and are ordered newest first with a stable tie-breaker.

Query parameters:

| Field   | Type   | Required | Rules                                                               |
|---------|--------|----------|---------------------------------------------------------------------|
| `state` | string | No       | One of `pendingapproval`, `approved`, or `rejected`; lowercase only |
| `skip`  | int    | No       | Default `0`; must be non-negative                                   |
| `take`  | int    | No       | Default `50`; must be between `1` and `100`, inclusive              |

`total` is count after optional state filtering and before pagination. `template_id` is same string accepted by both
`POST /v1/sms/template` and `POST /v1/messenger/template`. Parameter names are returned exactly as defined by user;
parameter values use complete `TemplateParameterType` set documented above. `state` uses complete `TemplateState` set.

Success example:

```json
{
    "status": "success",
    "data": {
        "items": [
            {
                "template_id": "payment-notice",
                "content": "Code: {{OTPCode:string}}, amount: {{amount:number}}",
                "parameters": {
                    "OTPCode": "string",
                    "amount": "number"
                },
                "state": "approved",
                "description": null,
                "created_at": "2026-09-02T10:30:00+03:30",
                "updated_at": "2026-09-02T11:00:00+03:30"
            }
        ],
        "total": 1
    }
}
```

Empty-page example:

```json
{
    "status": "success",
    "data": {
        "items": [],
        "total": 0
    }
}
```

Common errors: `2018` for missing or invalid API key; `2024` for invalid `state`, `skip`, or `take`.

## 7) Outgoing Webhooks

### 7.1 Events

Three outbound webhook streams are produced by the platform:

- Receive event (`receive`): inbound SMS received
- SMS status event (`status`): SMS delivery status changed
- Messenger status event (`messenger.status`): messenger delivery status changed

### 7.2 HTTP Request

- Method: `POST`
- Content-Type: `application/json`
- Timeout: `10 seconds`
- Success condition: any `2xx` response

Headers sent:

| Header                      | Description                                             |
|-----------------------------|---------------------------------------------------------|
| `X-Atlas-Webhook-Id`        | Webhook delivery public UUID                            |
| `X-Atlas-Webhook-Event`     | Event name (`receive`, `status`, or `messenger.status`) |
| `X-Atlas-Webhook-Attempt`   | Attempt number (starts at 1)                            |
| `X-Atlas-Webhook-Timestamp` | Unix timestamp (seconds)                                |
| `X-Atlas-Webhook-Signature` | Signature format `v1=<base64_hmac_sha256>`              |
| `User-Agent`                | `AtlasSMS-Webhooks/1.0`                                 |

### 7.3 Signature Verification

Signing input:

```
<timestamp>.<raw_request_body>
```

Algorithm:

- HMAC-SHA256 using your webhook endpoint secret bytes
- Base64 encode output
- Compare with header value after removing `v1=` prefix

Pseudo code:

```text
hex = HMAC_SHA256( $"{timestamp}.{raw_body}", secret )
base64 = BASE64(hex_to_bytes(hex))
expected_signature = "v1=" + base64
```

Important:

- Verify on the exact raw body bytes.
- Reject old timestamps according to your replay policy.
- It is highly recommended to validate signature + timestamp for every webhook request before processing payload data.

### 7.4 Receive Event Payload

Header:

- `X-Atlas-Webhook-Event: receive`

Body:

```json
{
    "id": "0195ffd0-7e08-7d4d-8eef-dd4f7ca5e6fd",
    "type": "receive",
    "occurred_at": "2026-04-04T12:00:00+03:30",
    "attempt": 1,
    "version": "1",
    "data": [
        {
            "id": 1543201,
            "line_number": "983000XXX",
            "sender": "98912****567",
            "message": "سلام",
            "receive_date": "2026-04-04T11:59:58+03:30"
        }
    ]
}
```

### 7.5 Status Update Event Payload

Header:

- `X-Atlas-Webhook-Event: status`

Body:

```json
{
    "id": "0195ffd2-76d4-7d10-9d40-c0d2fc61f774",
    "type": "status",
    "occurred_at": "2026-04-04T12:05:00+03:30",
    "attempt": 1,
    "version": "1",
    "data": [
        {
            "id": "0195ff8c-55aa-7e74-a337-c2fd4f6e85c5",
            "local_id": "order-10001",
            "status_delivery": 1002,
            "delivery_time": "2026-04-04T12:04:54+03:30"
        }
    ]
}
```

### 7.6 Messenger Status Event Payload

Header:

- `X-Atlas-Webhook-Event: messenger.status`

Body:

```json
{
    "id": "0195ffd2-76d4-7d10-9d40-c0d2fc61f774",
    "type": "messenger.status",
    "occurred_at": "2026-04-04T12:05:00+03:30",
    "attempt": 1,
    "version": "1",
    "data": [
        {
            "id": "0195ff8c-55aa-7e74-a337-c2fd4f6e85c5",
            "local_id": "order-10001",
            "status_delivery": 1002,
            "delivery_time": "2026-04-04T12:04:54+03:30"
        }
    ]
}
```

### 7.7 Retry and Delivery Behavior

- A failed attempt is retried with backoff: `5s`, `60s`, `120s`, `360s`, `600s`, then `900s`.
- Retries continue until endpoint max attempts is reached.
- After max attempts, delivery state becomes dead and is no longer retried.
- Duplicate delivery-item pairs are prevented at storage level.
- Receive webhooks are chunked to max 100 items per delivery.

Consumer recommendations:

- Treat handlers as idempotent using `X-Atlas-Webhook-Id` + event item IDs.
- Return a `2xx` quickly; process asynchronously when possible.

## 8) Official SDKs

Official open-source client libraries are available for five languages. Each SDK covers every endpoint in this document,
exposes typed request/response models and typed status/error enums, and ships a webhook signature verifier
(see [Signature Verification](#73-signature-verification)).

> **Beta.** All five SDKs are currently in beta. They are feature-complete against the API described here, but their
> public surface may still change in backward-incompatible ways before the 1.0 release. Pin an exact version in your
> dependency manifest, and review the repository's release notes before upgrading. The REST API itself is stable and is
> not affected by the SDKs' beta status. Please report issues on the relevant repository.

### 8.1 Packages

| Language            | Package                     | Install                            | Source                                                      |
|---------------------|-----------------------------|------------------------------------|-------------------------------------------------------------|
| .NET 8+             | `Adsefid.Sdk` (NuGet)       | `dotnet add package Adsefid.Sdk`   | [adsefid/sdk-dotnet](https://github.com/adsefid/sdk-dotnet) |
| Python 3.10+        | `adsefid` (PyPI)            | `pip install adsefid`              | [adsefid/sdk-python](https://github.com/adsefid/sdk-python) |
| Node.js 22+ (JS/TS) | `@adsefid/sdk` (npm)        | `npm install @adsefid/sdk`         | [adsefid/sdk-js](https://github.com/adsefid/sdk-js)         |
| PHP 8.2+            | `adsefid/sdk` (Packagist)   | `composer require adsefid/sdk`     | [adsefid/sdk-php](https://github.com/adsefid/sdk-php)       |
| Go 1.22+            | `github.com/adsefid/sdk-go` | `go get github.com/adsefid/sdk-go` | [adsefid/sdk-go](https://github.com/adsefid/sdk-go)         |

Usage is documented per language in the repositories linked above. Each README covers installation, authentication,
client configuration, the full mapping of SDK methods to the endpoints in this document, and error handling. Runnable
programs — including a quickstart and a webhook receiver — live in each repository's `examples/` directory.

Notes:

- The PHP SDK also requires a PSR-18 HTTP client, for example `composer require guzzlehttp/guzzle`.
- The JavaScript SDK is server-side only. It holds your secret API key, so it must never be bundled into browser code.

### 8.2 Shared Behavior

- Every SDK defaults to base URL `https://api.adsefid.com` and sends `X-API-KEY` on each request. Base URL, User-Agent,
  timeout, and the underlying HTTP client are configurable.
- No SDK reads environment variables implicitly. Read your API key yourself and pass it to the client explicitly.
- Failures surface as typed errors that carry the numeric [
  `WebServiceResponseCode`](#34-webserviceresponsecode-used-by-these-endpoints)
  and its symbolic name. Rate limits, validation errors, and transport errors have their own error types.

### 8.3 Webhook Verification Helpers

Each SDK verifies the `X-Atlas-Webhook-Signature` header and parses the payload into a typed event in one call. Always
pass the exact raw request body, before any JSON parsing your framework may have already done.

| Language        | Entry point                                                                        |
|-----------------|------------------------------------------------------------------------------------|
| .NET            | `WebhookVerifier.VerifyAndParse(rawBody, signature, timestamp, secret)`            |
| Python          | `verify_and_parse_webhook(raw_body, signature_header, timestamp_header, secret)`   |
| JavaScript / TS | `verifyAndParseWebhook({ rawBody, signatureHeader, timestampHeader, secret })`     |
| PHP             | `(new WebhookVerifier($secret))->verifyAndParse($rawBody, $signature, $timestamp)` |
| Go              | `webhooks.Verify(rawBody, signature, timestamp, secret)`                           |

A failed verification raises the SDK's webhook verification error. Reject the delivery with a `401` and do not reveal
why. Each repository contains a runnable webhook receiver under `examples/`.

## 9) Integration Notes

- `message_ids` and `local_ids` query params support comma-separated values.
- `local_id` uniqueness is enforced per user and channel.
- Status endpoints may include recently accepted messages from short-lived cache even before full DB persistence.
- Cancel operations are queue-state dependent: already-sent/in-progress messages appear in `failed_to_cancel`.
- Model-binding failures use the standard error envelope and return field errors under `error.details.errors`.
