API reference
Signal API
Signal identifies the companies and people visiting your site. This API gives you that data programmatically — the organisations that visited ranked by intent, the people identified, where they arrived from, and what Signal currently recommends.
0.1.0Every endpoint, parameter, limit and response shape below is read from the running service at build time. Nothing on this page is maintained by hand, so it cannot describe an endpoint that does not exist or omit one that does.
This page is the endpoint reference. The developer guide, workflows and case studies live alongside the service they describe.
Quickstart
Create a key in Exports & API, then make your first request. The key is shown once, on creation.
curl "https://app.signal.geysera.com/signal-api/v1/accounts" \
-H "Authorization: Bearer sk_sig_..."https://app.signal.geysera.com/signal-api/v1, as the service reports it in its own spec. We do not hardcode it here.Authentication
Authorization: Bearer sk_sig_...- Authorizationheaderrequired
- Bearer sk_sig_... (required)
- X-Correlation-IDheaderoptional
- optional; generated when absent, echoed on every response
- X-Idempotency-Keyheaderoptional
- optional; honoured on POST /audiences and POST /audiences/{key}/runs — a retry with the same key replays the first answer instead of creating or spending twice. The other audience writes are idempotent by design.
401 means the key is missing, malformed, unknown or revoked. 403 means the key is valid but lacks the scope for that endpoint. The two are deliberately distinct: a 403 means your credential is fine and re-issuing it will not help.
Scopes
- readscope
- The /signal-api/v1 REST endpoints: accounts, visitors, attribution, recommendations.
- copilotscope
- Ask questions in natural language at /signal-api/v1/copilot/ask. READ-ONLY: a key reaches the copilot's read tools and never the ones that change the workspace, whatever it is asked.
- audiencesscope
- /signal-api/v1/audiences: create, edit and delete audiences, review prospects, estimate runs and export accepted people (JSON, or CSV with ?format=csv). Spends nothing.
- enrichscope
- Start a paid enrichment run (POST /signal-api/v1/audiences/{key}/runs) or change daily lookups. SPENDS enrichment credits, within the workspace's caps. Grant it only to a key that should spend.
A key is scoped to one workspace. Results respect that workspace's plan caps, exactly as the dashboard does. Scopes are fixed when the key is created: to change reach, create a new key and revoke the old one.
/accountsList companies, one row per email domain. Consumer mailboxes share one "Personal email" row.
Query parameters
- pageintegeroptional1–1000, default 1
- Page number, from 1. Capped at 1000, so `page x page_size` bounds what offset pagination can reach: 50,000 rows at the default page size and 200,000 at the maximum. For a full export use `page_size=200`; to go beyond 200,000, narrow the query with a filter. Asking for page 1001 returns 422 rather than an empty page, so a walk cannot mistake the ceiling for the end of the data.
- page_sizeintegeroptional1–200, default 50
- Rows per page, 1-200. The default of 50 is sized for a screen; a full export should ask for 200, which also multiplies how far the 1000-page ceiling reaches.
- classificationlead | customer | excluded or nulloptional
Response fields
- accountsarray of PublicAccountrequired
- totalintegerrequired
- pageintegerrequired
- page_sizeintegerrequired
Status codes: 200 Successful Response · 401 Missing, malformed, unknown or revoked key · 403 Key lacks the required scope · 422 A parameter failed validation · 429 Rate limit exceeded for this key
curl "https://app.signal.geysera.com/signal-api/v1/accounts" \
-H "Authorization: Bearer sk_sig_..."{
"accounts": [
{
"company_domain": "string",
"company_name": "string",
"intent_score": 0,
"visitor_count": 0,
"visit_count": 0,
"classification": "string",
"first_seen_at": "2026-09-11T14:23:07Z",
"last_seen_at": "2026-09-11T14:23:07Z",
"is_locked": false
}
],
"total": 0,
"page": 0,
"page_size": 0
}/attributionVisits, identifications and revenue by channel and campaign for a date range.
The nightly attribution rollup, served as-is.
Deliberately reads the cached rollup only and never falls back to a live scan, unlike the dashboard endpoint. A live recompute scans up to 200k visitor rows; behind an API key that is a trivially repeatable way to exhaust the database, and an integrator polling on a cron would do it without meaning to. computed_at tells the caller how fresh the answer is so staleness is visible rather than hidden.
Query parameters
- daysintegeroptional1–365, default 30
- Trailing window in days. Attribution is a nightly rollup computed for a fixed set of windows, so any other value returns `has_data: false` with `available_windows` listing the ones that exist — a self-correcting answer rather than an error. That is the commonest cause of `has_data: false`, and it is NOT the same as having no attribution: when the window exists but nothing was captured, `available_windows` is absent.
Response fields
- window_daysintegerrequired
- The window this rollup covers, in days.
- has_databooleanrequired
- False when no rollup exists for this window. The rollup fields are then `null` — PRESENT in the response with a null value, not omitted — and `available_windows` lists the windows that do have one. Branch on this flag: do not read a null or a zero as 'no attribution', and do not test for a missing key.
- computed_attimestamp or nulloptional
- When the rollup was last computed.
- available_windowsarray of integer or nulloptional
- The windows that DO have a rollup, so a caller can retry with one that exists. Populated only when `has_data` is false; `null` otherwise — the key is always present.
- totalsAttributionWindowTotals or nulloptional
- funnelarray of AttributionFunnelStage or nulloptional
- Visitors -> Identified -> High intent. Nested by construction.
- channelsarray of AttributionChannel or nulloptional
- sourcesarray of AttributionSource or nulloptional
- Where visitors came FROM. Answers where to spend.
- campaignsarray of AttributionCampaign or nulloptional
- landing_pagesarray of AttributionLandingPage or nulloptional
- Pages visitors arrived ON. Answers what to write — a different question from `sources`, and not a substitute for it.
- first_vs_lastarray of AttributionFirstVsLast or nulloptional
- trendarray of AttributionTrendPoint or nulloptional
- Dense over the window, so real gaps stay visible.
- landing_channelarray of AttributionLandingChannel or nulloptional
- revenueAttributionRevenue or nulloptional
- Orders joined to a first touch. Read its `coverage_pct` first.
- tenant_scopeinteger or nulloptional
- How many tenants' data this rollup combines. 2 where a Signal workspace reads orders through a paired commerce tenant, 1 otherwise. Reported so a figure that spans both is not mistaken for one workspace's.
Status codes: 200 Successful Response · 401 Missing, malformed, unknown or revoked key · 403 Key lacks the required scope · 422 A parameter failed validation · 429 Rate limit exceeded for this key
curl "https://app.signal.geysera.com/signal-api/v1/attribution" \
-H "Authorization: Bearer sk_sig_..."{
"window_days": 0,
"has_data": false,
"computed_at": "2026-09-11T14:23:07Z",
"available_windows": [
0
],
"totals": {
"visitors": 0,
"visitors_total": 0,
"inferred_touches": 0,
"touch_coverage_pct": 0,
"identified": 0,
"identify_rate": 0,
"high_intent": 0,
"high_intent_rate": 0,
"with_click_id": 0,
"channels_active": 0,
"median_hours_to_identify": 0,
"multi_touch": 0,
"multi_touch_rate": 0,
"intent_threshold": 0,
"truncated": false
},
"funnel": [
{
"stage": "string",
"count": 0,
"pct": 0
}
],
"channels": [
{
"channel": "string",
"visitors": 0,
"identified": null,
"identify_rate": null,
"high_intent": null,
"avg_intent": null,
"share": null
}
],
"sources": [
{
"source_medium": "string",
"channel": null,
"visitors": 0,
"identified": null,
"identify_rate": null,
"high_intent": null,
"avg_intent": null
}
],
"campaigns": [
{
"campaign": "string",
"channel": null,
"visitors": 0,
"identified": null,
"identify_rate": null,
"high_intent": null,
"avg_intent": null
}
],
"landing_pages": [
{
"path": "string",
"visitors": 0,
"identified": null,
"identify_rate": null,
"high_intent": null,
"avg_intent": null
}
],
"first_vs_last": [
{
"channel": "string",
"first_touch": 0,
"last_touch": 0,
"delta": 0,
"role": null
}
],
"trend": [
{
"date": "string",
"total": 0,
"by_channel": null
}
],
"landing_channel": [
{
"path": "string",
"channel": "string",
"visitors": 0,
"identified": null,
"identify_rate": null
}
],
"revenue": {
"orders": 0,
"revenue": 0,
"currency": "string",
"coverage_pct": 0,
"attributed_orders": 0,
"attributed_revenue": 0,
"unattributed_revenue": 0,
"revenue_per_identified_visitor": 0,
"channels": [],
"products": []
},
"tenant_scope": 0
}/audiencesAudiences defined for this workspace
Query parameters
No parameters.
Response fields
- audiencesarray of PublicAudienceDefinitionrequired
- store_rolesarray of PublicStoreRolerequired
- The roles available for `confirmed_roles`, with their sizes.
- readinessPublicAudienceReadinessrequired
Status codes: 200 Successful Response · 401 Missing, malformed, unknown or revoked key · 403 Key lacks the required scope · 422 A parameter failed validation · 429 Rate limit exceeded for this key
curl "https://app.signal.geysera.com/signal-api/v1/audiences" \
-H "Authorization: Bearer sk_sig_..."{
"audiences": [
{
"key": "string",
"name": "string",
"title_terms": [
"string"
],
"employer_terms": [
"string"
],
"page_patterns": [
"string"
],
"confirmed_roles": [
"string"
],
"industry_terms": [
"string"
],
"job_functions": [
"string"
]
}
],
"store_roles": [
{
"role": "string",
"customers": 0,
"label": "string"
}
],
"readiness": {
"ready": false,
"missing": [
{
"need": "string",
"message": "string",
"href": "string",
"label": "string"
}
]
}
}/audiencesCreate an audience from its name
The key is derived from the name (lower-case, `_` for spaces), so a caller never has to invent one. Refuses a name whose key already exists — edit that audience with PUT /audiences/{key} instead. Reports what the definition matches, like PUT.
Query parameters
- X-Idempotency-Keystring or nulloptional
Status codes: 201 Successful Response · 401 Missing, malformed, unknown or revoked key · 403 Key lacks the required scope · 409 An audience with that name already exists, or this workspace cannot have one yet · 422 A parameter failed validation · 429 Rate limit exceeded for this key
curl -X POST "https://app.signal.geysera.com/signal-api/v1/audiences" \
-H "Authorization: Bearer sk_sig_..." \
-H "Content-Type: application/json" \
-d '{"name":"string"}'/audiences/{key}One audience's definition
Query parameters
- keystringrequired
Response fields
- keystringrequired
- Stable identifier, derived from the name on creation.
- namestringrequired
- title_termsarray of stringrequired
- employer_termsarray of stringrequired
- page_patternsarray of stringrequired
- SQL LIKE patterns against the page path. Empty matches nobody.
- confirmed_rolesarray of stringrequired
- Roles that count as confirmed members: the store's own, Geysera's (`geysera:`), or a purchase (`product:<id>`: bought that product, ever).
- industry_termsarray of stringrequired
- job_functionsarray of stringrequired
Status codes: 200 Successful Response · 401 Missing, malformed, unknown or revoked key · 403 Key lacks the required scope · 404 This workspace has no audience with that key · 422 A parameter failed validation · 429 Rate limit exceeded for this key
curl "https://app.signal.geysera.com/signal-api/v1/audiences/{key}" \
-H "Authorization: Bearer sk_sig_..."{
"key": "string",
"name": "string",
"title_terms": [
"string"
],
"employer_terms": [
"string"
],
"page_patterns": [
"string"
],
"confirmed_roles": [
"string"
],
"industry_terms": [
"string"
],
"job_functions": [
"string"
]
}/audiences/{key}Create or replace an audience definition
Saves the definition and reports what it matches in this workspace's own data, so a term or page pattern that matches NOTHING is said at once rather than discovered as an empty list weeks later. Creating one is refused (409) where an audience cannot work yet — no store with members, or no enrichment on the plan — with what to fix; editing one that exists is not.
Query parameters
- keystringrequired
Status codes: 200 Successful Response · 401 Missing, malformed, unknown or revoked key · 403 Key lacks the required scope · 409 This workspace cannot have an audience yet · 422 A parameter failed validation · 429 Rate limit exceeded for this key
curl -X PUT "https://app.signal.geysera.com/signal-api/v1/audiences/{key}" \
-H "Authorization: Bearer sk_sig_..." \
-H "Content-Type: application/json" \
-d '{"name":"string"}'/audiences/{key}Delete an audience
Kept, not erased: the team's decisions and the run history stay (the stored classifications are removed — they describe a definition that is gone), and creating an audience with the same name brings it back (its calibration then reads as out of date, because the definition was re-made). Deleting also turns off its daily lookups, unsubscribes everyone from its daily email and pauses its sync rules, so nothing spends or sends for an audience that no longer exists. Refused while a run is in progress — cancel it first.
Query parameters
- keystringrequired
Status codes: 200 Successful Response · 401 Missing, malformed, unknown or revoked key · 403 Key lacks the required scope · 404 This workspace has no audience with that key · 409 A run for this audience is in progress · 422 A parameter failed validation · 429 Rate limit exceeded for this key
curl -X DELETE "https://app.signal.geysera.com/signal-api/v1/audiences/{key}" \
-H "Authorization: Bearer sk_sig_..."/audiences/{key}/acceptanceThe spec's acceptance tests, on this workspace's data
Each check reports its bar, what it measured and over how many, and passes, fails, or says `pass: null` when the data cannot decide (the profile rate needs its whole sample looked up: start a run with `mode: acceptance`). Reads stored data and spends nothing; the made-up-address probe is POST /{key}/acceptance/made-up-addresses.
Query parameters
- keystringrequired
Response fields
- audiencestringrequired
- The audience key these checks were run against.
- passedintegerrequired
- failedintegerrequired
- undecidedintegerrequired
- Criteria that could not be decided either way.
- checksarray of PublicAcceptanceCheckrequired
Status codes: 200 Successful Response · 401 Missing, malformed, unknown or revoked key · 403 Key lacks the required scope · 404 This workspace has no audience with that key · 422 A parameter failed validation · 429 Rate limit exceeded for this key
curl "https://app.signal.geysera.com/signal-api/v1/audiences/{key}/acceptance" \
-H "Authorization: Bearer sk_sig_..."{
"audience": "string",
"passed": 0,
"failed": 0,
"undecided": 0,
"checks": [
{
"check": "string",
"label": "string",
"bar": "string",
"measured": "string",
"pass": false,
"detail": "string"
}
]
}/audiences/{key}/acceptance/made-up-addressesLook up 20 invented addresses; none should return a profile
The spec's first acceptance test, live against the enrichment provider. Costs credits only if the provider wrongly returns a profile. Each lookup is checked against the spending limits and recorded in the credit ledger; no profile is stored. A key needs the `enrich` scope.
Query parameters
- keystringrequired
Status codes: 200 Successful Response · 401 Missing, malformed, unknown or revoked key · 403 Key lacks the required scope · 404 This workspace has no audience with that key · 422 A parameter failed validation · 429 Rate limit exceeded for this key
curl -X POST "https://app.signal.geysera.com/signal-api/v1/audiences/{key}/acceptance/made-up-addresses" \
-H "Authorization: Bearer sk_sig_..."/audiences/{key}/account-watchConfirmed members to call: late reorders, and back on the site without buying
The spec's Account Watch, over the last `days`. `late`: members whose time since their last order is longer than any gap they have had between two orders, who became late in the window. `browsing_without_buying`: members the pixel identified viewing pages in the window, with no order since their first view in it. Orders are the store's own, attributed to the account (customer id first); `audience_pages` lists only pages matching the audience's patterns. Most members are never identified on the site, so the second list is short by nature.
Query parameters
- keystringrequired
- daysintegeroptional1–365, default 90
- The window, in days.
- limitintegeroptional1–200, default 50
- Max people per list.
Response fields
- audiencestringrequired
- daysintegerrequired
- The window these figures cover.
- membersintegerrequired
- known_accountsintegerrequired
- orders_synced_throughtimestamprequired
- How far this store's order history is known to be whole. Everything here is measured up to that point, not up to now.
- latePublicLateMembersrequired
- browsing_without_buyingPublicBrowsingWithoutBuyingrequired
Status codes: 200 Successful Response · 401 Missing, malformed, unknown or revoked key · 403 Key lacks the required scope · 404 This workspace has no audience with that key · 409 This audience names no store role, so it has no confirmed members to watch · 422 A parameter failed validation · 429 Rate limit exceeded for this key
curl "https://app.signal.geysera.com/signal-api/v1/audiences/{key}/account-watch" \
-H "Authorization: Bearer sk_sig_..."{
"audience": "string",
"days": 0,
"members": 0,
"known_accounts": 0,
"orders_synced_through": "2026-09-11T14:23:07Z",
"late": {
"count": 0,
"accounts": [
{
"account": "string",
"name": null,
"company": null,
"orders": 0,
"revenue": 0,
"first_order_at": null,
"last_order_at": null,
"days_since_last_order": 0,
"longest_gap_days": null,
"late_since": null,
"visitor_id": null
}
]
},
"browsing_without_buying": {
"count": 0,
"people": [
{
"account": "string",
"visitor_id": null,
"name": null,
"company": null,
"orders": 0,
"last_order_at": null,
"page_views": 0,
"first_view_in_window": "2026-09-11T14:23:07Z",
"last_view": "2026-09-11T14:23:07Z",
"audience_pages": []
}
]
}
}/audiences/{key}/automationTurn daily lookups of an audience's new readers on or off
Kept apart from the definition on purpose: replacing the definition (PUT /{key}) must never switch spending on or off as a side effect.
Spends money, so a key needs the `enrich` scope.
Query parameters
- keystringrequired
Status codes: 200 Successful Response · 401 Missing, malformed, unknown or revoked key · 403 Key lacks the required scope · 404 This workspace has no audience with that key · 422 A parameter failed validation · 429 Rate limit exceeded for this key
curl -X PUT "https://app.signal.geysera.com/signal-api/v1/audiences/{key}/automation" \
-H "Authorization: Bearer sk_sig_..." \
-H "Content-Type: application/json" \
-d '{"auto_enrich":false}'/audiences/{key}/digestThe audience's daily email: who gets it and what was sent
Every morning, after the day's refresh, each subscriber receives the people accepted into the audience since the previous email — by the model, with its reason, or by a teammate — and the likely people still waiting for a decision. Readers nothing is known about are only counted. Only workspace members can subscribe, each for themselves. Nothing is sent before the audience is calibrated, to a workspace whose email is switched off, or on a day with nobody accepted or to review; `recent` says which happened each day. A day that was not sent for a reason other than "nobody new" keeps its people for the next email.
Query parameters
- keystringrequired
Response fields
- audiencestringrequired
- subscribedboolean or nulloptional
- Whether the caller is subscribed. null for an API key: a digest goes to a person, and a key is not one.
- subscribersintegerrequired
- workspace_email_suppressedbooleanrequired
- True stops every send for this workspace, whatever the subscriptions say.
- email_only_toarray of string or nulloptional
- When set, the only addresses mail may reach. null means no restriction.
- outputsPublicOutputsGaterequired
- Whether this audience's outputs are switched on, and why not if not. One definition for every output: the page, the export, sync-rule creation and the hourly push all read this. There is no pass mark — the gate asks only that the CURRENT definition has been measured.
- next_includes_people_sincetimestamp or nulloptional
- Where the next digest's window starts.
- recentarray of PublicDigestSendrequired
- Newest first, at most 7.
Status codes: 200 Successful Response · 401 Missing, malformed, unknown or revoked key · 403 Key lacks the required scope · 404 This workspace has no audience with that key · 422 A parameter failed validation · 429 Rate limit exceeded for this key
curl "https://app.signal.geysera.com/signal-api/v1/audiences/{key}/digest" \
-H "Authorization: Bearer sk_sig_..."{
"audience": "string",
"subscribed": false,
"subscribers": 0,
"workspace_email_suppressed": false,
"email_only_to": [
"string"
],
"outputs": {
"enabled": false,
"reason": "string"
},
"next_includes_people_since": "2026-09-11T14:23:07Z",
"recent": [
{
"sent_on": "string",
"status": "string",
"accepted": 0,
"likely": 0,
"candidate": 0,
"recipients": 0,
"delivered": 0,
"detail": "string"
}
]
}/audiences/{key}/digestSubscribe yourself to the audience's daily email, or stop
For the signed-in person only — the email goes to their own address on this workspace's team. An API key cannot subscribe: it has no mailbox, and a prospect list must not be sent to an address typed in.
Query parameters
- keystringrequired
Status codes: 200 Successful Response · 401 Missing, malformed, unknown or revoked key · 403 Key lacks the required scope, or the caller is a key rather than a signed-in person (the daily email goes to a person) · 404 This workspace has no audience with that key · 409 Your account on this workspace has no email address to send to · 422 A parameter failed validation · 429 Rate limit exceeded for this key
curl -X PUT "https://app.signal.geysera.com/signal-api/v1/audiences/{key}/digest" \
-H "Authorization: Bearer sk_sig_..." \
-H "Content-Type: application/json" \
-d '{"subscribed":false}'/audiences/{key}/overviewThe whole pipeline for one audience, stage by stage
One read for the pipeline: people who read the audience's pages (identified), how many have a professional profile and a company record (profiled), how many are likely or candidates and how many are already known customers (classified), the team's decisions (reviewed), the latest calibration, and runs in progress. Every count comes with what it is a count of.
Query parameters
- keystringrequired
Response fields
- audiencePublicAudienceDefinitionrequired
- An audience as the workspace defined it. Every term list may be empty, and an empty `page_patterns` matches NOBODY rather than everybody — the definition is fail-closed.
- stagesPublicAudienceStagesrequired
- The funnel from reader to reviewed decision, stage by stage. Each stage counts a different population, so the figures are not a single series that narrows: read each against its own `of`/`of_what`.
- calibrationPublicAudienceRun or nulloptional
- The run that measured this definition, if one has.
- calibration_stalebooleanrequired
- True when the definition changed after it was measured.
- outputsPublicOutputsGaterequired
- Whether this audience's outputs are switched on, and why not if not. One definition for every output: the page, the export, sync-rule creation and the hourly push all read this. There is no pass mark — the gate asks only that the CURRENT definition has been measured.
- active_runPublicAudienceRun or nulloptional
- The run in progress, if any.
- recent_runsarray of PublicAudienceRunrequired
- Newest first, at most 5.
- credits_this_monthintegerrequired
- automationPublicAutomationStaterequired
Status codes: 200 Successful Response · 401 Missing, malformed, unknown or revoked key · 403 Key lacks the required scope · 404 This workspace has no audience with that key · 422 A parameter failed validation · 429 Rate limit exceeded for this key
curl "https://app.signal.geysera.com/signal-api/v1/audiences/{key}/overview" \
-H "Authorization: Bearer sk_sig_..."{
"audience": {
"key": "string",
"name": "string",
"title_terms": [
"string"
],
"employer_terms": [
"string"
],
"page_patterns": [
"string"
],
"confirmed_roles": [
"string"
],
"industry_terms": [
"string"
],
"job_functions": [
"string"
]
},
"stages": {
"identified": {
"readers": 0,
"of_what": "string"
},
"profiled": {
"profiled": 0,
"with_company_record": 0,
"of": 0
},
"classified": {
"likely": 0,
"likely_read_pages": 0,
"candidate": 0,
"confirmed": {
"members": 0,
"known_accounts": 0,
"late_last_90_days": 0,
"browsing_without_buying_last_90_days": 0
}
},
"reviewed": {
"accepted": 0,
"rejected": 0,
"reviewed": 0,
"unreviewed": 0,
"to_review": 0,
"nothing_known": 0,
"automatic": 0,
"overruled": 0,
"confirmed": 0
}
},
"calibration": {
"id": "string",
"tenant_id": "string",
"audience_key": "string",
"mode": "string",
"status": "string",
"max_credits": 0,
"credits_spent": 0,
"counts": null,
"result": null,
"error": "string",
"started_by": "string",
"started_at": "2026-09-11T14:23:07Z",
"finished_at": "2026-09-11T14:23:07Z"
},
"calibration_stale": false,
"outputs": {
"enabled": false,
"reason": "string"
},
"active_run": {
"id": "string",
"tenant_id": "string",
"audience_key": "string",
"mode": "string",
"status": "string",
"max_credits": 0,
"credits_spent": 0,
"counts": null,
"result": null,
"error": "string",
"started_by": "string",
"started_at": "2026-09-11T14:23:07Z",
"finished_at": "2026-09-11T14:23:07Z"
},
"recent_runs": [
{
"id": "string",
"tenant_id": "string",
"audience_key": "string",
"mode": "string",
"status": "string",
"max_credits": 0,
"credits_spent": 0,
"counts": null,
"result": null,
"error": "string",
"started_by": "string",
"started_at": "2026-09-11T14:23:07Z",
"finished_at": "2026-09-11T14:23:07Z"
}
],
"credits_this_month": 0,
"automation": {
"auto_enrich": false,
"monthly_credit_budget": 0
}
}/audiences/{key}/prospectsPeople who look like this audience, with the evidence
`likely`: the person's title or employer matches the audience's terms. `candidate`: they read the audience's pages, but nothing known about them matches.
Evidence is limited to professional fields and the audience's own pages. Members of `confirmed_roles` (confirmed members), staff, GPC opt-outs and the suppression list never appear. `at_known_account` marks someone whose business email domain belongs to an account already in the excluded segment — a colleague at a known customer.
Query parameters
- keystringrequired
- tierlikely | candidate or nulloptional
- One tier only. Omit for both.
- include_reviewedbooleanoptionaldefault false
- Include people already accepted or rejected.
- limitintegeroptional1–200, default 50
- Max rows per tier.
- offsetintegeroptional0–100000, default 0
- Skip this many rows per tier (paging).
- sortname | email | title | employer | page_views | last_page_view | last_order or nulloptional
- Sort each tier by this, across all its rows (not just the page). Omit for the default: colleagues at known accounts last, then most recently active.
- directionasc | descoptionaldefault desc
- With `sort`. Empty values always sort last.
- sincetimestamp or nulloptional
- Only people who entered their level at or after this time (ISO 8601) — the daily list of new prospects. The level's start is stored by a daily refresh and after every run, so someone new today appears once the next refresh has seen them. People already in the audience when its history began (`classification_history_starts_at`) are never new.
Response fields
- audiencestringrequired
- audience_namestringrequired
- classification_history_starts_attimestamp or nulloptional
- Before this, `classified_at` is unknown rather than absent.
- likely_countintegerrequired
- From the query, not the length of `likely`.
- likely_viewed_audience_pagesintegerrequired
- likely_matched_on_profile_onlyintegerrequired
- Matched on their profile without viewing an audience page.
- candidate_countintegerrequired
- likelyarray of PublicProspectrequired
- Title or employer matches the audience.
- candidatearray of PublicProspectrequired
- Read the audience's pages, but nothing known about them matches.
- include_reviewedbooleanrequired
- sortPublicProspectSortrequired
- caveatsarray of stringrequired
- What these figures do and do not say. Render them with the numbers.
Status codes: 200 Successful Response · 401 Missing, malformed, unknown or revoked key · 403 Key lacks the required scope · 404 This workspace has no audience with that key · 422 A parameter failed validation · 429 Rate limit exceeded for this key
curl "https://app.signal.geysera.com/signal-api/v1/audiences/{key}/prospects" \
-H "Authorization: Bearer sk_sig_..."{
"audience": "string",
"audience_name": "string",
"classification_history_starts_at": "2026-09-11T14:23:07Z",
"likely_count": 0,
"likely_viewed_audience_pages": 0,
"likely_matched_on_profile_only": 0,
"candidate_count": 0,
"likely": [
{
"visitor_id": "string",
"email": "string",
"name": "string",
"title": "string",
"employer": "string",
"linkedin_url": "string",
"matched_on": [
"string"
],
"evidence_sources": {
"title": "string",
"employer": "string",
"job_history": "string",
"education": "string",
"industry": "string",
"job_function": "string",
"model_read": "string",
"audience_pages": "string"
},
"industry": "string",
"job_function": "string",
"headline": "string",
"job_history": "string",
"education": "string",
"model_read": {
"decision": null,
"reason": null,
"fields": null
},
"audience_pages": [
"string"
],
"audience_page_views": 0,
"last_audience_page_view": "2026-09-11T14:23:07Z",
"has_ordered": false,
"last_order_at": "2026-09-11T14:23:07Z",
"business_email": "string",
"business_email_source": "string",
"business_email_confidence": "string",
"at_known_account": false,
"review": "string",
"classified_at": "2026-09-11T14:23:07Z"
}
],
"candidate": [
{
"visitor_id": "string",
"email": "string",
"name": "string",
"title": "string",
"employer": "string",
"linkedin_url": "string",
"matched_on": [
"string"
],
"evidence_sources": {
"title": "string",
"employer": "string",
"job_history": "string",
"education": "string",
"industry": "string",
"job_function": "string",
"model_read": "string",
"audience_pages": "string"
},
"industry": "string",
"job_function": "string",
"headline": "string",
"job_history": "string",
"education": "string",
"model_read": {
"decision": null,
"reason": null,
"fields": null
},
"audience_pages": [
"string"
],
"audience_page_views": 0,
"last_audience_page_view": "2026-09-11T14:23:07Z",
"has_ordered": false,
"last_order_at": "2026-09-11T14:23:07Z",
"business_email": "string",
"business_email_source": "string",
"business_email_confidence": "string",
"at_known_account": false,
"review": "string",
"classified_at": "2026-09-11T14:23:07Z"
}
],
"include_reviewed": false,
"sort": {
"by": "string",
"direction": "string"
},
"caveats": [
"string"
]
}/audiences/{key}/reviewsPeople accepted or rejected, newest first
Every decision, with each person's professional fields as they stand now. `automatic` is true when the model decided, from its reading of the person's professional profile (`reason`); it follows the model's latest reading. A decision the team made is kept even if the definition later stops matching the person: it is a human judgement, and neither the rules nor the model overrule it.
`format=csv` answers with `text/csv`, not JSON.
Query parameters
- keystringrequired
- decisionaccepted | rejected or nulloptional
- Only this decision. Omit for both.
- limitintegeroptional1–500, default 100
- offsetintegeroptional0–100000, default 0
- sortdecided_at | name | email | title | employer | last_orderoptionaldefault decided_at
- Sort by this, across all rows.
- directionasc | descoptionaldefault desc
- Empty values always sort last.
- formatjson | csvoptionaldefault json
- csv: the whole list as a spreadsheet (limit and offset ignored) — the export. Same columns as the JSON.
Response fields
- audiencestringrequired
- totalintegerrequired
- All matching decisions, not the length of `reviews`.
- sortPublicReviewSortrequired
- reviewsarray of PublicReviewrequired
Status codes: 200 The decisions, as JSON — or the whole list as a spreadsheet when `format=csv` · 401 Missing, malformed, unknown or revoked key · 403 Key lacks the required scope · 404 This workspace has no audience with that key · 422 A parameter failed validation · 429 Rate limit exceeded for this key
curl "https://app.signal.geysera.com/signal-api/v1/audiences/{key}/reviews" \
-H "Authorization: Bearer sk_sig_..."{
"audience": "string",
"total": 0,
"sort": {
"by": "string",
"direction": "string"
},
"reviews": [
{
"visitor_id": "string",
"decision": "string",
"decided_at": "2026-09-11T14:23:07Z",
"automatic": false,
"reason": "string",
"email": "string",
"name": "string",
"title": "string",
"employer": "string",
"industry": "string",
"linkedin_url": "string",
"esp": {
"state": null,
"label": null,
"lists": null
},
"last_order_at": "2026-09-11T14:23:07Z",
"business_email": "string",
"business_email_source": "string",
"business_email_confidence": "string"
}
]
}/audiences/{key}/reviewsRecord the team's decision on one prospect
Idempotent: the latest decision for a person stands. Identified by email, the way a reader names a person, rather than by an internal id.
Query parameters
- keystringrequired
Status codes: 200 Successful Response · 401 Missing, malformed, unknown or revoked key · 403 Key lacks the required scope · 404 No audience with that key, or no identified visitor with that email · 422 A parameter failed validation · 429 Rate limit exceeded for this key
curl -X POST "https://app.signal.geysera.com/signal-api/v1/audiences/{key}/reviews" \
-H "Authorization: Bearer sk_sig_..." \
-H "Content-Type: application/json" \
-d '{"email":"string","decision":"accepted"}'/audiences/{key}/reviews/clearWithdraw the team's decision on one person
The person returns to the prospect list as undecided. For a decision made by mistake; a changed mind is a new decision (POST /reviews). A decision the model made is made again at the next refresh — overrule it with POST /reviews instead.
Query parameters
- keystringrequired
Status codes: 200 Successful Response · 401 Missing, malformed, unknown or revoked key · 403 Key lacks the required scope · 404 No audience with that key, or no decision recorded for that email · 422 A parameter failed validation · 429 Rate limit exceeded for this key
curl -X POST "https://app.signal.geysera.com/signal-api/v1/audiences/{key}/reviews/clear" \
-H "Authorization: Bearer sk_sig_..." \
-H "Content-Type: application/json" \
-d '{"email":"string"}'/audiences/{key}/runsLook up the people in an audience, within a credit limit
Spends enrichment credits. Runs the pipeline — professional profile, then company record — over the audience's people, stopping before any lookup that would cross `max_credits`. Nobody is looked up twice; suppressed people and opt-outs are skipped; the workspace's credit caps and this audience's monthly budget (set with its daily lookups) still apply. Estimate first with POST /{key}/runs/estimate.
A key needs the `enrich` scope; `audiences` alone can never spend.
Query parameters
- keystringrequired
- X-Idempotency-Keystring or nulloptional
Status codes: 200 Successful Response · 401 Missing, malformed, unknown or revoked key · 403 Key lacks the required scope · 404 This workspace has no audience with that key · 409 A run for this audience is already in progress · 422 A parameter failed validation · 429 Rate limit exceeded for this key · 503 The run could not be started; nothing was spent
curl -X POST "https://app.signal.geysera.com/signal-api/v1/audiences/{key}/runs" \
-H "Authorization: Bearer sk_sig_..." \
-H "Content-Type: application/json" \
-d '{"mode":"prospects","max_credits":0}'/audiences/{key}/runsRecent runs for an audience, newest first
Query parameters
- keystringrequired
Response fields
- runsarray of PublicAudienceRunrequired
- Newest first, at most 20.
Status codes: 200 Successful Response · 401 Missing, malformed, unknown or revoked key · 403 Key lacks the required scope · 404 This workspace has no audience with that key · 422 A parameter failed validation · 429 Rate limit exceeded for this key
curl "https://app.signal.geysera.com/signal-api/v1/audiences/{key}/runs" \
-H "Authorization: Bearer sk_sig_..."{
"runs": [
{
"id": "string",
"tenant_id": "string",
"audience_key": "string",
"mode": "string",
"status": "string",
"max_credits": 0,
"credits_spent": 0,
"counts": null,
"result": null,
"error": "string",
"started_by": "string",
"started_at": "2026-09-11T14:23:07Z",
"finished_at": "2026-09-11T14:23:07Z"
}
]
}/audiences/{key}/runs/{run_id}/cancelStop a run; it makes no further paid lookups
Takes effect before the next paid lookup. Credits already spent stay spent, and what was found stays found. Also releases a run whose worker stopped, which would otherwise block every later run of this audience.
Query parameters
- keystringrequired
- run_idstringrequired
Status codes: 200 Successful Response · 401 Missing, malformed, unknown or revoked key · 403 Key lacks the required scope · 404 No audience with that key, or no such run · 409 That run has already finished · 422 A parameter failed validation · 429 Rate limit exceeded for this key
curl -X POST "https://app.signal.geysera.com/signal-api/v1/audiences/{key}/runs/{run_id}/cancel" \
-H "Authorization: Bearer sk_sig_..."/audiences/{key}/runs/estimateWho a run would look up, and the most it could spend
Spends nothing. Counts the people a run would cover and, of those, the ones not looked up yet — the only ones a run pays for — and the most that could cost in enrichment credits. A lookup that finds nobody is free, so a run usually spends less.
Query parameters
- keystringrequired
Status codes: 200 Successful Response · 401 Missing, malformed, unknown or revoked key · 403 Key lacks the required scope · 404 This workspace has no audience with that key · 422 A parameter failed validation · 429 Rate limit exceeded for this key
curl -X POST "https://app.signal.geysera.com/signal-api/v1/audiences/{key}/runs/estimate" \
-H "Authorization: Bearer sk_sig_..." \
-H "Content-Type: application/json" \
-d '{"mode":"prospects"}'/audiences/summariesOne line per audience: the funnel counts a collapsed row shows
Every audience in the workspace with the four counts that head its row — identified, profiled, likely, accepted — and whether its outputs are on.
This exists because a page listing audiences should cost one request, not one per audience. It is deliberately NOT `GET /{key}/overview` repeated: that call also computes Account Watch, the calibration, recent runs and the month's credits, which a collapsed row does not show. Measured on the pilot, per audience: the overview is 3.2s, these counts are 1.4s.
The counts come from the same functions the overview uses, so the two cannot disagree about what "likely" means.
Query parameters
No parameters.
Response fields
- audiencesarray of PublicAudienceSummaryrequired
Status codes: 200 Successful Response · 401 Missing, malformed, unknown or revoked key · 403 Key lacks the required scope · 422 A parameter failed validation · 429 Rate limit exceeded for this key
curl "https://app.signal.geysera.com/signal-api/v1/audiences/summaries" \
-H "Authorization: Bearer sk_sig_..."{
"audiences": [
{
"key": "string",
"name": "string",
"identified": 0,
"profiled": 0,
"likely": 0,
"accepted": 0,
"outputs": {
"enabled": false,
"reason": null
}
}
]
}/copilot/askAsk a question about this workspace's commerce data
Natural-language analysis over the Signal metric layer.
## When to Use - Any question about revenue, AOV, products, pricing, page conversion or what sells together.
## Notes - Returns `refusal` instead of `answer` when the question cannot be answered honestly, because a correlation presented as a cause is worse than no answer. - A causal question ("did X drive Y") is ANSWERED, with an interval and an explicit verdict when that interval includes zero. This note used to say such questions were "refused by design until a causal identification tier exists"; the tier exists and answers, so a caller coding to that sentence would never have asked one. - When a causal question names no date, the analysis SUBSTITUTES the largest recorded price change in the window as the intervention and says so in the answer. That substitution is the contract — not a refusal, and not an analysis of the thing you named. - `trace` lists every tool that ran with its coverage and the exact SQL. - Numbers in `answer` are checked against the tool results before returning; a draft containing an unsupported figure is rejected rather than shipped.
Query parameters
No parameters.
Status codes: 200 Successful Response · 401 Missing, malformed, unknown or revoked key · 403 Key lacks the required scope · 422 A parameter failed validation · 429 Rate limit exceeded for this key
curl -X POST "https://app.signal.geysera.com/signal-api/v1/copilot/ask" \
-H "Authorization: Bearer sk_sig_..." \
-H "Content-Type: application/json" \
-d '{"question":"string"}'/copilot/streamAsk a question, streamed per analysis step
Identical to `/ask` but emits an SSE event as each stage completes, so the UI can show `planning → running 3 tools → checking → writing` instead of a spinner.
Events: `plan`, `execute`, `verify`, `answer_segment`, `narrate`, `done`, `error`. The `done` event carries the same payload `/ask` returns.
`answer_segment` carries the answer a sentence at a time as it is written. Each segment has already passed the numeric guard — the same check the complete answer faces — so a segment is safe to render the moment it arrives. Concatenating every `answer_segment` yields exactly the `answer` field of the final `done` payload; clients that would rather not stream can ignore these and use `done` alone.
Query parameters
No parameters.
Status codes: 200 Successful Response · 401 Missing, malformed, unknown or revoked key · 403 Key lacks the required scope · 422 A parameter failed validation · 429 Rate limit exceeded for this key
curl -X POST "https://app.signal.geysera.com/signal-api/v1/copilot/stream" \
-H "Authorization: Bearer sk_sig_..." \
-H "Content-Type: application/json" \
-d '{"question":"string"}'/copilot/threadsList this workspace's conversations
Most recently updated first. Powers the history sidebar. Also carries the workspace's display name, which the page greets the user with, and which kinds of data it holds — both ride along here rather than costing a round trip each.
`capabilities` reports whether this workspace has orders, pixel events and identified accounts. A client uses it to offer example questions that are answerable here: a workspace with no connected store has no order source at all, so every commerce question refuses, and offering them is how a new tenant's first four questions all came back empty.
Query parameters
No parameters.
Response fields
- threadsarray of PublicThreadSummaryrequired
- workspacestring or nulloptional
- The workspace's company name, or its domain, or null.
- capabilitiesPublicWorkspaceCapabilitiesrequired
- What this workspace has connected, and so what can be asked about it. All three false can mean nothing is connected OR that the check itself failed; it is not proof of an empty workspace.
Status codes: 200 Successful Response · 401 Missing, malformed, unknown or revoked key · 403 Key lacks the required scope · 422 A parameter failed validation · 429 Rate limit exceeded for this key
curl "https://app.signal.geysera.com/signal-api/v1/copilot/threads" \
-H "Authorization: Bearer sk_sig_..."{
"threads": [
{
"id": "string",
"thread_key": "string",
"title": "string",
"created_at": "2026-09-11T14:23:07Z",
"updated_at": "2026-09-11T14:23:07Z",
"question_count": 0
}
],
"workspace": "string",
"capabilities": {
"orders": false,
"pixel": false,
"accounts": false
}
}/copilot/threads/{thread_id}A conversation and its messages
Full history for one conversation, including each answer's disclosures and the tool trace behind it — so an answer someone acted on months ago is still auditable.
`thread_id` accepts **either** identifier: the `thread_key` you chose and sent to `/ask`, or the server-generated `id` that `/threads` returns. They are different values and both name the same conversation.
A thread belonging to another workspace returns 404 rather than 403: the tenant filter *is* the authorization check, and 404 avoids confirming the id exists. An identifier matching nothing returns 404 too, whatever its shape — it used to return 500 for anything that was not a UUID.
Query parameters
- thread_idstringrequired
Response fields
- idstringrequired
- thread_keystringrequired
- titlestring or nulloptional
- created_attimestamprequired
- updated_attimestamprequired
- messagesarray of PublicThreadMessagerequired
Status codes: 200 Successful Response · 401 Missing, malformed, unknown or revoked key · 403 Key lacks the required scope · 404 No conversation with that identifier in this workspace — also returned for a thread owned by another workspace, which avoids confirming the id exists · 422 A parameter failed validation · 429 Rate limit exceeded for this key
curl "https://app.signal.geysera.com/signal-api/v1/copilot/threads/{thread_id}" \
-H "Authorization: Bearer sk_sig_..."{
"id": "string",
"thread_key": "string",
"title": "string",
"created_at": "2026-09-11T14:23:07Z",
"updated_at": "2026-09-11T14:23:07Z",
"messages": [
{
"id": "string",
"role": "string",
"content": "string",
"kind": "string",
"question_kind": "string",
"disclosures": [],
"trace": [],
"rating": "string",
"created_at": "2026-09-11T14:23:07Z",
"turn_id": "string"
}
]
}/recommendationsSuggested next actions that have not been done yet.
Recommended actions that are currently open.
"Open" means what it means in the app: status `new`, or `snoozed` with the snooze elapsed. Read-only — this endpoint does not change a recommendation's status.
Query parameters
No parameters.
Response fields
- recommendationsarray of PublicRecommendationrequired
- totalintegerrequired
- window_daysintegerrequired
Status codes: 200 Successful Response · 401 Missing, malformed, unknown or revoked key · 403 Key lacks the required scope · 422 A parameter failed validation · 429 Rate limit exceeded for this key
curl "https://app.signal.geysera.com/signal-api/v1/recommendations" \
-H "Authorization: Bearer sk_sig_..."{
"recommendations": [
{
"id": "string",
"play_id": "string",
"kind": "string",
"scope": "string",
"priority": 0,
"action": "string",
"rationale": "",
"source": "",
"evidence": null,
"updated_at": "2026-09-11T14:23:07Z"
}
],
"total": 0,
"window_days": 0
}/visitorsList identified people with name, email, title and company. Contact fields are hidden on the free plan.
Visitors belonging to accounts inside the plan's resolution cap.
Two rules are load-bearing and mirror the dashboard exactly:
* The free-plan rule is the dashboard's, from agents/signal/free_allowance.py: the first 150 people identified each month are shown in full; beyond that contact fields are withheld and a company known only through such people is masked. The paywall has to hold on every surface or it holds on none. * `is_locked` says so explicitly rather than leaving an integrator to guess why the values are null.
Query parameters
- pageintegeroptional1–1000, default 1
- Page number, from 1. Capped at 1000, so `page x page_size` bounds what offset pagination can reach: 50,000 rows at the default page size and 200,000 at the maximum. For a full export use `page_size=200`; to go beyond 200,000, narrow the query with a filter. Asking for page 1001 returns 422 rather than an empty page, so a walk cannot mistake the ceiling for the end of the data.
- page_sizeintegeroptional1–200, default 50
- Rows per page, 1-200. The default of 50 is sized for a screen; a full export should ask for 200, which also multiplies how far the 1000-page ceiling reaches.
- min_intentinteger or nulloptional0–100
- 0-100 display scale, as shown in the app.
- classificationlead | customer | excluded or nulloptional
- Filters on THIS PERSON's status (see `status`). Omitted: everyone except excluded. `excluded`: only them.
- segmentprovider | dtc | internal | unclassified or nulloptional
- Filters on what the STORE says this person is. `provider` is the trade segment — wholesale, partner, and their applicant/lead roles.
Response fields
- visitorsarray of PublicVisitorrequired
- totalintegerrequired
- pageintegerrequired
- page_sizeintegerrequired
Status codes: 200 Successful Response · 401 Missing, malformed, unknown or revoked key · 403 Key lacks the required scope · 422 A parameter failed validation · 429 Rate limit exceeded for this key
curl "https://app.signal.geysera.com/signal-api/v1/visitors" \
-H "Authorization: Bearer sk_sig_..."{
"visitors": [
{
"id": "string",
"company_domain": "string",
"company_name": "string",
"intent_score": 0,
"account_classification": "string",
"status": "string",
"orders_count": 0,
"has_ordered": false,
"store_segment": "unclassified",
"store_roles": [
"string"
],
"resolved_email": "string",
"resolved_name": "string",
"resolved_title": "string",
"employer": "string",
"first_visit_at": "2026-09-11T14:23:07Z",
"last_visit_at": "2026-09-11T14:23:07Z",
"visit_count": 0,
"is_locked": false
}
],
"total": 0,
"page": 0,
"page_size": 0
}Asking questions in natural language
With a copilot-scoped key you can ask about your workspace instead of assembling the query yourself. The response carries the answer, the tools that ran, and the figures behind it.
- POST /signal-api/v1/copilot/askcopilot
- Ask a question about this workspace's commerce data
- POST /signal-api/v1/copilot/streamcopilot
- Ask a question, streamed per analysis step
- GET /signal-api/v1/copilot/threadscopilot
- List this workspace's conversations
- GET /signal-api/v1/copilot/threads/{thread_id}copilot
- A conversation and its messages
Pagination
- paramsconvention
- page (1-based), page_size (default 50, maximum 200)
- responseconvention
- an array named for the resource (`accounts`, `visitors`) plus total, page, page_size. There is no `items` key.
- noteconvention
- total is capped at what your plan may resolve, never the raw row count
- out_of_rangeconvention
- page_size above 200 is rejected with 422 rather than silently clamped, so a script cannot believe it read more than it did
Unknown query parameters are ignored, not rejected. A misspelled parameter returns a normal response with defaults applied. A person notices; a script reports the wrong number confidently.
Rate limits
300 requests per 60 seconds, per key. Exceeding it returns 429.
Errors
Every error carries the same shape:
- error_codefield
- stable machine-readable code; one of CLIENT_ERROR, CONFLICT, FORBIDDEN, GONE, INTERNAL, METHOD_NOT_ALLOWED, NOT_FOUND, PAYLOAD_TOO_LARGE, PAYMENT_REQUIRED, RATE_LIMITED, UNAUTHORIZED, VALIDATION_ERROR
- messagefield
- human-readable description
- correlation_idfield
- echo of X-Correlation-ID; quote it in support requests
- detailsfield
- optional structured context
Branch on details rather than parsing message. Quote correlation_id when asking us about a specific request.
What is not available yet
- Writes. No endpoint creates or changes anything. The idempotency and concurrency contracts those need are not built, and shipping writes without them would be a promise we could not keep.
- Per-key narrowing below the workspace. Every key sees the whole workspace.
- Webhooks for these resources. Poll, or use Auto-sync Rules in the app.
Discovery
Both of these are generated from the running service. If this page and those ever disagree, they are right.
- https://app.signal.geysera.com/agent-api/capabilities — every action a key can call, with the scope each needs. Public, no key required.
- https://app.signal.geysera.com/signal-api/v1/openapi.json — OpenAPI for the REST endpoints.
Natural language
What it can read
The 55 read tools the copilot can call, published by the service and derived from its own registry. Write tools are absent because an API key cannot reach them — changes are made in the dashboard, or by asking in a signed-in session where each one is described and confirmed first.
- aov_decompositionread
- Splits AOV into average unit price x average units per order, before vs after a boundary date.
- attribution_overviewread
- Which channels, sources and campaigns the attributed sessions came from, and what they were worth — the same cached rollup the Attribution page renders.
- audience_acceptedread
- The people ACCEPTED into an audience, newest decision first — by the model from their professional profile (`automatic`, with its `reason`) or by the team: name, email, business email (their own address when it is at a work domain), title, employer, industry, last order.
- audience_account_watchread
- The audience's confirmed members (people the store's own role marks as members) worth a call: `late` — their reorder is overdue against their own ordering rhythm — and `browsing_without_buying` — identified on the site in the window with no order since.
- audience_digestread
- The audience's daily email — the people accepted since the last one, and those left to review: whether the person asking is subscribed, how many teammates are, and what was sent each of the last seven days — or why nothing was (not calibrated, the workspace's email is switched off, nobody new).
- audience_overviewread
- The whole pipeline for one audience, as the Audience Classification page shows it: identified (people who read its pages, 90 days), profiled (of those, with a professional profile / company record), classified (likely, how many of them read the pages, candidates, known accounts and how many became late), reviewed (accepted, rejected; `to_review` — likely the model's reading did not settle; `nothing_known` — readers nothing known about matches; and how the model's decisions stand: `automatic`, `overruled` by the team, `confirmed`), the latest calibration and whether the definition changed since, whether exports are on and why not, runs in progress, and this month's credits.
- audience_prospectsread
- People browsing this site who look like a professional audience the store does not yet sell to — e.g.
- audience_rejectedread
- The people REJECTED from an audience, newest decision first — by the model from their professional profile (`automatic`, with its `reason`) or by the team — with their title, employer and email.
- audience_run_estimateread
- Before starting an enrichment run: how many people it would look up and the most enrichment CREDITS (not money) it could spend.
- audience_runsread
- Recent enrichment runs for an audience: mode, status, credits spent, what was found, and — for calibration — how many known members and how many non-members the audience labels likely.
- causal_impactread
- Did a dated change actually CAUSE a metric to move? Fits a Bayesian interrupted time series: models the pre-change trend, predicts what would have happened without the change, and reports the difference with a credible interval.
- credit_usageread
- Credits used and left this month (UTC), every limit with when it resets and which runs out first, what started the lookups (audience runs, enrichment rules, a person, bulk lookups) with how many were found and how many were business contacts, and the latest lookups line by line.
- customer_accountsread
- The store's own customers by email, with the role the store gives them (provider, wholesale, …) and their LIFETIME order count, revenue and AOV.
- customer_countsread
- How many distinct people bought, how often they came back, and how many were new.
- data_coverageread
- What this workspace can and cannot answer for a window: order count, pixel volume, how many orders link to a pixel session, and which days the order sync missed.
- deletion_statusread
- Check whether a deletion or opt-out request has been honoured for one email address, and when.
- discount_codesread
- Which discount codes were used, with orders, customers, revenue, discount given and AOV per code.
- dry_run_sync_ruleread
- Show exactly which contacts a rule WOULD push, without pushing anything.
- enrichment_configread
- Who this workspace enriches and what it can still spend.
- esp_connectionread
- Every app this workspace can connect — email platforms, the store, Slack, webhooks — with each one's status (connected, attention: set up but not working, not_connected, coming_soon).
- esp_syncread
- Whether ESP sync is on (people are sent to the email platform only while it is), when it was turned on, who may be sent today counted by reason (subscribed / company email address / found work email) and how many wait for a known country, and where a workspace OWNER turns it on or off (fix_label).
- get_accountread
- Everything known about ONE account by domain: firmographics, its visitors, orders and ESP contacts.
- get_conversationread
- Read back one past conversation — its questions, answers and the caveats each answer carried.
- identification_statusread
- Is identification actually running right now, and when did it last resolve anyone? Use when a workspace says visitors stopped appearing — a quiet pipeline and quiet traffic look identical on the accounts page, and this is what tells them apart.
- intent_pagesread
- The page rules this workspace uses to score buying intent, and what share of its real traffic they match.
- journey_pathsread
- How visitors move BETWEEN pages — the page-to-page graph behind the Journey view, reading the same nightly rollup the page reads.
- lapsing_accountsread
- Which of the store's OWN known buyers in a segment have stopped ordering.
- learned_plansread
- What this assistant has learned to do for recurring questions in this workspace — which tools it reuses, how many times each was confirmed, and which have been retired.
- list_alert_destinationsread
- Slack destinations receiving this workspace's alerts, and whether each is active.
- list_api_keysread
- The workspace's API keys — id, name, prefix, last used.
- list_audiencesread
- Every audience this workspace has defined — name, key, title and employer terms, pages, known-member segment — and `store_roles`: what set_audience's confirmed_roles may name (the store's roles, Geysera's, and each product sold, as 'product:<id>' labelled 'Bought …').
- list_conversationsread
- Your own past conversations in this workspace, most recently updated first, with each one's title, question count and when it was last touched.
- list_ordersread
- The individual orders themselves, most recent first, with status, total, items, discount and the customer.
- list_teamread
- Who has access to this workspace: members with their roles, invitations still pending, and how many seats are left.
- list_webhooksread
- Registered webhook endpoints and whether each is enabled.
- membership_conversion_pathsread
- What is bought together.
- open_recommendationsread
- The workspace's open recommended actions, from both the journey and attribution surfaces.
- order_sourcesread
- Where orders came from according to the STORE, and how they were checked out: the channel it attributed each order to (utm, organic, referral, typein), whether the order was a subscription renewal, the payment method and the shipping method — with orders and revenue for each, plus what shipping was charged and how much revenue shipped free.
- page_funnelread
- Per page: sessions that saw it, and how many added to cart, reached checkout and purchased in that same session.
- page_to_product_attributionread
- Which pages were seen in the same session as a purchase, optionally restricted to orders containing a given product.
- preview_enrichment_ruleread
- Count an UNSAVED enrichment rule step by step — how many people each step leaves — before creating it.
- price_historyread
- When a product's price actually changed.
- product_catalogueread
- The products the store SELLS — every item in the catalogue with its SKU, price, categories and stock status, whether or not it has ever sold.
- product_performanceread
- Revenue, orders, units and quantity-weighted unit price per product.
- recall_factsread
- What this workspace has taught the assistant, and when.
- revenue_and_aovread
- Revenue, order count, AOV, discount and units per period (day/week/month).
- search_accountsread
- The accounts board: search by name or domain, filter by classification and minimum intent, sort, paginate.
- search_visitorsread
- Individual identified people, with the filters the Visitors page offers: text search, classification, minimum intent, whether they have ordered, whether they have been contacted, which intent page they hit, store segment and when they were last seen.
- subscriptionread
- This workspace's GEYSERA plan — what they pay us — its status, when it next renews, and whether it is set to cancel at the end of the period.
- sync_rule_targetsread
- The lists an auto-sync rule can send to in a connected email platform, read live from it (marking the ones Geysera made), or — with weekly_csv — the owners and admins who may receive a weekly CSV.
- sync_rulesread
- The auto-sync rules that push identified accounts to the connected ESP: their filters, status and last run.
- visiting_accountsread
- Which organisations visited the site, ranked by intent score, with their visits, visitors and any orders.
- visitor_enrichmentread
- What enrichment found for ONE visitor, by their email address, and whether it has expired.
- visitor_journeyread
- The pages ONE visitor viewed, in order, with time on each.
- workspace_setupread
- Is this workspace actually working? Store connection, onboarding progress, pixel status, identification health and the current plan and usage, in one call.
55 tools are listed above. 55 are not.
They change the workspace. An API key reaches read tools only, whatever it asks for — the planner's tool list is filtered by how you authenticated, so a key cannot name one. Listing them would advertise a capability that always refuses.
A signed-in dashboard session. Changes are proposed first: the turn returns pending_confirmation describing the effect, and nothing runs until it is approved.
More documentation
Guides & case studies
This page covers the endpoints. Longer-form documentation lives on the app host, next to the service it describes, at https://app.signal.geysera.com/developers.
Get started
- Quickstart
Create a key and make your first call.
- Install the pixel
The tag, what it collects, and how consent works. Prerequisite for every workflow here.
Guides
- Developer guide
The three surfaces, conventions, the copilot, webhooks, and the things that will surprise you.
Workflows
- Receive identified visitors by webhook
Get a POST the moment a visitor is identified, and not miss any.
- Email subscribers who viewed a product
One email about the product they looked at, and nothing if they bought it anyway. Built in Klaviyo on a Signal event.
- Export and keep in sync
Pull the account list once, then keep it current without refetching it all.
- Spend enrichment credits only where they pay back
Stop paying to enrich visitors you will never contact, without losing the ones you would have called.
- Prove a change actually worked
Did the price rise move AOV, or was it the season? The one analysis that supports a causal claim.
- Give Signal to any LLM as tools
Four tool definitions and a loop. Works with Claude, GPT, Gemini, OpenRouter or a local model.
- Ask questions in natural language
Use the copilot from code, and read the parts of the answer that matter.
- Alert your team when a target account appears
Filter the webhook against your named-account list and post to Slack within minutes.
- Build a daily call list
One call, a ranking you can read, and a prompt that is willing to say leave this one alone.
- Write a first-touch email from what they read
An opener grounded in real behaviour, with the SKIP rule that stops it inventing one.
- Spot a buying committee
Three people from one company in a week is one deal, not three leads.
- Notice an account going quiet
The cheapest churn signal you have, weeks before the renewal conversation.
- Fill your CRM with who is actually visiting
Compare the two account sets first; the gap is usually not the one you assumed.
- Find the pages that create pipeline
Rank content by who it brings rather than how many, and read the coverage first.
- Score and route inbound leads
Use what someone did before the form to decide who should get it.
- Keep an ad audience in sync
Suppress customers, retarget high intent, and the consent questions to answer first.
- A weekly revenue brief your LLM writes
Gather deterministically, write with a model, and propagate the disclosures.
- Watch your own data quality
The workflow that protects every other workflow: knowing when the data went quiet.
Playbooks
- Account-based marketing, end to end
Six stages, the two that carry the value, and the human gate to keep.
- Reach the buyer while the signal is warm
Identification is hourly, not instant — and the thing that decays is days, not minutes. Design for same-day, every day.
- Win back the customers who are already returning
Your store knows who lapsed. Signal knows which of them is back on the site this week — measured, that is 33 people, not 8,926.
- Protect your margin when you change prices
AOV rises after a price rise by arithmetic, not evidence. What to check instead, and the one number Signal cannot give you.
- Turning content into pipeline
Measure content on who it brings, then prove the change with a causal test.
- Let an LLM run your workspace
The response contract, refusals, the confirmation gate, and what a key can never do.
Case studies
- Most of your visitors were not people
69% of one account's events were unflagged crawlers.
- The pipeline that reported success while doing nothing
A job that caught its own failure and completed, for days.
- Why attribution covers less than you think
The ceiling is structural, and knowing the number is the useful part.