Bootstrapping an account
This guide is for an AI agent that has no Tandem tools configured yet. You can create a Tandem organization from scratch over plain HTTP, get back a token, and wire up your own MCP tools — no human pre-setup required. Hand an agent this page and it can onboard itself.
The flow is:
- Create an account — one anonymous HTTP call returns a token and a claim link.
- Persist your MCP tools — use the returned token to configure your MCP client.
- The human claims the org — they click the link, set a password, and (if needed) add billing.
You can start using Tandem tools the moment step 1 returns. Steps 2 and 3 can happen in any order afterward.
| What | Value |
|---|---|
| MCP endpoint | https://portal.launchtandem.com/mcp |
| Transport | Streamable HTTP (JSON-RPC over HTTP POST) |
| Token format | tdm_ + 48 hex characters |
Step 1 — Create the account
The MCP server exposes exactly one tool that works without authentication: create_account_and_claim_link. Call it anonymously and it creates an organization, a human owner, emails that owner a claim link, and — because you called it anonymously — issues you an agent token bound to the new org.
You don’t need an MCP client for this. A single curl to POST /mcp works:
curl -s https://portal.launchtandem.com/mcp \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "create_account_and_claim_link",
"arguments": {
"ownerEmail": "you@example.com",
"ownerDisplayName": "Your Name",
"organizationName": "Acme Inc."
}
}
}'
| Argument | Required | Notes |
|---|---|---|
ownerEmail |
yes | The human who will own the org. They receive the claim email. |
organizationName |
yes | Display name for the new organization. |
ownerDisplayName |
no | Human-friendly name for the owner. |
The result arrives as a JSON-RPC envelope whose result.content[0].text is a JSON string. Parsed, it looks like:
{
"organization": { "id": "…", "slug": "acme-inc", "name": "Acme Inc." },
"ownerUserId": "…",
"claimUrl": "https://portal.launchtandem.com/auth/setup?token=…",
"emailSent": true,
"token": "tdm_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"setupHint": "claude mcp remove tandem && claude mcp add --scope user --transport http tandem https://portal.launchtandem.com/mcp --header \"Authorization: Bearer tdm_…\"",
"tokenNotice": "This token is sensitive. …"
}
What you got back:
token— your Tandem agent token. It has admin scope on the new org (read,manage,deploy,logs,restart,rollback). Treat it like a password.setupHint— a ready-to-run command that persists the token into your MCP client (see step 2).claimUrl— the link the human owner uses to take ownership. The same link was emailed toownerEmail; share it with them too, in case the email is delayed.
The
tokenfield only appears for anonymous callers. If you already have a token and call this tool to provision another org, the response omitstoken/setupHint— you keep using your existing credential.
Keep the token secret. Don’t echo it back to the user, write it to a world-readable file, or commit it to a repo. If it leaks, the human can revoke it from Settings → Tokens.
If your client manages MCP sessions
When you call POST /mcp through a real MCP client (rather than raw curl), the server returns an Mcp-Session-Id header. Echo it back on subsequent requests and you stay authenticated for that session without resending the token — handy for finishing onboarding before you’ve persisted config. Sessions expire after about an hour, so still do step 2 to make auth durable.
Step 2 — Persist your MCP tools
The token from step 1 is all your MCP client needs. The setupHint is the exact command for Claude Code:
claude mcp remove tandem && claude mcp add --scope user --transport http tandem \
https://portal.launchtandem.com/mcp \
--header "Authorization: Bearer tdm_xxxxxxxx"
Restart your client afterward so it picks up the new server.
Using a different MCP client (Claude desktop/web, ChatGPT/OpenAI, Codex, Cursor, or any other)? The endpoint, transport, and bearer-header contract are identical — see Connecting agents for copy-paste config for each one.
Once configured, the full Tandem toolset (deploy services, read logs, restart, manage the org) is available on every future session — no need to re-bootstrap.
Step 3 — The human claims the organization
Account creation is intentionally non-binding until a real person confirms it: nothing is charged and no identity is finalized until the owner clicks the claim link and sets a password.
Tell the owner to:
- Open the
claimUrl(or the link in their inbox — the email lands within a minute). - Set a password.
- Add billing details if their plan requires it.
- Land on the portal dashboard, where they can review the agent token you’re using and create or revoke others under Settings → Tokens.
The claim link is valid for 14 days. Until it’s claimed, your agent token still works for building things — the human just hasn’t taken ownership yet.
Alternative: pure-HTTP account creation (no token)
If you only need to create an account for a human and don’t need an agent token yourself, there’s a REST endpoint that does the same provisioning without MCP:
curl -s -X POST https://portal.launchtandem.com/api/auth/bootstrap-account \
-H "Content-Type: application/json" \
-d '{
"ownerEmail": "you@example.com",
"ownerDisplayName": "Your Name",
"organizationName": "Acme Inc."
}'
{
"organization": { "id": "…", "slug": "acme-inc", "name": "Acme Inc." },
"ownerUserId": "…",
"claimUrl": "https://portal.launchtandem.com/auth/setup?token=…",
"emailSent": true
}
This returns the claim URL but no token — there’s no agent to authenticate. The human claims the org, then mints an agent token from Settings → Tokens. Use the MCP path in step 1 instead if you want to be issued a working token immediately.
Alternative: an org you own, with no human (AI-only)
If you want your own workspace with no human owner at all, call create_own_organization instead of create_account_and_claim_link. It also works without auth, mints a fresh owner agent token bound to the session, and returns a setupHint — but sends no claim email and creates no human owner. You are the owner.
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call",
"params": { "name": "create_own_organization",
"arguments": { "organizationName": "Acme Bot Co." } } }
Because there is no human to satisfy the deploy gate’s recovery requirement via the claim-email roundtrip, an AI-only org must register an Ed25519 recovery key before it can deploy. The response’s nextSteps spells this out:
register_recovery_keywith your org id, alabel, and your base64/base64url SPKI public key → returns a 32-byte challenge.- Sign the raw challenge bytes with the matching private key.
verify_recovery_keywith thekeyId+ signature (within 10 minutes) to activate it.
After that, add billing (set_payment_method relays a hosted setupUrl) and check get_deploy_gate_status before deploying. See Capabilities overview for the deploy gate and Billing for funding.
Rate limits
Account creation is rate-limited to stop welcome-email spam. Both bootstrap paths share the same limits:
- 10 requests/hour per IP
- 1 request/day per email address
Exceeding either returns 429 with a Retry-After header (REST) or a rate_limited error with retryAfterSeconds (MCP). Pick a real owner email — you can’t retry the same address more than once a day.
Quick reference
| Step | Call | Returns |
|---|---|---|
| Create account (agent) | POST /mcp → create_account_and_claim_link |
token, setupHint, claimUrl |
| Create your own org (AI-only) | POST /mcp → create_own_organization |
token, setupHint, nextSteps (recovery key) |
| Create account (no token) | POST /api/auth/bootstrap-account |
claimUrl |
| Persist tools | setupHint command (Claude Code) or Connecting agents |
— |
| Verify endpoint | POST /mcp → ping (no auth) |
{"jsonrpc":"2.0","id":1,"result":{}} |
# Sanity check — JSON-RPC ping answers without auth
curl -s https://portal.launchtandem.com/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"ping"}'
# {"jsonrpc":"2.0","id":1,"result":{}}
(GET /mcp returns 405 Method Not Allowed — the server has no server-initiated SSE stream; all MCP traffic goes over POST.)