Agent signup — get an account without a human in the loop
Autonomous agents can create a Satsignal account programmatically: redeem an invite code from an existing workspace owner, or solve a proof-of-work challenge (hashcash) — no CAPTCHA, no browser form. Signup ends with a magic-link email, so the agent needs a readable email inbox; API keys are then minted in the dashboard. This guide is the whole flow, written for an agent reading it cold.
Companion docs: Agents implementer spec · API reference · Agent runtime guide · Production checklist
1. Start with discovery — always
curl -s https://app.satsignal.cloud/api/v1/signup
{
"agent_signup": {
"enabled": true,
"proof_types": ["invite", "pow"],
"challenge_url": "/api/v1/signup/challenge",
"pow": {
"algorithm": "sha256-leading-zero-bits",
"difficulty_bits": 22
}
}
}
Treat this document — not this guide — as the source of truth for which lanes the host currently accepts and what the proof-of-work difficulty is. Every knob is operator-configurable and read at call time: a host may disable the whole lane (enabled: false), turn individual proof types on or off, or change difficulty_bits. Never hardcode the difficulty or the lane list. The endpoint is unauthenticated and always returns 200, even when signup is off.
Additional proof types may appear in proof_types over time; the discovery document advertises exactly what this host accepts today.
2. The shape of every signup
POST https://app.satsignal.cloud/api/v1/signup
Content-Type: application/json
{
"email": "agent-inbox@example.com",
"proof": { "type": "<one of proof_types>", ... }
}
No API key — this is deliberately the pre-key endpoint (an agent signing up has no key yet). The proof object replaces the CAPTCHA a browser signup would face; everything else about account creation is identical to the human path:
- Success is always the same body —
{"ok": true, "message": ...}— whether or not the email was actually sent (anti-enumeration; a suppressed address gets no different answer). - A single-use magic link, valid 15 minutes, is emailed to the address. The account is created when the link is first used — not before. The agent (or its operator) must be able to read that inbox and GET the link.
- Every proof failure — unknown invite, expired challenge, insufficient work, replay — returns the same generic
403 invalid_proof. The error deliberately never says which check failed.
3. Lane A — invite code
curl -s -X POST https://app.satsignal.cloud/api/v1/signup \
-H "Content-Type: application/json" \
-d '{"email":"agent-inbox@example.com",
"proof":{"type":"invite","token":"<invite token>"}}'
Invite tokens are minted by an existing workspace owner in their dashboard at /w/<workspace>/signup-invites — the operator-referral lane. A token can be single-use or multi-use (up to 100 uses), with an optional expiry; revocation is immediate. If a partner or platform gave your agent an invite token, this is the lane to use — no proof-of-work needed.
4. Lane B — proof of work (hashcash)
Fully autonomous: no human mints anything. Three steps.
4.1 Fetch a challenge (GET or POST both work; issuance is stateless):
curl -s https://app.satsignal.cloud/api/v1/signup/challenge
{
"challenge": "spw1.<expires_unix>.<rand_hex>.<mac_hex>",
"algorithm": "sha256-leading-zero-bits",
"difficulty_bits": 22,
"expires_at": 1781000000
}
The challenge is valid for 10 minutes and single-use — a solved challenge cannot be replayed for a second signup.
4.2 Solve it. Find a nonce (printable ASCII, ≤ 64 chars) such that sha256(challenge + "." + nonce) has at least difficulty_bits leading zero bits. Read the bits from the response — do not assume a fixed value. At the current default (22 bits ≈ 4M hashes) this is seconds of CPU:
import hashlib, itertools
def solve(challenge: str, bits: int) -> str:
for i in itertools.count():
nonce = str(i)
d = hashlib.sha256(f"{challenge}.{nonce}".encode()).digest()
n = 0
for b in d:
if b == 0:
n += 8
continue
n += 8 - b.bit_length()
break
if n >= bits:
return nonce
4.3 Submit:
curl -s -X POST https://app.satsignal.cloud/api/v1/signup \
-H "Content-Type: application/json" \
-d '{"email":"agent-inbox@example.com",
"proof":{"type":"pow",
"challenge":"spw1.<...>",
"nonce":"<solved nonce>"}}'
Then read the inbox and GET the magic link.
5. Rate limits
| surface | default budget |
|---|---|
POST /api/v1/signup | 10 / hour / IP |
GET /api/v1/signup/challenge | 30 / hour / IP |
| magic-link emails per address | shared with the /login budget |
429 responses carry Retry-After. The per-address email budget is shared with the human login form, so hammering both doors does not double it.
6. What you get, honestly
- The account lands on the free plan: 100 anchors per calendar month, one workspace, every proof type. Server-side
.mbntcopies are kept until you delete the proof — on every plan — unless a sealed anchor sets an explicitretain_dayswindow (then the response'sretain_untilis the enforced window's end; on standard anchors the numericretain_untilis bookkeeping, not an expiry) — still, download bundles at anchor time. - An inbox is still required. The magic link is the account bootstrap; an agent-readable mailbox (e.g. an agent-mail provider) closes the loop without a human.
- Key minting: one interactive bootstrap, then fully programmatic. The first key is minted in the dashboard at
/w/<workspace>/keysafter the magic-link signin (scopesanchors:create,proofs:read). Platforms provisioning proof on behalf of their own tenants mint akeys:adminkey once in that dashboard, then callPOST /api/v1/keysto issue per-tenant scoped sub-keys headlessly — no browser, no magic link per tenant. Each sub-key is bounded to a subset of the parent's scopes and (optionally) specific folders, is rate-limited, and carries full mint lineage;GET/DELETE /api/v1/keyslist and revoke them.keys:adminitself stays dashboard-grantable only — an API-minted key can never carry it. Once a key exists, every integration step is a plain Bearer HTTP call; see the agents implementer spec and the OpenAPI spec. This programmatic-mint lane is operator-enabled per host and defaults off — on a host where it is not enabled,POST/GET/DELETE /api/v1/keysreturn a uniform404. Before designing around it, confirm it is enabled (or request it) for your host by emailing hello@satsignal.cloud; theGET /api/v1/signupdiscovery document reports whether this host offers it (key_mint_api.enabled). - Self-serve billing does not exist yet. Higher volume is arranged by email: hello@satsignal.cloud (see pricing).
7. Where this fits
This guide covers account bootstrap only. The anchor-side integration — folders, anchoring, the four-part agent-session pattern, verification — starts at the agent runtime guide and the docs index. Verification never needs any of this: /verify and the .mbnt bundle are account-free forever.
Questions about this specification? Email hello@satsignal.cloud.