On this page

Quickstart

Five minutes from nothing to your first identified company.

Signal tells you which companies and people visit your website. This page gets you a working integration; the guides go deeper.


1. Get a key

Sign in, go to Exports & API, name a key and choose what it reaches.

Scope Reaches
read the REST endpoints
copilot the natural-language endpoint

The key is shown once. It is stored hashed — if you lose it, create another and revoke the old one. Scopes are fixed at creation: to change what a key reaches, make a new one. A credential whose powers can grow after it was reviewed is one nobody can reason about.

2. Your first call

curl "https://app.signal.geysera.com/signal-api/v1/accounts?page_size=5" \
  -H "Authorization: Bearer sk_sig_…"
{ "accounts": [ … ], "total": 1927, "page": 1, "page_size": 5 }

That is the whole authentication story: one header.

3. Ask a question instead

With a copilot-scoped key:

curl https://app.signal.geysera.com/agent-api/signal/copilot/ask \
  -H "Authorization: Bearer sk_sig_…" -H "Content-Type: application/json" \
  -d '{"question": "which companies visited most last week?", "thread_id": null}'

The response carries the answer, the tools that ran, and the figures behind it. It is read-only whatever it is asked.

4. Stop polling

# Register an endpoint; we POST the moment someone is identified.

Webhooks are how most integrations should work. Polling asks us every few minutes whether anything happened; a webhook tells you when it does. → Receive identified visitors by webhook


Before you build: four things that will save you a day

1. total includes masked records. On the free plan, people identified after the first 150 of their month come back with is_locked: true and their contact fields null. Count is_locked: false rows when you mean "people we can contact".

2. Unknown query parameters are rejected, not ignored. ?limit=10 returns 422 VALIDATION_ERROR; details.errors[].field names limit, and the message lists the parameters the endpoint accepts. A typo'd filter fails loudly instead of returning unfiltered data — so treat a 422 as a bug in your call, not a transient error to retry.

3. 401 and 403 mean different things. 401 = the key is missing, malformed, unknown or revoked. 403 = the key is fine and lacks the scope. Re-issuing a key fixes the first and never the second.

4. Identification yield depends on your audience, not your setup. Two accounts with identical configuration matched ~50% and ~0% of visitors. That is a property of who visits you, not a misconfiguration. Read why attribution covers less than you think before you build a target around a rate.


Discover the surface instead of trusting this page

curl https://app.signal.geysera.com/agent-api/capabilities

Public, no key needed, and generated from the routes' own auth dependencies — so it cannot describe an endpoint that does not exist or omit one that does. It carries the endpoint list with the scope each needs, the pagination bounds, the rate limit and its scope, the error vocabulary, and the response headers you should act on.

Every number in this documentation is derived from that endpoint or asserted against the code by a test. If this page and /capabilities ever disagree, /capabilities is right — and that is a bug we want to hear about.


Where to go next

Guides — what you are trying to do

Reference

  • Full API guide — scopes, conventions, limits
  • GET /signal-api/v1/openapi.json — OpenAPI 3.1 for the REST endpoints

Case studies — what we learned running this


What is not available yet

Stated plainly so you do not design around something that does not exist:

  • Writes on visitors, accounts, attribution and recommendations. Those are read-only. A visitor's status or a recommendation changes through the copilot or the dashboard. Audiences and copilot conversations do accept writes — the guide's Audiences section lists them, and POST /audiences/{key}/runs spends enrichment credits, so treat it like any other paid call.
  • An optimistic-locking contract on the writes that do exist. Audience writes take an Idempotency-Key, so retrying one is safe. There is no expected_updated_at check, so two people editing the same audience overwrite each other rather than one getting a conflict.
  • Per-key narrowing below the workspace. Every key sees the whole workspace.
  • More than one webhook event type. Today: signal.visitor_identified.

This page is maintained next to the service it describes and rendered here. Read it as markdown if you are pointing an agent at it.