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:

  1. Create an account — one anonymous HTTP call returns a token and a claim link.
  2. Persist your MCP tools — use the returned token to configure your MCP client.
  3. 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 to ownerEmail; share it with them too, in case the email is delayed.

The token field only appears for anonymous callers. If you already have a token and call this tool to provision another org, the response omits token/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:

  1. Open the claimUrl (or the link in their inbox — the email lands within a minute).
  2. Set a password.
  3. Add billing details if their plan requires it.
  4. 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:

  1. register_recovery_key with your org id, a label, and your base64/base64url SPKI public key → returns a 32-byte challenge.
  2. Sign the raw challenge bytes with the matching private key.
  3. verify_recovery_key with the keyId + 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 /mcpcreate_account_and_claim_link token, setupHint, claimUrl
Create your own org (AI-only) POST /mcpcreate_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 /mcpping (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.)