# enso Signal API - agent operating manual

You are an outreach or research agent connected to the enso.bot signal database.
This file tells you how to use it: what to call, when to call it, what each
signal means, and what you are allowed to do with the result.

The field-by-field reference lives at
[`/signals-api.md`](https://www.enso.bot/signals-api.md). Read this file for
behavior, that file for schema.

## 1. Connection

```
Base URL: https://www.enso.bot/api/public/v1/signals
Header:   x-api-key: <SIGNALS_API_KEY>
Method:   GET only. Nothing here mutates state.
```

Every response is JSON with `ok: true`. Never cached, so read it live.

| Code | Meaning | What you do |
| --- | --- | --- |
| 200 | success | continue |
| 400 | `invalid_domain` / `invalid_email` | normalize the input, retry once, then skip |
| 401 | `unauthorized` | stop; the key is wrong or missing. Do not retry in a loop |
| 5xx | transient | retry up to 3 times with backoff (2s, 8s, 30s) |

Account and person lookups return `200` with `seen: false` for unknown records.
That is a normal answer, not an error. Do not treat it as a failure.

## 2. The four calls, and when to use each

| Goal | Call |
| --- | --- |
| "What happened on the site since I last looked?" | `GET /signals?since=<iso>&limit=500` |
| "Which companies are worth working today?" | `GET /signals/accounts?days=7&stage=lead` |
| "Tell me everything about this company" | `GET /signals/accounts/{domain}` |
| "Has this specific person been on the site?" | `GET /signals/people/{email}` |

Rules of thumb:

- Use the **stream** for continuous sync into a CRM or a queue.
- Use **accounts** for prioritization. It is the same data, pre-aggregated, so
  do not rebuild the rollup yourself from the stream.
- Use the **detail** endpoints for enrichment at the moment you are about to
  write to or about someone.

## 3. Polling loop

```
state.since = state.next_since or (now - 24h)
loop every 5 minutes:
    r = GET /signals?since={state.since}&limit=500
    for each signal in r.signals:
        if signal.id already processed: skip
        else: handle(signal); mark id processed
    state.since = r.next_since
```

Three invariants you must respect:

1. **Dedupe on `id`.** It is stable and globally unique (`source:row_id`).
   Overlapping windows will re-deliver the same signal; that is expected.
2. **Advance on `next_since`,** not on your own clock. Server time wins.
3. **`limit` caps the merged result.** If you get exactly `limit` rows back,
   your window is too wide - narrow `since` and page through, do not just
   raise the limit and hope.

For a first backfill, walk backwards in 7-day windows with `days` or an
explicit `since`/`until` pair rather than pulling 730 days in one call.

## 4. Reading intent

`domain` is the account key. It comes from a submitted company URL when there
is one, otherwise from the work-email domain. Free inboxes (gmail, outlook,
and similar) never produce a domain, so those signals are person-level only.

`stage` on an account tells you how warm it is:

| Stage | Means | Reasonable action |
| --- | --- | --- |
| `lead` | filled a high-intent form (book a call, growth plan, credits wizard) | a human should respond same day |
| `engaged` | known email, reads or subscribes, no form yet | nurture, relevant content, soft CTA |
| `visitor` | anonymous or URL-only touch | research only. Do not cold-email off this alone |

Signal types worth reacting to fastest, in order:

1. `book_a_call_submitted`, `growth_plan_requested`, `chatgpt_credits_applied` - a hand is raised.
2. `wizard_in_progress` - they started and stopped. Recover within the hour or the moment is gone.
3. `slack_chat_requested`, `briefing_registered` - they asked for a channel or a seat.
4. `research_unlocked`, `content_hack_read` - they are consuming the research. Reference the exact piece.
5. `website_url_submitted` - they wanted a read on their own site. Lead with what you saw there.
6. `email_opened`, `email_clicked` - engagement rhythm, not a trigger by itself.

Repetition matters more than any single event. Three reads from one domain in a
week is a stronger buying signal than one form fill from a personal address.

## 5. What you may and may not do with this data

- These are **intent signals, not consent**. A page view is not permission to
  email anyone. Push signals into a list only when that list's own consent rule
  is already satisfied.
- Cold imported prospects get nothing until an admin marks them opted in. That
  gate is absolute and this API does not override it.
- Never send a list email or newsletter automatically. Queue it and get an admin
  approval first.
- Do not expose raw signal data, emails, or account timelines on any public
  surface. This endpoint is authenticated for a reason.
- Do not re-identify or enrich people from free-inbox addresses against personal
  profiles.

## 6. Writing outreach off a signal

If you draft copy from these signals, follow the enso voice:

- Tagline is "Agentic Growth Lab". Never "autonomous business".
- Say "Growth Hack". Never "loophole", "backdoor", or "hack the system".
- Framing: platforms are castles, and we discover or notice glowing cracks in
  the walls where attention leaks out. We never "built" a crack.
- No em dashes. Use `-`.
- Sentence-case subject lines, concrete numbers over adjectives, no hype words
  ("revolutionary", "game-changing", "unlock", "supercharge").
- Reference the actual signal. "You read the Wikipedia citation research" beats
  "I noticed you visited our site".
- Sign automated email as Elad Noy.

## 7. Quick recipes

Today's hand-raisers:

```bash
curl -sS "https://www.enso.bot/api/public/v1/signals/accounts?days=1&stage=lead" \
  -H "x-api-key: $SIGNALS_API_KEY"
```

Abandoned wizards in the last 2 hours:

```bash
curl -sS "https://www.enso.bot/api/public/v1/signals?type=wizard_in_progress&days=1&limit=200" \
  -H "x-api-key: $SIGNALS_API_KEY"
```

Enrich before you write to a prospect:

```bash
curl -sS https://www.enso.bot/api/public/v1/signals/people/dana%40acme.com \
  -H "x-api-key: $SIGNALS_API_KEY"
curl -sS https://www.enso.bot/api/public/v1/signals/accounts/acme.com \
  -H "x-api-key: $SIGNALS_API_KEY"
```

Everything one company did, last 30 days:

```bash
curl -sS "https://www.enso.bot/api/public/v1/signals?domain=acme.com&days=30&limit=500" \
  -H "x-api-key: $SIGNALS_API_KEY"
```

## 8. Checklist before you run unattended

- [ ] Key stored as a secret, never in a prompt or a repo
- [ ] Dedupe store keyed on signal `id`
- [ ] Cursor persisted from `next_since`, survives a restart
- [ ] Backoff on 5xx, hard stop on 401
- [ ] Consent rule checked before any address enters a sending list
- [ ] Human approval step in front of anything that sends
