# Synorb agent guide: succeed on the first session

Synorb has one credential: the **Synorb Key**. Use the same value on both
surfaces. Keep it in the host's secret store; never put it in browser code,
logs, prompts, or source control.

```http
Authorization: Bearer YOUR_SYNORB_KEY
```

Use MCP for an interactive agent loop. Use the REST API for deterministic,
stateless work such as a backend job, a scheduled poll, or a product request.
Both read the same Streams and return the same provenance-bearing Manifests.

## Pick your path

Every agent uses Synorb the same way: one key, Manifests arrive, you pay per
item delivered. What differs is where you start.

**Personal agents** (assistants running one person's life or work - Instinct,
Muse, Town): start with a Beacon. Do not pull on demand - save a scoped watch
and receive Manifests as they publish. First call: `synorb-save-beacon`.

**Coding agents** (builders that scaffold software - Replit, Lovable, Cursor,
Claude Code): you are building a product on Synorb, not consuming it yourself.
Start at https://synorb.com/agents/coding-agents. The single-shot prompts walk
you through key handling, density checks, and the scope receipt.

**Voice agents** (real-time conversation - Vapi, Retell, ElevenLabs): latency is
your constraint. One Manifest pull is a complete answer; never enter plan mode
mid-call. Use SSE, keep scopes narrow.

**Cowork agents** (desktop knowledge-work agents - Claude Cowork, Perplexity
Computer, Operator): stay ambient. Let Beacons feed you fresh context while the
human works, and cite the source URLs in whatever you draft.

**Vertical agents** (domain agents - sales, finance, legal, compliance): your
buyers audit provenance. Every Manifest carries its source URL, billing is per
item delivered, and error states (rate limits, entitlement, expiry) are explicit
so you can handle them in code.

## 24 Hour Agent Demo and Starter account ladder

`GET https://synorb.com/connect` without an email creates an anonymous **24
Hour Agent Demo** Synorb Key: no email is required, it can retrieve up to
1,000 Manifests for 24 hours, and the key expires. Read the top-level
`expires_at` in the response and surface it to the user. After expiry, REST
and MCP return an actionable authentication error: mint a new 24 Hour Agent
Demo key at `/connect` or sign up free at https://synorb.com/signup.

To keep the existing key alive, bind an email before expiry:

```http
POST /api/connect/bind-email
Authorization: Bearer YOUR_SYNORB_KEY
Content-Type: application/json

{"email":"owner@example.com","name":"Optional name"}
```

This starts Cognito email verification. Confirm the emailed OTP with the same
Bearer key:

```http
POST /api/connect/bind-email/verify
Authorization: Bearer YOUR_SYNORB_KEY
Content-Type: application/json

{"email":"owner@example.com","code":"123456"}
```

Only then does the same key become **Starter** — email required, free forever,
and 1,000 Manifests per month — with no MCP or REST reconfiguration. Paid Streams
plans are Individual ($10/month, 10,000 Manifests/month), Professional
($100/month, 100,000 Manifests/month), and Startup ($1,000/month, 1,000,000
Manifests/month). See https://synorb.com/pricing.

## Non-negotiable rules

1. Resolve tag names before filtering. `Payments` is not necessarily a
   canonical tag; `Payment Systems` may be. Call `GET /ontology/tags` (or
   `synorb-tags`) first, then use the returned tag name or ID. Unknown tags
   return an empty result, not an error.
2. Scope with `stream_ids`. They are the reliable, explicit scope. Do not send
   an empty `stream_ids` array or omit all scope fields when you intended a
   narrow query: an empty scope can run against the global visible catalog.
   `required_surfaces` is not a reliable execution scope; do not use it as a
   substitute for resolved `stream_ids`.
3. For a REST pull, plan first, then replay the request byte-for-byte as JSON
   values with the returned `plan_token`. The execute body may contain the plan
   request fields plus `plan_token`, and nothing else. Adding `target_count`,
   a filter, or an innocuous-looking field invalidates the token. Re-plan with
   the new full request; re-planning is zero-quota.
4. Start compactly. Use count/orient for preflight, then request only the
   on-topic Manifests needed to answer. Cite each source URL returned with a
   factual claim.

## Bootstrap

```bash
curl -s https://synorb.com/connect
curl -s -H "Authorization: Bearer $SYNORB_KEY" https://api.synorb.com/account
curl -s -H "Authorization: Bearer $SYNORB_KEY" \
  'https://api.synorb.com/ontology/tags?search=Payment%20Systems&page_size=10'
```

Catalog pagination is always inside `data.pagination`, not at the response
top level. Read `data.pagination.next` and continue until it is `null` when a
complete catalog is required.

```json
{
  "data": {
    "entities": [{"id": "tag_123", "name": "Payment Systems", "tag_type": "topic"}],
    "pagination": {"page_num": 0, "page_size": 10, "next": null, "prev": null}
  },
  "usage": {"items_in_response": 0}
}
```

## Actionable authentication and provisioning errors

Every error below is a top-level JSON object. Agents should route on
`error_code` and use the `action_url`; do not parse the human-readable prose.
The shared envelope is `error`, `error_code`, `action`, and `action_url`, with
state-specific recovery fields.

### Demo-key provisioning rate limit — HTTP 429

The anonymous `/connect` endpoint allows 10 demo keys per IP per hour. This is
an integration ramp, not a Manifest quota error. Stop minting keys and bind an
existing key; the same key remains valid and becomes Starter (free forever,
1,000 Manifests/month). A bound key is identity-authenticated and is not held
to the anonymous IP provisioning limit.

```json
{
  "error": "Rate limit exceeded. Max 10 demo keys per IP per hour.",
  "error_code": "demo_key_rate_limited",
  "action": "bind_email",
  "action_url": "https://api.synorb.com/api/connect/bind-email",
  "upgrade": "Bind an email to upgrade to Starter (free forever, 1,000 Manifests/month) - your same key keeps working: POST /api/connect/bind-email",
  "bind_email": "https://api.synorb.com/api/connect/bind-email",
  "signup": "https://synorb.com/signup",
  "retry_after": "1h",
  "retry_after_seconds": 3600,
  "same_key": true,
  "limit_scope": "ip_per_hour"
}
```

POST the bind action with the current key:

```bash
curl -sS -X POST https://api.synorb.com/api/connect/bind-email \
  -H "Authorization: Bearer $SYNORB_KEY" \
  -H 'content-type: application/json' \
  -d '{"email":"you@example.com","name":"Optional name"}'
```

### Suspicious activity — HTTP 429

This is anomaly detection, not a quota or pricing tier. Do not retry-loop or
try to route around it with more anonymous keys. Legitimate builders can use
the identity escape hatch below; otherwise contact `team@synorb.com`.

```json
{
  "error": "Suspicious activity detected.",
  "error_code": "suspicious_activity",
  "action": "bind_email",
  "action_url": "https://api.synorb.com/api/connect/bind-email",
  "verify": "If you are integrating legitimately, bind an email to verify your key: POST /api/connect/bind-email. Otherwise contact team@synorb.com.",
  "bind_email": "https://api.synorb.com/api/connect/bind-email",
  "contact_email": "team@synorb.com"
}
```

### Expired Synorb Key — HTTP 401

An expired key is distinct from an unknown or malformed key. Mint a fresh
24 Hour Agent Demo key or sign up; do not retry the expired credential.

```json
{
  "error": "Synorb Key expired",
  "error_code": "synorb_key_expired",
  "action": "mint_new_demo_key",
  "action_url": "https://synorb.com/connect",
  "recovery": "Mint a new 24 Hour Agent Demo key at https://synorb.com/connect or sign up free at https://synorb.com/signup",
  "connect": "https://synorb.com/connect",
  "signup": "https://synorb.com/signup"
}
```

## Canonical REST plan/execute contract

`POST /manifests/plan` validates and fingerprints the request without pulling
Manifests or consuming quota. `POST /manifests/exec` validates the exact
replay and then executes the normal Manifest query.

```bash
# Plan. Preserve this JSON object exactly for execute.
curl -sS -X POST https://api.synorb.com/manifests/plan \
  -H "Authorization: Bearer $SYNORB_KEY" -H 'content-type: application/json' \
  -d '{"stream_ids":[123],"tag_ids":["tag_123"],"days":1,"mode":"default","target_count":5}'
```

```json
{
  "plan_token": "synorb_plan_...",
  "request_fingerprint": "sha256:...",
  "quota_charged": false,
  "execute": "POST /manifests/exec with the identical request plus plan_token"
}
```

```bash
# Valid: the original request plus plan_token, with no changed or added fields.
curl -sS -X POST https://api.synorb.com/manifests/exec \
  -H "Authorization: Bearer $SYNORB_KEY" -H 'content-type: application/json' \
  -d '{"stream_ids":[123],"tag_ids":["tag_123"],"days":1,"mode":"default","target_count":5,"plan_token":"synorb_plan_..."}'
```

An invalid token is a zero-quota `409` with `error="invalid_plan_token"`.
Do not retry execute with modified fields; make a fresh plan instead.

## Billing rules

| Operation | Manifest quota |
| --- | --- |
| `mode=count`, `mode=orient`, plan, stream/catalog/details/tag pulls | Zero |
| Beacon create, list, archive, restore, delete | Zero |
| A topic-filtered execute | Only returned, on-topic Manifests |
| Duplicate Manifest returned by a repeated query | Not billed again within the dedup window; inspect the usage receipt |
| Extraction failure or clean empty result | Zero unless a Manifest was actually returned |
| Invalid plan token, invalid scope, unknown tag | Zero |

Always inspect the response `usage` block and `quota.manifests_billed_this_call`.
`target_count` is a ceiling, not a promise: Synorb does not pad a response with
unrelated Manifests merely to fill it.

## Worked recipes

The MCP equivalent uses the same sequence: catalog/details/tag resolution then
`synorb-manifests`. The REST pairs below are deliberately copy-pasteable.

### 1. Morning digest across surfaces

Goal: give a compact source-cited morning update from selected surfaces.

1. `GET /ontology/tags?search=Payment%20Systems`
2. `POST /manifests/plan` with resolved `stream_ids`, tag ID, and `days=1`
3. `POST /manifests/exec` with the exact planned request plus `plan_token`

```json
// plan request
{"stream_ids":[123,456],"tag_ids":["tag_123"],"days":1,"mode":"default","target_count":8}
// execute response (abridged)
{"data":{"manifests":[{"id":"m_1","brief":{"headline":"..."},"citations":[{"canonical_url":"https://source.example/item"}]}]},"quota":{"manifests_billed_this_call":1}}
```

Cost: tag lookup and plan are zero; the execute bills only returned on-topic
Manifests. Failure modes: unknown tag gives empty; absent scope can broaden to
global; a changed execute payload gives `invalid_plan_token` and must be
re-planned.

### 2. Watch this for me with a Beacon

Goal: save a reusable watch, then pull it with an explicit time window.

Resolve the canonical topic first. `natural_language_intent` is descriptive
metadata only; it is not executable. Translate it into `stream_ids` and
`topic_filters` before creating the Beacon.

```bash
curl -sS -X POST https://api.synorb.com/v3/beacons \
  -H "Authorization: Bearer $SYNORB_KEY" -H 'content-type: application/json' \
  -d '{
    "name":"Payment systems watch",
    "natural_language_intent":"Watch payment infrastructure changes",
    "stream_ids":[123,456],
    "topic_filters":[{"tag_id":"tag_123","tag_name":"Payment Systems","tag_type":"topic"}],
    "default_date_window":{"lookback_hours":24}
  }'
```

```json
{"beacon_id":"synorb_bcn_...","name":"Payment systems watch","status":"draft","topic_filters":[{"tag_id":"tag_123","tag_name":"Payment Systems","tag_type":"topic"}]}
```

`topic_filters` is an array. Each entry is either a canonical tag object
(`tag_id`, `tag_name`, `tag_type`) as above or a previously resolved canonical
tag name. Do not save a guessed display string.

Poll using the path that executes Beacons:

```bash
curl -sS -H "Authorization: Bearer $SYNORB_KEY" \
  'https://api.synorb.com/streams/123/manifests?beacon_id=synorb_bcn_...&lookback_hours=24'
```

Do **not** use `POST /manifests/query` to run a Beacon. The executable path is
`GET /streams/{id}/manifests?beacon_id=...` with an explicit window.

Lifecycle (all zero-quota):

```bash
curl -X POST -H "Authorization: Bearer $SYNORB_KEY" https://api.synorb.com/v3/beacons/synorb_bcn_.../archive
curl -X POST -H "Authorization: Bearer $SYNORB_KEY" https://api.synorb.com/v3/beacons/synorb_bcn_.../restore
curl -X DELETE -H "Authorization: Bearer $SYNORB_KEY" https://api.synorb.com/v3/beacons/synorb_bcn_...
```

Cost: CRUD is zero; a poll bills only returned on-topic Manifests. Failures:
archived Beacon returns `beacon_archived`; no explicit lookback/date returns
`date_window_required`; a stream not saved in the Beacon is rejected unless an
explicit override is supplied: use `override_streams`,
`override_source_channels`, or `override_topics` for that dimension, or
`broaden_sources=true` to replace the saved scope deliberately. Watch when the
user needs repeated updates; pull directly for a one-off question.

### 3. Deep research across surfaces

Goal: build an evidence set across several chosen surfaces without accidental
global retrieval.

1. `synorb-catalog(query="...")` or REST stream catalog; select candidates.
2. `synorb-details(stream_ids=[...])`; retain the exact IDs.
3. Resolve each tag through `/ontology/tags`.
4. Plan and execute using the chosen `stream_ids`, tag IDs, date range, and
   `target_count`.

```json
{"stream_ids":[123,456,789],"tag_ids":["tag_123","tag_456"],"tag_logic":"and","published_date_from":"2026-09-01","published_date_to":"2026-09-06","mode":"default","target_count":20}
```

Expected execute response: `data.manifests[]`, `data.pagination`, source
citations, and a quota receipt. Cost: catalog/details/tag lookups and plan are
zero; returned matches bill once after dedup. Failure modes: an `and` query can
legitimately be empty; split into separate scoped research lanes rather than
silently dropping a filter.

### 4. Due diligence on one organization cross-surface

Goal: compare the organization's owned and external coverage.

```json
{"stream_ids":[123,456],"tag_ids":["org_tag_123"],"published_date_from":"2026-08-07","published_date_to":"2026-09-06","mode":"default","target_count":15}
```

Expected response: only Manifests matching the resolved organization tag from
the stated Streams, each with its source provenance. Cost: matched-only.
Failure modes: never substitute a similarly named organization; resolve a
returned ambiguity first. If no selected Stream contains the tag, report an
empty scoped result rather than remove scope or fall back to global search.

### 5. Meeting prep on one company

Goal: get the few most useful, recent, citable updates.

```json
{"stream_ids":[123],"tag_ids":["org_tag_123"],"days":7,"significance":"high","mode":"default","target_count":5}
```

Expected response: up to five on-topic Manifests with sources for a briefing.
Cost: matched-only, deduplicated. Failure modes: `orient` is only a sample and
cannot support a complete briefing; use default mode for the final five.

## Failure contracts

| Condition | Contract | Agent action |
| --- | --- | --- |
| Empty/missing scope | Can become a global visible-catalog query | Stop and resolve/use `stream_ids` |
| Unknown tag | Empty result, zero quota | Resolve through `/ontology/tags` |
| Invalid/changed plan token | `409 invalid_plan_token`, zero quota | Re-plan the complete changed request |
| `planning_failed` | No execute, zero quota | Fix the typed scope/tag/date error and re-plan |
| `beacon_archived` | `409`, zero quota | Restore explicitly, then poll |
| `date_window_required` | `422`, zero quota | Add `lookback_hours` or a start/end date |
| Catalog page | Items in `data.*`; cursor in `data.pagination` | Follow `next` until `null` |

## Machine-readable entry points

- `https://synorb.com/llms.txt` — short bootstrap index
- `https://synorb.com/llms-full.txt` — extended API/MCP reference
- `https://synorb.com/agent-guide.md` — this operational guide
- `https://synorb.com/openapi.json` — REST schema
- `https://synorb.com/connect` — key provisioning plus MCP instructions
