Skip to content

Pagination & Errors

List endpoints use cursor pagination. A page looks like this:

{
"items": [ /* ... */ ],
"nextCursor": "djE6OTIxOTA1"
}

To fetch the next page, pass nextCursor back as the cursor query parameter:

Terminal window
curl "https://publicapi.shout.com/v1/forms?limit=50&cursor=djE6OTIxOTA1" \
-H "Authorization: Bearer $SHOUT_API_KEY"

When nextCursor is null, you’ve reached the last page — stop.

{ "items": [ /* ... */ ], "nextCursor": null }

Rules:

  • The cursor is opaque. It’s an internal token — don’t parse it, build it, or reason about its contents. Pass it back verbatim. A malformed cursor returns 400 invalid_request.
  • limit controls page size: default 25, maximum 100. Values above 100 are capped at 100; 0 falls back to the default.
  • Cursors encode a stable sort, so walking pages won’t skip or duplicate rows even as new data arrives. Use a filter like updated_since / since (not the cursor) to poll for new data.

A few small collections (workspaces, groups, inboxes) are returned unpaginated — they still use the { "items": [...], "nextCursor": null } shape for consistency, but nextCursor is always null.

Errors (except 401 and 429, see below) return a JSON body with a stable machine-readable error code and a human-readable message:

{
"error": "not_found",
"message": "No form with this id exists."
}

Branch on the error code, not the message (messages may be reworded).

Statuserror codeMeaning
200 OKSuccess.
201 CreatedA resource was created (returns the new resource; a Location header where applicable).
204 No ContentSuccess with no body (e.g. delete).
400 Bad Requestinvalid_requestMalformed request — bad cursor, unknown filter value, missing required field.
401 Unauthorized(empty body)Missing, invalid, expired or revoked key.
403 Forbiddeninsufficient_scopeValid key, but it lacks the scope this endpoint needs.
404 Not Foundnot_foundNo such resource, or it isn’t visible to your tenant.
409 ConflictconflictThe request conflicts with existing state (e.g. a duplicate contact email — the body includes the existing contactId).
422 Unprocessableunprocessable / unprocessable_entityThe request was well-formed but can’t be fulfilled (e.g. replying to a non-email ticket, or a form with no resolvable owner).
429 Too Many Requests(empty body)Rate limit exceeded — see below.
502 Bad Gatewaydelivery_failedA downstream delivery failed (e.g. a ticket reply email couldn’t be sent). Not caused by your request.
{
"error": "insufficient_scope",
"message": "This endpoint requires the 'responses:read' scope."
}
{
"error": "conflict",
"message": "A contact with this email already exists.",
"contactId": "1dcc380c-8076-2a37-5fb8-3a2265f83a67"
}

Each key may make 120 requests per minute, measured as a sliding window. Beyond that:

HTTP/2 429
retry-after: 5

The Retry-After header gives the number of seconds to wait before retrying. Back off for that long, then continue. Building a small retry-with-backoff wrapper around your HTTP client is recommended for any high-volume integration.