# Synorb agent guide: keys without a browser, the 72 Hour Agent Demo, and Starter

This page is part of the Synorb agent guide at https://synorb.com/agent-guide.md.
Key provisioning for runtimes that have no browser or no header access, the one-tap email bind, the OTP fallback, plans and limits, and what happens at expiry.

## No browser, or an already-open MCP session with no header access?

`GET /connect` suits agents with a browser or plain HTTP GET. Some runtimes
cannot use it. They connect to `mcp.synorb.com` cold and cannot inject an
`Authorization` header into an already-open session. Or their sandbox blocks
scraping credentials out of an HTML page.

Two keyless alternatives mint a 72 Hour Agent Demo key like `/connect`. All
three share one rate limit and one reuse rule. Send back the `visitor_token`
to get the same key. Without it you get a new key. The rule, exactly as every
response states it:

```text
Key reuse rule: send back your `visitor_token` to get the same key. Send it as the `visitor_token` field or in the `X-Synorb-Visitor-Token` header. Without a valid token, you get a new key, a new org, and a new 15-Manifest allowance, even if another caller shares your network. New keys are capped at 3 per network per 24 hours. Treat the token like the key.
```

The two keyless options:

- **MCP tool `synorb-get-demo-key`** — call it with no credential at all, on
  Core or Advanced. Takes an optional `agent_name` (audit logging only).
- **`POST https://api.synorb.com/api/connect/demo-key`** — same payload shape
  as `/connect`, `{"agent_name": "..."}` optional body. It is also served at
  `https://synorb.com/api/connect/demo-key`.

Both return `{"api_key", "expires_at", "manifest_quota", ...}` as flat,
top-level fields. They also duplicate the key as a top-level `synorb_key` for
callers that standardized on that name. `expires_at` is 72 hours after issue,
and `manifest_quota` is 25.

The JSON response of `GET /connect` nests the key at `credentials.synorb_key`
instead. That object also carries connector URLs and MCP headers, so its shape
is a full object, not a bare key. `/connect` also mirrors `synorb_key` as a
flat top-level field for the same reason. So all three mint paths give you a
working top-level `synorb_key`. The primary, most-detailed shape of `/connect`
stays under `credentials`.

Prefer `Authorization: Bearer <api_key>` on every later call, on both REST and
MCP. Your MCP host may truly not be able to set headers on an already-open
Streamable HTTP session. In that case, pass the key as an `api_key` argument on
the tool call itself. Every authenticated MCP tool accepts it as a fallback when
no header is present.

This is the demo-tier-first fallback, not the recommended path. A tool argument
can end up in the transcripts or logs of your own runtime; a header usually
does not. Synorb never echoes the argument back in a tool result, an error, or
its own logs.

A call to an authenticated tool with no header and no `api_key` returns a 401
with a `WWW-Authenticate` header. Its `resource_metadata` field points at the
RFC 9728 document. That response names `synorb-get-demo-key` and
`POST /api/connect/demo-key` as the recovery path. `tools/list` itself still
needs no credential, so nothing blocks discovery. These keyless tools also
never need a credential: `synorb-get-demo-key`, `synorb-stream-search`,
`synorb-catalog`, `synorb-details`, and `synorb-configure-beacon`.

## 72 Hour Agent Demo and Starter account ladder

`GET https://synorb.com/connect` without an email creates an anonymous **72
Hour Agent Demo** Synorb Key. Read the top-level `expires_at` in the response
and show it to the user.

<!-- SYNORB:FACTS:demo-key:START -->
The 72 Hour Agent Demo key needs no email. It lasts 72 hours and can pull up to 15 Manifests. It includes 5 Beacons; 3 of them are starter Beacons.
<!-- SYNORB:FACTS:demo-key:END -->

After expiry, REST and MCP return an actionable authentication error. Mint a
new 72 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. **Recommended:**
use the one-tap link below. An agent should default to it, not to the OTP code
flow. The link needs nothing more from a human than a single tap.

### Recommended: one-tap confirmation link, no code to relay

Use this when you already built something for your user on a demo key, such as
a Beacon or a saved pull. Then you need only the user's email to keep it
running past the 72h expiry. Use it when you ask the user for an email in the
middle of a session. Add `"confirmation_method":"link"`:

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

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

Synorb emails the user ONE one-tap confirmation link. There is no code to relay
back through you. The user never sees or pastes a key.

The response is `{"status":"confirmation_link_sent", ...}`. You have nothing
more to poll or confirm. When the user taps the link, their browser calls
`GET /api/connect/bind-email/confirm?token=...` directly. Then the SAME Synorb
Key becomes Starter, and you keep using the credential you already have.

Tell the user something like: "I emailed you a one-tap confirmation link to
keep this running past today. No code, no key: tap the link."

Cognito may already have verified the supplied email, for example a returning
email. In that case, the request activates Starter immediately, with no link.
This matches the fast path of the OTP flow below.

**Over MCP:** call the `synorb-bind-email` tool instead of the REST call above.
It uses the same underlying flow, the same one-tap link, and the same
authenticated Synorb Key. Pass `email` (required) and, optionally, `name`.

`confirmation_method` defaults to `"link"`. That is the only value that a
pure-MCP integration can complete end-to-end. There is no MCP tool for the OTP
code. If you pass `"otp"`, you must confirm it yourself over REST at
`POST /api/connect/bind-email/verify`.

The tool call does not wait for anything more. Its response mirrors the REST
`confirmation_link_sent` shape (or the immediate-activation shape). The actual
tap still happens in the user's browser at
`GET /api/connect/bind-email/confirm?token=...`. That step runs only over REST,
because it is a human who clicks a link, not a tool call.

### Fallback: OTP code

Use this only when a one-tap link is not an option for your integration. One
example is an environment that cannot deliver clickable links. Another example
is a human who prefers to type a code back. This flow still requires the human
to receive a code and give it back to you (or type it somewhere). The link flow
above avoids that step completely:

```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"}
```

**Email restrictions and collisions.** Check two things before or after you
call `bind-email` (either flow above):

- **No plus-addressed emails.** Synorb rejects `name+tag@example.com`-style
  addresses immediately with `HTTP 422` and `error_code: "plus_addressed_email_not_supported"`.

  Synorb creates no `verification_required` state, so nothing waits on a code
  or link that never arrives. Use the base address (`name@example.com`)
  instead.
- **Email already bound to a different account.** The email can already belong
  to a DIFFERENT Synorb account.

  Then the response is `HTTP 409` with `error_code: "email_already_bound"`. For
  a Cognito-identity collision, the code is `"email_already_linked"`. The
  response also has a `recovery` field and `action_url: "https://synorb.com/signin?mode=reset-request"`.

  This is not a dead end. If that account belongs to your user, tell them to
  sign in at that URL (or use "Forgot password?") to recover it directly. This
  request never touches or merges their existing account. If the account is not
  theirs, retry with a different email address.

Only then does the same key become **Starter**, with no MCP or REST
reconfiguration. The verification response confirms
`account.key_expires_at: null`: the Starter Synorb Key never expires. Its
`password_setup.required` and legacy `password_setup_required` fields are
`false`. A password is optional. You need one only for human dashboard/Studio
sign-in. API and MCP keep using the Synorb Key alone.

### Plans and limits

<!-- SYNORB:FACTS:starter:START -->
Starter is free forever and needs an email. It gives 100 Manifests per usage period and 10 Beacons.
<!-- SYNORB:FACTS:starter:END -->

<!-- SYNORB:FACTS:paid-plans:START -->
The paid plans are Individual ($10 per month, 1,000 Manifests), Professional ($100 per month, 10,000 Manifests), and Startup ($1,000 per month, 100,000 Manifests).
<!-- SYNORB:FACTS:paid-plans:END -->

<!-- SYNORB:FACTS:plans-connect:START -->
| Plan | Price | Manifests per usage period | Beacons | Delivery | Access |
|------|-------|----------------------------|---------|----------|--------|
| Starter | Free | 100 | 10 | Daily Batch | MCP + REST API |
| Individual | $10 per month or $96 per year | 1,000 | 25 | Daily Batch | MCP + REST API |
| Professional | $100 per month or $960 per year | 10,000 | 50 | Continuous Delivery | MCP + REST API, webhooks |
| Startup | $1,000 per month or $9,600 per year | 100,000 | 100 | Continuous Delivery | MCP + REST API, webhooks, S3 export, Digest Builder, Records |
| Enterprise | Contact sales | Set by contract | No limit | Continuous Delivery | MCP + REST API, webhooks, S3 export, Digest Builder, Sync Ontology, WebSocket, managed services, Records |
<!-- SYNORB:FACTS:plans-connect:END -->

<!-- SYNORB:FACTS:usage-period:START -->
Each self-serve plan has a hard limit and no overage. A usage period starts on the day you signed up, not on the first of the month. Enterprise contracts set the Enterprise limits.
<!-- SYNORB:FACTS:usage-period:END -->

<!-- SYNORB:FACTS:beacon-limits:START -->
Every plan can save Beacons, up to a limit: 72 Hour Agent Demo 5, Starter 10, Individual 25, Professional 50, Startup 100. Enterprise has no limit. At the limit, a save returns `beacon_limit_exceeded`. Archive a Beacon or change plan.
<!-- SYNORB:FACTS:beacon-limits:END -->

See https://synorb.com/pricing.

### What happens to Beacons and data at bind or at expiry

Binding an email does not rotate anything. It only clears `expires_at` and
updates the contact email on the existing key. The same `key_id`, `org_id`, and
Bearer token stay live (`synorb_key_unchanged: true` in the verify response).
Any Beacons, saved data, and usage history that you created under the demo key
belong to that `org_id`. They carry over automatically. You have nothing to
re-create or migrate after a successful bind.

If the key never gets a bound email and expires, the opposite happens. Synorb
does not delete or purge the org, but no credential can reach it again.
Authentication checks `expires_at` and returns `401` once it has passed.
`/connect` (and its keyless equivalents) always mint a *new* org on the next
call. They never resurrect an expired one.

So a Beacon on an unbound, expired demo key becomes permanently unreachable
through any credential. This is true even though nothing actively destroyed
it. Bind before `expires_at` if the work needs to survive past 72 hours. No
separate export or recovery step is available afterward.

### Usage status and exhaustion responses

Only REST routes that explicitly attach usage headers return them. Usage
headers are not guaranteed on every authenticated route. They are also not
guaranteed on errors that return before usage context is available (including
some Beacon, plan-preview, and 429 responses). When a response includes
`X-Synorb-Usage-*` headers, they mirror the usage snapshot of that response.
Read the `usage` or allowance envelope that the endpoint returns for the actual
balance and `manifests_billed_this_call`.

If the response has no usage envelope and no headers, do not infer a charge
from the HTTP status alone. For compatibility, the older `X-Synorb-Quota-*`
headers and `quota_*` fields remain available. They describe the same customer
usage. Do not treat them as a second counter.

For an anonymous demo key, the reset boundary is the persisted key expiry.
When the demo key uses all of its allowance, the `429` detail includes the real
`key_expires_at`, `upgrade_url`, and a `starter_bind` action. That action keeps
the same key on Starter. Recommend email binding first; present paid plans
after that.

Near-expiry and low-usage responses can include a usage notice with the same
guidance, in the headers or in the envelope. A repeated delivery of a Manifest
that Synorb already billed can still appear in delivery telemetry. It does not
consume more customer usage in that usage period.

Do not silently reset or extend an already-issued legacy demo key. Preserve
its recorded expiry and billed-identity history. Only a verified email bind
converts an active key to Starter.
