MQTT.Agent - open protocol for AI agents

Back to blog
REST API Developer Experience

A REST API for Teams That Don't Want to Hold an MQTT Socket

Why we ship a REST publish endpoint alongside MQTT, and when each one is the right tool.

December 2, 2025
6 min read
By CloudSignal Team

The product had MQTT from day one. The REST publish endpoint came later, because we kept hearing the same shape of question from teams that had no interest in holding a socket open. This post is about why we shipped it, what it does well, and where the broker still wins.

Who asked for REST

Three real use cases pushed us to add an HTTP path to publish. The first is backend cron jobs. A serverless function that fires once a minute to send a status ping does not benefit from a persistent connection. By the time the MQTT client has negotiated TLS and subscribed to anything, the function has already timed out. A POST request, a 200 back, done. The second is glue tools. Zapier, n8n, Make, generic webhook receivers, Supabase edge functions, GitHub Actions. None of them know what MQTT is, but all of them can hit an HTTP endpoint with a JSON body. Customers were writing tiny relay services just to bridge the gap, and that is the kind of yak shaving we would rather absorb. The third is languages and runtimes with no good MQTT client. PHP-FPM under shared hosting, certain edge runtimes, legacy ColdFusion stacks, internal tools written in whatever a team happened to have. We do not want to gate access to the broker on whether someone can find a maintained client library for their language of choice.

The endpoint

The shape is intentionally boring.

curl -X POST https://api.cloudsignal.app/v1/publish \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "topic": "devices/sensor-1/data", "payload": { "temp": 23.5 }, "qos": 1, "retain": false }'

Internally, the request is converted into an MQTT publish against the broker, exactly as if it had come in on a TCP socket. The acknowledgement you get back depends on QoS. With qos: 0 we accept and dispatch without waiting, which is fire and forget. With qos: 1 we wait for the broker to confirm the message was queued for delivery before returning a 200, which is at-least-once semantics from the publisher’s point of view. The bearer token is an API key scoped to your organisation, and topic-level permissions are enforced the same way they are for MQTT clients, so a key restricted to alerts/* cannot publish under devices/*.

Where the broker still wins

We would rather you reach for direct MQTT when it is the right tool, so here is the honest list of things REST cannot do as well.

Fanout is the big one. The whole point of a broker is that one publish goes to every subscriber whose topic filter matches, and the broker does the distribution for free. That happens on the broker for MQTT publishers too, but if you are using REST you are probably also subscribing somewhere, and the subscription side still needs MQTT. We have not solved subscribe-over-HTTP yet, so if your service needs to receive real-time messages, you are holding an MQTT socket whether you like it or not.

Session persistence is the next one. MQTT 5 clean-session and message-expiry semantics live on the broker because the broker is the thing that knows whether a subscriber is online. If a QoS 1 message is published while a subscriber is offline, the broker holds it and replays on reconnect. REST publishers do not get a session at all, so any retry logic on the publish side is yours to write.

Last-will-and-testament cannot work over REST. The whole mechanism depends on the broker noticing a TCP-level disconnect on a long-lived session and dispatching a message you registered at connect time. Stateless HTTP has nothing to disconnect from. If you need presence signals or graceful-shutdown alerts, your publishers need to be MQTT clients.

Latency and bidirectionality are the last two. A direct MQTT socket round-trip is single-digit milliseconds inside our network. REST adds an HTTP front door and a TLS handshake per call if you are not using keep-alive, and the path is one-way. If you have a workload where the publisher also needs to react to messages on related topics, do not pretend REST is enough.

Quotas and rate limits

Rate limits are per-organisation, not per-IP. The cap scales with plan tier. On the Pro plan, the REST API allows 50,000 publishes per month and bursts up to a few hundred requests per second before throttling. Scale lifts that to 200,000 per month, with a higher burst ceiling. The single-publish payload cap is 256 KB, which is the same limit MQTT publishers hit on our side. Over that, you get a 413.

When you exceed the rate limit you get a 429 Too Many Requests with a Retry-After header in seconds. We track the quota in Redis with sub-millisecond overhead, so the rate-limit check does not add a perceptible tail to a successful publish. Batch publishes count as one request against the rate limit but as N messages against the monthly quota, which is usually what teams want when they are flushing a buffer.

Idempotency and retries

Be honest about what we do and do not handle. If your client retries a publish after a network blip, both publishes go through. The REST endpoint does not currently expose an idempotency-key header, so duplicate publishes are duplicate messages, full stop. We have it on the list, but it is not shipped yet.

For your retry strategy, the rule of thumb is: retry 5xx with exponential backoff, do not retry 4xx. A 429 is the exception, where the Retry-After value tells you exactly how long to wait. QoS 0 publishes from REST are fire and forget, which means we accept and respond before the broker has confirmed dispatch. If you cannot tolerate loss, use QoS 1 and wait for the 200. If you are happy with loss in exchange for lower tail latency, QoS 0 is the right choice, but know which one you picked.

What we want to add next

The piece we hear about most is subscribe-over-HTTP. The use cases are the same as for publish: serverless functions, webhook receivers, runtimes with no MQTT client. We are looking at server-sent events as the transport, because it is a one-way push from server to client and it survives proxies and CDNs better than long-polling or raw WebSockets. SSE will not replace MQTT for anything that needs bidirectional flow, but it will close the loop for a real set of customers who want to receive messages without holding a broker socket. We are not committing dates, but it is the next bridge we want to build.

Ready to get started?

Try CloudSignal free and connect your first agents in minutes.

Start Building Free