# REST messaging API design: resources, methods, status codes

> A REST messaging API models each send as a resource in a messages collection. Clients POST a message and, because delivery is asynchronous, the server returns 202 Accepted with a message identifier and a status URL, then reports the final outcome over a webhook. HTTP methods and status codes follow RFC 9110 semantics.

- Canonical URL: https://www.broadnet.me/reference/rest-messaging-api-design
- Language: en
- Last updated: 2026-07-19
- Publisher: BroadNet (https://www.broadnet.me)

How a standards-based REST messaging API is modelled: a messages collection, HTTP methods and status-code semantics per RFC 9110, synchronous vs asynchronous send (202 Accepted plus a status callback), content negotiation and URL-vs-header versioning — with a REST-vs-SMPP tradeoff table.

## The messages resource model

A REST messaging API treats every message as a resource under a collection — conventionally /messages — addressable by a server-assigned identifier. The verb, not a payload field, expresses intent: POST /messages creates and enqueues a new outbound message; GET /messages/{id} reads its current state; GET /messages lists and filters. This is the REST layer that sits above the same operator connections SMPP reaches on the wire, so the resource a client POSTs maps, downstream, to a submit_sm on a bound SMPP session. Because the identifier is minted by the server and returned in the response (and usually echoed in a Location header), the client has a stable handle to poll or to correlate against later status callbacks.

*Create a message in the collection (generic REST pattern)*

```http
POST /v1/messages HTTP/1.1
Host: api.example
Content-Type: application/json
Authorization: Bearer <token>
Idempotency-Key: 8f14e45f-ea7b-4c0f-9b2a-1d2c3e4f5a6b

{
  "to": "+15551234567",
  "from": "BRANDNAME",
  "text": "Your one-time code is 401 928",
  "callback_url": "https://app.example/webhooks/dlr"
}
```

## HTTP methods and their guarantees

RFC 9110 defines two properties that govern how a messaging client and any intermediary may treat a request. Safe methods (GET, HEAD) have no side effects and are freely cacheable and retryable. Idempotent methods (GET, HEAD, PUT, DELETE) can be repeated with the same net effect. POST is neither safe nor idempotent, which is exactly the problem for message submission: a retried POST could double-send. The standard remedy is not to abuse PUT but to keep POST and make it idempotent with an Idempotency-Key request header, so a replayed submission returns the original result instead of creating a second message.

*HTTP methods on a messages resource (RFC 9110)*

| Method | On /messages | Safe | Idempotent |
|---|---|---|---|
| POST | Create + enqueue a new message | No | No (make it idempotent via Idempotency-Key) |
| GET | Read a message or list the collection | Yes | Yes |
| HEAD | Metadata / existence check | Yes | Yes |
| PUT | Replace a mutable resource (e.g. a webhook config) | No | Yes |
| PATCH | Partial update of a mutable resource | No | No |
| DELETE | Cancel a still-queued message | No | Yes |

## Status-code semantics for send

The status code the server returns encodes whether the message was accepted, completed or rejected — and message send is almost always asynchronous. RFC 9110 defines 202 Accepted precisely for this case: the request was accepted for processing but processing is not complete, and the response should describe where to check on it. A messaging API therefore returns 202 for a queued send, reserving 201 Created for a resource that is fully materialised and 200 OK for a truly synchronous operation. Client errors (malformed payload, invalid destination, unknown sender) are 4xx and must not be retried unchanged; server and upstream failures are 5xx and are safe to retry with backoff under the same Idempotency-Key.

*Status-code semantics for message submission (RFC 9110)*

| Code | Meaning | When a messaging API uses it |
|---|---|---|
| 202 Accepted | Accepted, processing incomplete | Async send queued; final state comes via callback |
| 201 Created | Resource created | Synchronous create where the resource is complete |
| 200 OK | Request completed | Synchronous status read or truly immediate operation |
| 400 / 422 | Client error / unprocessable | Malformed body or invalid field — do not retry unchanged |
| 401 / 403 | Unauthenticated / forbidden | Missing or insufficient credentials |
| 409 Conflict | State conflict | Idempotency-Key reused with a different payload |
| 429 Too Many Requests | Rate limited | Throttle; honour Retry-After and back off |
| 5xx | Server / upstream failure | Transient — retry with backoff under the same key |

## Synchronous vs asynchronous delivery

There are two distinct latencies in a messaging API and it is a design error to conflate them. The API latency is how long the HTTP call takes to accept the message; the delivery latency is how long the operator network takes to hand the message to the handset and return a receipt. Because the second is unbounded and out of the sender's control, a well-designed API acknowledges immediately (202) with an accepted or queued state, then delivers the terminal outcome asynchronously — a delivery-status callback (the HTTP analogue of an SMPP delivery receipt). Clients that need the final state either subscribe to the callback (push) or poll GET /messages/{id} (pull); the callback is preferred because it avoids polling load and reports state changes within seconds.

*202 Accepted response with a status handle*

```http
HTTP/1.1 202 Accepted
Location: /v1/messages/01HZY8Q3M6
Content-Type: application/json

{
  "id": "01HZY8Q3M6",
  "status": "accepted",
  "created_at": "2026-07-19T09:15:22Z",
  "status_url": "/v1/messages/01HZY8Q3M6"
}
```

## Content negotiation and versioning

Clients and servers agree on representation through the RFC 9110 content-negotiation headers: the request carries Content-Type for the body it sends and Accept for what it wants back, and the server echoes the chosen media type in the response Content-Type. Most messaging APIs standardise on application/json and use application/problem+json for errors. API versioning has two mainstream patterns. URL versioning (/v1/messages) is explicit, cache-friendly and trivial to route, at the cost of a coarse, whole-surface bump. Header or media-type versioning (Accept: application/vnd.example.v2+json) keeps URLs stable and lets a resource evolve independently, at the cost of being invisible in a browser and harder to test by hand. Whichever is chosen, additive changes (new optional fields) should never require a version bump; only breaking changes do.

*API versioning approaches*

| Approach | Example | Trade-off |
|---|---|---|
| URL path | /v1/messages | Explicit, cacheable, easy to route; coarse-grained bumps |
| Custom header | Api-Version: 2026-07-19 | Clean URLs, date-pinned; invisible to caches and browsers |
| Accept media type | Accept: application/vnd.example.v2+json | Per-resource evolution; harder to test by hand |
| Query parameter | /messages?version=2 | Simple; muddies caching and the resource identity |

## REST vs SMPP: choosing the transport

The HTTP/REST layer and SMPP are two transports onto the same messaging network, and mature senders use both. REST is the right choice for application integration: it is stateless, firewall-friendly over ordinary HTTPS, easy to secure with bearer tokens, and simple to instrument. SMPP is the right choice for very high sustained throughput and the lowest per-message overhead, because a long-lived binary session avoids per-message connection cost and gives fine-grained window control. The table contrasts the two so an integrator can match the transport to the workload; for BroadNet's concrete REST endpoints see the developer API reference, and for the wire protocol see the SMPP overview.

*REST (HTTP) vs SMPP as a messaging transport*

| Property | REST / HTTP | SMPP |
|---|---|---|
| Model | Stateless request/response over HTTPS | Stateful long-lived binary session (bind) |
| Framing | JSON over HTTP/1.1 or HTTP/2 | Binary PDUs (submit_sm, deliver_sm) |
| Delivery receipt | Status webhook / poll GET | deliver_sm receipt on the same session |
| Throughput | Good; per-request overhead | Highest sustained; windowed, low overhead |
| Auth | Bearer token / API key / OAuth 2.0 | system_id + password at bind |
| Flow control | 429 + Retry-After, RateLimit headers | Window size + enquire_link throttling |
| Best for | App integration, webhooks, moderate volume | Carrier-grade high-volume A2P firehoses |

## Frequently asked questions

### Why does a REST messaging API return 202 instead of 200 or 201?

Because message delivery is asynchronous. RFC 9110 defines 202 Accepted as 'accepted for processing, processing not complete', which exactly describes a queued send. The API acknowledges immediately with a message id and a status handle, then reports the terminal delivery outcome later over a callback. 201 and 200 imply the operation is already complete.

### How should a messages resource be modelled?

As a collection: POST /messages creates and enqueues a message, GET /messages/{id} reads its state, and GET /messages lists and filters. The server mints the identifier and returns it (often in a Location header) so the client has a stable handle to poll or to correlate against status callbacks.

### POST is not idempotent — how do you avoid double-sends?

You keep POST (it correctly expresses 'create a new message') and make it idempotent with an Idempotency-Key request header. A retried submission carrying the same key returns the original result instead of creating a second message. Abusing PUT to force idempotency breaks the REST resource model.

### What is the difference between API latency and delivery latency?

API latency is how long the HTTP call takes to accept the message; delivery latency is how long the operator network takes to reach the handset and return a receipt. The second is unbounded and out of the sender's control, which is why the API acks fast (202) and reports final delivery asynchronously.

### Should I version a messaging API in the URL or in a header?

URL versioning (/v1/messages) is explicit, cache-friendly and easy to route but bumps the whole surface at once. Header or media-type versioning keeps URLs stable and lets resources evolve independently but is invisible to caches and harder to test by hand. Either way, additive changes should never force a version bump.

### When should I use REST versus SMPP?

Use REST for application integration: stateless, firewall-friendly HTTPS, bearer-token auth and easy instrumentation. Use SMPP for the highest sustained throughput and lowest per-message overhead, where a long-lived windowed binary session pays off. Both reach the same messaging network; many senders run REST for control traffic and SMPP for high-volume firehoses.

## Sources

- [IETF RFC 9110 — HTTP Semantics](https://datatracker.ietf.org/doc/html/rfc9110)
- [IETF RFC 9111 — HTTP Caching](https://datatracker.ietf.org/doc/html/rfc9111)

## Related

- [SMPP protocol overview](https://www.broadnet.me/reference/smpp-protocol-overview)
- [Webhook delivery & reliability](https://www.broadnet.me/reference/webhook-delivery-and-reliability)
- [Idempotency & rate limiting](https://www.broadnet.me/reference/idempotency-and-rate-limiting)
- [API errors & pagination](https://www.broadnet.me/reference/api-errors-and-pagination)
- [BroadNet API reference](https://www.broadnet.me/developers/api-reference)
