Sending & Receiving
How to send and receive messages through agrirouter in production integrations, with addressing, retries, chunk reassembly, and feed confirmation
This page covers the integration-level concerns around sending and receiving messages: addressing modes, retries, chunk reassembly on the receive side, and feed confirmation. The walkthrough-style introductions live in the getting-started tutorials and are the right starting point the first time through.
For the conceptual messaging model, see Messaging.
Sending a payload end to end
Every outbound message is a single HTTPS request to POST /messages. The envelope is carried in HTTP headers, and the payload is the raw request body with Content-Type: application/octet-stream. See Send Your First Message for the full header reference.
A successful 200 response means agrirouter has accepted the message for routing. It does not confirm delivery to any recipient. There is no asynchronous delivery acknowledgement event for the sender.
Addressing modes
Choose between direct addressing and publish on a per-message basis using two headers.
Set x-agrirouter-is-publish: false and provide x-agrirouter-direct-recipients with a comma-separated list of recipient endpoint UUIDs. The message is delivered only to those endpoints, provided a route exists from the sender to each recipient for this message type.
Use direct addressing for interactive flows where the user picks the target, for example sending an application map from an FMIS to a specific machine.
Set x-agrirouter-is-publish: true and usually omit x-agrirouter-direct-recipients. The message is delivered to every endpoint that has a capability for the message type and is reachable through a route the account owner has configured.
Use publish for automated flows with an open-ended set of receivers, for example telemetry streamed from machines to any subscribed platform.
A publish send can still carry a list in x-agrirouter-direct-recipients, in which case those recipients receive the message in addition to any subscribers. This is an edge case, not a separate mode.
Retries and idempotency
x-agrirouter-context-id is an application-generated identifier for the payload (max 50 characters, unique per payload). When retrying the same payload after a transport failure, reuse the same context ID so agrirouter can deduplicate. Generate a fresh context ID for a new payload.
For transient failures (network errors, 5xx responses, 429), back off exponentially before retrying. For validation failures (400, 403), fix the request and send with the same context ID; there is no point retrying an invalid payload as-is.
The gateway does not return a Retry-After header on 429, so the backoff schedule is up to the client. See Rate limits for how the limits are scoped and how to find the concrete number for your application.
Chunking
Payloads larger than the internal chunk size are split by agrirouter on the way out. The sender does not implement chunking. Two headers make this work:
content-length: total payload size in bytes. The API uses this to decide whether to split.x-agrirouter-context-id: shared across all resulting chunks, so the recipient can reassemble.
The maximum payload size accepted by the API is 256 MB. Payloads exceeding that limit are rejected with 413.
Receiving through the SSE stream
Events are delivered through a single Server-Sent Events stream. Open GET /events with a valid access token and keep the connection open; events stream as they occur. There is no polling API, no webhook callback, and no push-notification fallback.
The stream covers two families of events. The data-flow events tell you about messages and files arriving on your feeds; the tenant-state events tell you about authorizations and endpoint visibility:
| Event | Meaning |
|---|---|
MESSAGE_RECEIVED | A new message has arrived in the feed of one of your endpoints. |
FILE_RECEIVED | A chunked file payload (TaskData, Shape, PDF, image, video) has been fully reassembled by agrirouter and is ready to download. |
ENDPOINT_DELETED | One of your endpoints was deleted, either by your application or by the account owner. |
ENDPOINTS_LIST_CHANGED | The set of endpoints visible to your application in a tenant changed, or a visible endpoint's capabilities or routes changed. |
AUTHORIZATION_ADDED | A user granted your application a new authorization for a tenant. |
AUTHORIZATION_REVOKED | A user revoked an authorization. Access to the tenant for the given scope is already gone when the event is delivered. |
See Receive Your First Message for the wire format and a sample MESSAGE_RECEIVED event, and the Events catalog for the per-event payloads.
Replay on reconnect
Long-lived SSE connections are the simplest model, but many integrations cannot hold one open: batch jobs, mobile clients, processes that cycle through restarts. When you open a fresh SSE connection, the gateway replays every unconfirmed event that arrived on your endpoints while you were disconnected, then continues with live events. You do not need a cursor; reconnecting is enough to catch up.
Confirming events promptly matters: confirmed events drop out of the replay window, so the next reconnect only replays genuinely missed events.
Downloading the payload
Both MESSAGE_RECEIVED and FILE_RECEIVED events may include the payload directly as payload (base64 inline, for small payloads) or as a payload_uri link to download (for larger payloads). Exactly one of the two is present.
payload_uri links are time-limited and expire after at most 15 minutes. Download the payload as soon as you receive the event.
The payload_uri is pre-signed and does not need an Authorization header. The response body is the raw binary (application/octet-stream) in the original format.
Chunked file reassembly
For chunked message types (TaskData, Shape, PDF, images, videos), agrirouter reassembles the chunks server-side and emits a single FILE_RECEIVED event when the whole payload has arrived. The event carries a message_ids array listing the agrirouter message IDs of the individual chunks, plus one payload_uri (or inline payload) for the reassembled file.
Your application does not see the intermediate chunks as separate events. You download the reassembled payload once and confirm every ID in message_ids so the chunks drop out of the feed.
Confirming messages
Every event you handle must be confirmed with POST /confirmations, otherwise it keeps replaying on reconnect and accumulates in your endpoint's feed. A confirmation is a (endpoint_id, message_id) pair; a single request can carry many.
A successful 202 means the confirmation was accepted for processing. The feed is updated asynchronously, so a just-confirmed message may still appear in the replay window for a brief interval.
Unconfirmed events accumulate in your feed and will replay on every reconnect. Confirm events after your application has processed them.
Error handling
| Error | Cause | Resolution |
|---|---|---|
400 on send | Malformed headers, unsupported message type, or missing required field. | Validate the request against the API spec before retrying. |
400 on send | No route exists from the sender to a specified direct recipient. | Ask the account owner to configure a route for the sender, recipient, and message type. |
403 on send | The endpoint ID in the request does not belong to a tenant the access token is authorized for. | Confirm the access token matches the endpoint's tenant; confirm the endpoint has not been deleted. |
413 on send | Payload exceeds the 256 MB limit. | Split the data at the application level before sending. |
| Event not arriving | The recipient's capabilities or the account's routes do not cover the message type. | Verify the recipient's capabilities and confirm the account owner has configured a route. |
payload_uri returns 403 or 404 | The 15-minute expiry window has passed. | Trigger a fresh event, or re-request via the SSE stream on reconnect. |
See Errors for the full HTTP status code list and response body shape.