> ## Documentation Index
> Fetch the complete documentation index at: https://docs.swarmd.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Setup

> Register an agent, collect its credentials, install the SDK, and confirm the connection works.

# Setup

Everything on this page is shared by all three tracks. Do it once.

By the end you'll have an agent identity, three secrets in a `.env` file, a
short script that proves those credentials talk to the platform, and the one
call that turns the identity into an agent Swarmd will actually route to.

<Note>
  Already have `SWARMD_AGENT_ID` and `SWARMD_CLIENT_SECRET` from someone on your
  team? Skip to [Step 4](#step-4-install-the-sdk).
</Note>

***

## Step 1: Get a tenant

A **tenant** is your organisation on Swarmd. Everything — agents, users,
policies, subscriptions — lives inside one.

If your organisation is already on Swarmd, ask an admin to add you. Otherwise
create one:

```bash theme={null}
curl -X POST https://api.dev.swarmd.ai/tenant-auth/v1/tenants \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Corp",
    "user": {
      "email": "you@acme.com",
      "password": "your-secure-password",
      "firstName": "Jane",
      "lastName": "Smith"
    }
  }'
```

Verify the email that arrives, then log in to get a bearer token:

```bash theme={null}
curl -X POST https://api.dev.swarmd.ai/tenant-auth/v1/login \
  -H "Content-Type: application/json" \
  -d '{ "email": "you@acme.com", "password": "your-secure-password" }'
```

```bash theme={null}
export SWARMD_TOKEN="eyJhbG..."   # the accessToken from the response
```

<Tip>
  This page covers only what the SDK needs. The surrounding tenant-auth flows —
  email verification, token refresh, inviting teammates — are in the
  [API reference](/api-reference/overview), or do them in the
  [dashboard](https://app.dev.swarmd.ai).
</Tip>

***

## Step 2: Create your agent identity

Registration takes two calls, and the one that hands you credentials comes
first. `POST /registry/v1/agents` creates an **identity**: a name, an OAuth2
service account, and a webhook signing key. It asks for no URLs, because your
agent doesn't exist yet.

```bash theme={null}
curl -X POST https://api.dev.swarmd.ai/registry/v1/agents \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $SWARMD_TOKEN" \
  -d '{
    "name": "weather-agent",
    "description": "Answers questions about the weather."
  }'
```

| Field         | Required | What it is                                 |
| ------------- | -------- | ------------------------------------------ |
| `name`        | Yes      | The tenant-controlled name of the agent.   |
| `description` | No       | An optional tenant-controlled description. |

<Note>
  **Credentials first, code second.** This is deliberate: you get working
  credentials before you have anything to point Swarmd at, so you can build,
  run and test the agent locally with real identity.

  What you hold after this call is an *unbootstrapped shell* — an identity with
  no [A2A agent card](https://google.github.io/A2A/) behind it. It can mint
  tokens, but it cannot be subscribed to anything, cannot send or receive relay
  traffic, and cannot be sent a webhook. The registry reports its status as
  `BOOTSTRAP_REQUIRED` and refuses to use it: *"Agent must be bootstrapped
  before it can be used."*

  You clear that in [Step 7](#step-7-attach-your-agent-card), once there's a
  real agent serving a real card.
</Note>

***

## Step 3: Save the three secrets

The response contains three values:

```json theme={null}
{
  "agentId": "0c6eab9a-0569-4788-8f27-5056a6963437",
  "clientSecret": "ka5Qqf0O6kRIu5g5pw93bZGyD5vnahiG",
  "webhookSecret": "9f7e1c8d2b3a4f5e6d7c8b9a0f1e2d3c4b5a6f7e8d9c0b1a2f3e4d5c6b7a8f9e"
}
```

| Value           | What it is                                                                                                                                                       | Env var                 |
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- |
| `agentId`       | Your agent's UUID. Doubles as its OAuth2 **client id** — the agent's identity everywhere on the platform.                                                        | `SWARMD_AGENT_ID`       |
| `clientSecret`  | OAuth2 secret. Every **outbound** call the agent makes — registry, relay, MCP, LLM gateway — is authenticated by exchanging this for a short-lived bearer token. | `SWARMD_CLIENT_SECRET`  |
| `webhookSecret` | HMAC-SHA256 key. Swarmd signs every **inbound** webhook with it, so your agent can prove a "your subscriptions changed" kick really came from Swarmd.            | `SWARMD_WEBHOOK_SECRET` |

<Warning>
  **All three are shown exactly once.** Swarmd never returns them again — copy
  them into your secret store now. What recovery looks like differs by secret:

  * **`clientSecret` — rotate it in place.**
    `POST /registry/v1/agents/{agentId}/credential` mints a fresh one and
    returns `{ "clientId": "…", "clientSecret": "…" }`. The `clientId` is the
    agent id and does not change, so the agent keeps its identity, its
    subscriptions and its grants. Update `SWARMD_CLIENT_SECRET` and you're done.
  * **`webhookSecret` — no rotation endpoint.** `.../credential` neither returns
    nor changes it. Recovering it means deregistering the agent
    (`DELETE /registry/v1/agents/{agentId}`) and reactivating it
    (`POST /registry/v1/agents/{agentId}/reactivate`), which mints a fresh
    `clientSecret` *and* a fresh `webhookSecret` against the same agent id.
    Deregistration deactivates the agent's subscriptions, and reactivation does
    not restore them — an operator has to re-create the grants.

  The `agentId` is not a secret. It's your client id, and you can read it back
  from `GET /registry/v1/agents` at any time.
</Warning>

<Accordion title="What happens if I skip the webhook secret?">
  Your agent still works. Outbound calls are unaffected — they only use
  `clientSecret`.

  What you lose is *push*. When an operator subscribes your agent to a new
  sub-agent or grants it a new MCP server, Swarmd normally POSTs a signed kick to
  your agent's `/admin/webhook` and the agent re-discovers its catalogue in place.
  Without the secret, that endpoint refuses every delivery with `503`, and
  somebody has to `POST /admin/refresh` (or restart the pod) by hand after each
  change.

  Set it. It costs one line of `.env`.
</Accordion>

***

## Step 4: Install the SDK

Install the package for your track. The wrappers pull in `swarmd-sdk` plus
their framework, so you only install one thing.

<CodeGroup>
  ```bash Core SDK theme={null}
  pip install swarmd-sdk
  ```

  ```bash Google ADK theme={null}
  pip install swarmd-google-adk
  ```

  ```bash LangChain theme={null}
  pip install swarmd-langchain
  ```
</CodeGroup>

Use a virtualenv. The wrappers pin `a2a-sdk`, `mcp`, and their framework to
tested ranges, and those ranges have real teeth — see
[dependency ranges](/sdks/python/reference#dependency-ranges).

***

## Step 5: Write your `.env`

The SDK reads configuration from environment variables and loads a `.env` file
automatically via `python-dotenv`.

```bash .env theme={null}
# --- Identity: who your agent is ------------------------------------
SWARMD_AGENT_ID=0c6eab9a-0569-4788-8f27-5056a6963437
SWARMD_CLIENT_SECRET=ka5Qqf0O6kRIu5g5pw93bZGyD5vnahiG
SWARMD_WEBHOOK_SECRET=9f7e1c8d2b3a4f5e6d7c8b9a0f1e2d3c4b5a6f7e8d9c0b1a2f3e4d5c6b7a8f9e

# --- Where the platform lives ---------------------------------------
SWARMD_BASE_URL=https://api.dev.swarmd.ai
SWARMD_TOKEN_URL=https://auth.dev.swarmd.ai/realms/swarmd/protocol/openid-connect/token

# --- Your LLM: pick one ---------------------------------------------
# Route through Swarmd (audited, no provider key needed):
SWARMD_LLM_GATEWAY_ID=
# ...or go direct to OpenAI:
OPENAI_API_KEY=sk-...
OPENAI_MODEL=gpt-4o
```

<Warning>
  `.env` holds live secrets. Add it to `.gitignore` before you add anything to
  it. In production, inject these as environment variables from your secret
  manager rather than shipping a file.
</Warning>

<Accordion title="Why do I need to set BASE_URL and TOKEN_URL if they're the defaults?">
  For `swarmd-sdk` on its own, you don't — `SwarmDClient.from_env()` falls back
  to the production URLs.

  For the **wrappers** you do. Their `create_runtime()` only configures the
  runtime when all four of `SWARMD_AGENT_ID`, `SWARMD_CLIENT_SECRET`,
  `SWARMD_BASE_URL` and `SWARMD_TOKEN_URL` are present. Leave any one unset and
  you get **standalone mode**: the agent boots and serves, but discovers no
  sub-agents and no MCP tools.

  That's a deliberate escape hatch for local development without a Swarmd
  account — but it's also the single most common "why can't my agent see
  anything?" cause. If discovery is silently empty, check all four.
</Accordion>

***

## Step 6: Confirm it works

Before writing any agent code, prove the credentials are good:

```python check.py theme={null}
import os
from uuid import UUID

from dotenv import load_dotenv

from swarmd_sdk import SwarmDClient

load_dotenv()

me = UUID(os.environ["SWARMD_AGENT_ID"])

with SwarmDClient.from_env() as client:
    agents = client.get_agent_subscriptions(me)
    print(f"Authenticated as {me}")
    print(f"Subscribed to {len(agents)} agent(s):")
    for a in agents:
        print(f"  - {a.name}: {a.description}")
```

<Note>
  `get_agent_subscriptions()` takes your own agent id as an argument even though
  the client already authenticates as that agent — the endpoint is shared with
  tenant-admin callers who can query any agent. When an *agent* calls it, the
  registry compares the id against the JWT subject and rejects a mismatch with
  `403`, so passing anything other than your own id will fail.
</Note>

```bash theme={null}
python check.py
```

Three possible outcomes:

<AccordionGroup>
  <Accordion title="Authenticated as … / Subscribed to 0 agent(s)">
    **Correct.** An unbootstrapped shell has no subscriptions and can't be given
    any until its card is attached, and even a bootstrapped agent starts with none
    until an operator grants them. Your credentials work — that's what this step
    was checking. Move on.
  </Accordion>

  <Accordion title="ValueError: Missing required environment variables">
    `.env` isn't being found or isn't being read. Confirm the file sits in the
    directory you run `python` from, and that the variable names match exactly
    (`SWARMD_AGENT_ID`, not `SWARMD_AGENTID`).
  </Accordion>

  <Accordion title="AuthenticationError / TokenRefreshError">
    The token endpoint rejected your client credentials. Usually one of:

    * `SWARMD_CLIENT_SECRET` was truncated on copy — it has no separators, so a
      partial paste looks plausible.
    * The secret was rotated with
      `POST /registry/v1/agents/{agentId}/credential` and `.env` still holds the
      old one.
    * The agent was deregistered, which revokes its service account.
    * `SWARMD_TOKEN_URL` points at the wrong realm or the wrong environment.

    See [Troubleshooting](/sdks/python/troubleshooting#authentication-fails-at-startup).
  </Accordion>
</AccordionGroup>

***

## Step 7: Attach your agent card

This is the second half of registration, and the one that makes the agent
real. Come back to it once your agent is running and serving its card — the
SDK serves it at `/.well-known/agent-card.json` — on a URL Swarmd can reach.
You can build, run and iterate on the agent before this call; nothing that
involves another party — subscriptions, relay traffic, webhooks — works until
after it.

```bash theme={null}
curl -X PUT https://api.dev.swarmd.ai/registry/v1/agents/$SWARMD_AGENT_ID \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $SWARMD_TOKEN" \
  -d '{
    "agentCardUrl": "https://my-agent.example.com/.well-known/agent-card.json",
    "healthCheckUrl": "https://my-agent.example.com/health",
    "visibility": "PRIVATE",
    "registryVersion": "1.0.0",
    "reason": "First bootstrap"
  }'
```

| Field             | Required | What it is                                                                                                           |
| ----------------- | -------- | -------------------------------------------------------------------------------------------------------------------- |
| `agentCardUrl`    | Yes      | Your A2A-compliant agent card. Swarmd fetches it now to read the agent's name, description, capabilities and skills. |
| `healthCheckUrl`  | No       | Polled for a `200`. The card URL is used when omitted.                                                               |
| `visibility`      | Yes      | `PUBLIC`, `INTERNAL` or `PRIVATE` — who may discover and subscribe to this version.                                  |
| `registryVersion` | Yes      | Semantic version for this immutable snapshot, e.g. `1.0.0`.                                                          |
| `reason`          | No       | Up to 500 characters of notes on what changed in this version.                                                       |
| `authConfig`      | No       | Tenant-level authentication Swarmd should use when reaching your agent.                                              |

With the card attached, the shell becomes a routable agent: operators can
subscribe it to sub-agents and grant it MCP servers, the relay will carry its
traffic, and the webhook kicks described in
[Core concepts](/sdks/python/concepts#6-refresh-keeps-the-catalogue-live) start
arriving.

<Note>
  **Every `PUT` appends a version.** It is a full replacement, not a patch —
  send the complete body each time. Swarmd re-resolves the card, appends a new
  immutable version, and leaves the previous one intact;
  `GET /registry/v1/agents/{agentId}/versions` returns the history. Bump
  `registryVersion` on each publish.

  To change just the tenant-facing name or description, use
  `PATCH /registry/v1/agents/{agentId}` instead — that edits identity metadata
  and doesn't touch the card.
</Note>

<Tip>
  Routine card drift doesn't need a `PUT`.
  `POST /registry/v1/agents/{agentId}/refresh-card` re-fetches the card at the
  URL already on file and updates the cached metadata if it has changed. The
  Google ADK wrapper calls it on boot for you; with the core SDK or LangChain,
  call `client.refresh_agent_card(agent_id)` yourself. It cannot bootstrap a
  shell — until you `PUT` a card URL there is nothing for it to fetch.
</Tip>

***

## Next

<CardGroup cols={2}>
  <Card title="Core concepts" icon="diagram-project" href="/sdks/python/concepts">
    What the runtime, the two token audiences, and the refresh loop actually
    do. Ten minutes that make every helper obvious.
  </Card>

  <Card title="Skip to building" icon="rocket" href="/sdks/python/core-sdk">
    Straight into code: [Core SDK](/sdks/python/core-sdk),
    [Google ADK](/sdks/python/google-adk), or
    [LangChain](/sdks/python/langchain).
  </Card>
</CardGroup>
