Offers
An offer is a document you send with a tracked link. anymail records when the recipient opens it, how long they read and how far they get. Offers are read-only in the API. The API returns metadata and statistics. The content of the document is not returned.
| Method | Path | Action |
|---|---|---|
GET |
/api/v1/offers |
List sent offers |
GET |
/api/v1/offers/{id} |
Get an offer with its visits |
List offers#
| Parameter | Type | Notes |
|---|---|---|
status |
string | One of sent, opened, read. The API applies it to the fetched page. See Pagination. |
prospect_id |
UUID | Offers sent to one prospect. |
campaign_id |
UUID | Offers sent in one campaign. |
limit |
integer | 1 to 100. Default 50. |
cursor |
string | See Pagination. |
curl "https://app.anymail.watch/api/v1/offers?status=read" \
-H "Authorization: Bearer $ANYMAIL_TOKEN"
Response 200
{
"data": [
{
"id": "9a1f0b2c-0000-4000-8000-000000000001",
"prospect": { "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "email": "ada@example.com", "first_name": "Ada", "last_name": null },
"company": { "id": "1b4e28ba-2fa1-11d2-883f-0016d3cca427", "name": "Example Ltd", "domain": "example.com" },
"campaign": null,
"document": { "format": "html_pasted", "label": "Proposal v2", "url": null, "path": "offers/example/proposal-v2.html" },
"url": "https://app.seegnals.com/offer/Qm9…",
"sent_at": "2026-09-01T10:00:00.000+00:00",
"status": "opened",
"visit_status": "returned",
"visits": 2,
"returns": 1,
"total_active_seconds": 247,
"progress": { "max_page_reached": 3, "total_pages": 6, "completed": false },
"first_viewed_at": "2026-09-01T11:02:00.000+00:00",
"last_viewed_at": "2026-09-02T09:15:00.000+00:00"
}
],
"next_cursor": null
}
Two status fields#
| Field | Values | Answers |
|---|---|---|
status |
sent, opened, read |
Did the recipient read it? read means they reached the end (completed) or the last known page. |
visit_status |
sent, viewed, returned |
How many times did they come back? 0, 1, or 2 or more visits. |
total_active_secondsis the sum over all visits.progress.total_pagesisnullfor a PDF without sections. For such a documentcompletedis the only signal forread.urlis the public address of the offer, the one the app shows under Copy link. It uses the white-label host when the workspace sets one.campaignisnullwhen the offer was not sent from a campaign.
Errors: 400 for an invalid status, UUID, limit or cursor.
Get an offer#
GET /api/v1/offers/{id} returns the row above plus the visits and the reading time per page.
curl "https://app.anymail.watch/api/v1/offers/9a1f0b2c-0000-4000-8000-000000000001" \
-H "Authorization: Bearer $ANYMAIL_TOKEN"
Response 200 (the fields of the list row are shortened)
{
"id": "9a1f0b2c-0000-4000-8000-000000000001",
"status": "opened",
"visits": 2,
"visit_list": [
{
"session_id": "s2",
"started_at": "2026-09-02T09:15:00.000+00:00",
"active_seconds": 40,
"device": "iPhone · Safari",
"max_page_reached": 3,
"completed": false,
"visit_index": 2
},
{
"session_id": "s1",
"started_at": "2026-09-01T11:02:00.000+00:00",
"active_seconds": 207,
"device": "Mac · Chrome",
"max_page_reached": 2,
"completed": false,
"visit_index": 1
}
],
"pages": [
{ "id": "sec1", "position": 0, "label": "Intro", "page_type": "cover", "visits_read": 2, "total_active_seconds": 70, "last_read_at": "2026-09-02T09:16:00.000+00:00" },
{ "id": "sec2", "position": 1, "label": "Pricing", "page_type": "pricing", "visits_read": 1, "total_active_seconds": 30, "last_read_at": null }
]
}
visit_listholds up to the 200 latest visits, newest first.visit_indexcounts in time order, so1is the first visit.deviceis approximate, for exampleMac · Chrome,Unknown deviceorAutomated client.pagessums the reading time per page or section over all visits. It is empty for a PDF without sections.
Errors: 404 with {"error":"Offer not found."}.