REST API
Offers
Read sent offers with their read status, visits, returns and total reading time, and one offer with every visit and the time spent per page.
Updated 4 September 2026
An offer is a document sent as a personal, tracked link. The API returns what the offer page in the app shows: who opened, how far they read, how long, and whether they came back. The document itself is not returned.
GET /api/v1/offers
Query: status (sent, opened, read; applied to the fetched page, see Pagination), prospect_id, campaign_id, limit, cursor.
curl "https://app.seegnals.com/api/v1/offers?status=read" -H "Authorization: Bearer $SEEGNALS_TOKEN"
const res = await fetch("https://app.seegnals.com/api/v1/offers?status=read", {
method: "GET",
headers: {
"Authorization": `Bearer ${process.env.SEEGNALS_TOKEN}`
}
});
const json = await res.json().catch(() => null);
console.log(res.status, json);
import os, requests
res = requests.get(
"https://app.seegnals.com/api/v1/offers?status=read",
headers={"Authorization": f"Bearer {os.environ['SEEGNALS_TOKEN']}"},
timeout=20,
)
print(res.status_code, res.json() if res.content else None)
{
"data": [
{
"id": "9a1f…",
"prospect": { "id": "7c9e…", "email": "ada@example.com", "first_name": "Ada", "last_name": null },
"company": { "id": "1b4e…", "name": "Example Ltd", "domain": "example.com" },
"campaign": null,
"document": { "format": "html_pasted", "label": "Proposal v2", "url": null, "path": "offers/…/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 axes, on purpose. status answers “did they read it”: sent, opened, or read when the reader reached the end or the last known page. visit_status answers “did they come back”: sent, viewed (one visit), returned (two or more). total_active_seconds is the sum over all visits. progress.total_pages is null for a PDF without sections, in which case completed is the only signal for read. url is the public offer address shown under Copy link in the app, on your white-label host if you set one.
Errors: 400 for a bad status, uuid, limit or cursor; 401; 429.
GET /api/v1/offers/{id}
The row above plus the visits and the per-page reading time:
{
"…": "fields from the list",
"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_list holds up to 200 most recent visits, newest first; visit_index is chronological (1 is the first visit). device is coarse: Mac · Chrome, Unknown device, Automated client. pages sums time per page or section over all visits and is empty for a PDF without sections.
Errors: 404 { "error": "Offer not found." }; 401; 429.