---
title: "Quickstart"
description: "Create an organization and an agent, send mail, and wait for the reply as a durable event — in about five minutes."
canonical_url: "https://www.agent-identity.dev/docs/getting-started/quickstart"
markdown_url: "https://www.agent-identity.dev/docs/getting-started/quickstart.md"
last_updated: "2018-10-20"
x_farming_labs_generated_preamble: true
agent:
  tokenBudget: 1200
  task: "Create an organization and one agent on Agent Identity, send an email as that agent, and wait for the reply as an event."
  outcome: "One verified organization, one agent with a deliverable mailbox, one accepted outbound message, and one event returned from a long-poll wait."
  appliesTo:
    package:
      - "@agentidentity/sdk"
  prerequisites:
    - "Node.js 20 or newer."
    - "An email address you can read, to receive the owner verification code."
  commands:
    - run: "npm install @agentidentity/sdk"
      description: "Install the client. It has no runtime dependencies."
  sideEffects:
    - "Creates a real organization and a real, deliverable mailbox. Mail you send leaves the building."
    - "The admin API key is returned exactly once and cannot be re-read."
  verification:
    - description: "Confirm the key works."
      expect: "client.whoami() resolves with your organization."
    - description: "Send a message and then wait for the resulting event."
      expect: "mail.send resolves with status queued or sent, and events.wait returns an event with timedOut false."
  rollback:
    - "Suspend the agent with client.identities.suspend and revoke the key with client.apiKeys.revoke."
  failureModes:
    - symptom: "createOrganization is rejected because the slug is taken."
      resolution: "Slugs are unique across the service. Add a suffix and retry."
    - symptom: "Mail sends before verification are rejected or limited."
      resolution: "Until the owner code is verified an organization can only email its owner, a few times a day. Call verifyOwner with the code from the inbox."
    - symptom: "events.wait returns timedOut true."
      resolution: "A wait matches only events created after it starts unless `since` is passed. Start the wait before sending, or pass `since` from just before the send."
    - symptom: "401 on every call."
      resolution: "The key is missing, revoked, or from another organization. Send it as Authorization: Bearer <key>."
---

# Quickstart
URL: /docs/getting-started/quickstart
LLM index: /llms.txt
Description: Create an organization and an agent, send mail, and wait for the reply as a durable event — in about five minutes.
Related: /docs/sdk, /docs/getting-started/authentication, /docs/concepts/events, /docs/mail/sending, /docs/cli

<!-- farming-labs:agent-contract:start -->
## Agent Contract

Task: Create an organization and one agent on Agent Identity, send an email as that agent, and wait for the reply as an event.
Outcome: One verified organization, one agent with a deliverable mailbox, one accepted outbound message, and one event returned from a long-poll wait.

### Applies To

- Package: `@agentidentity/sdk`

### Prerequisites

- Node.js 20 or newer.
- An email address you can read, to receive the owner verification code.

### Commands

- `npm install @agentidentity/sdk` — Install the client. It has no runtime dependencies.

### Side Effects

- Creates a real organization and a real, deliverable mailbox. Mail you send leaves the building.
- The admin API key is returned exactly once and cannot be re-read.

### Verification

- Confirm the key works.
  - Expected: client.whoami() resolves with your organization.
- Send a message and then wait for the resulting event.
  - Expected: mail.send resolves with status queued or sent, and events.wait returns an event with timedOut false.

### Rollback

- Suspend the agent with client.identities.suspend and revoke the key with client.apiKeys.revoke.

### Failure Modes

- createOrganization is rejected because the slug is taken. — Recovery: Slugs are unique across the service. Add a suffix and retry.
- Mail sends before verification are rejected or limited. — Recovery: Until the owner code is verified an organization can only email its owner, a few times a day. Call verifyOwner with the code from the inbox.
- events.wait returns timedOut true. — Recovery: A wait matches only events created after it starts unless `since` is passed. Start the wait before sending, or pass `since` from just before the send.
- 401 on every call. — Recovery: The key is missing, revoked, or from another organization. Send it as Authorization: Bearer <key>.
<!-- farming-labs:agent-contract:end -->

# Quickstart

By the end of this page you will have an organization, an agent with a real mailbox, one
sent message, and one event that you waited on rather than polled for.

You need Node 20+ and an email address you can read. Everything here runs against the
hosted API at `https://api.agent-identity.dev`; there is nothing to install or run
besides the SDK.

```bash title="terminal"
npm install @agentidentity/sdk
```

## 1. Create an organization

An organization owns your agents, keys, and events. Creating one is the only call that
needs no key — it returns the first one.

```ts title="setup.ts"
import { createOrganization } from "@agentidentity/sdk";

const BASE_URL = "https://api.agent-identity.dev";

const org = await createOrganization(BASE_URL, {
  name: "Acme",
  slug: "acme",
  ownerEmail: "you@acme.com",
});

console.log(org.adminApiKey); // aid_live_… — shown once, save it now
```

<Callout type="warn">
  `adminApiKey` is returned once and never again — it is stored hashed. Put it somewhere
  safe now, for example `export AID_KEY="aid_live_…"`.
</Callout>

Naming an `ownerEmail` sends a six-digit code to that inbox. Return it to prove a human
owns the organization:

```ts title="setup.ts"
import { verifyOwner } from "@agentidentity/sdk";

await verifyOwner(BASE_URL, org.adminApiKey, "481923");
```

Until you verify, the organization can only email its owner, and only a few times a day.
`org.nextStep` says what is outstanding in words you can show a person.

## 2. Create an agent

Creating an agent provisions its mailbox in the same call, so the address comes back
with it.

```ts title="agent.ts"
import { AgentClient } from "@agentidentity/sdk";

const client = new AgentClient({
  baseUrl: "https://api.agent-identity.dev",
  apiKey: process.env.AID_KEY!,
});

const agent = await client.identities.create({
  handle: "support-agent",
  displayName: "Support Agent",
});

console.log(agent.id);             // 6f21… — keep this
console.log(agent.mailboxAddress); // support-agent@…
```

## 3. Send mail as that agent

```ts title="agent.ts"
const message = await client.mail.send({
  identityId: agent.id,
  to: ["you@acme.com"],
  subject: "Hello",
  text: "First message.",
  idempotencyKey: "quickstart-1",
});

console.log(message.status); // "queued", then "sent", then "delivered"
```

`send` resolves once the message is accepted, not once it is delivered: the status moves
from `queued` through `sent` to `delivered`, `bounced`, or `complained` as the provider
reports back. Those transitions arrive as events, which is the next step.

`idempotencyKey` is optional but cheap insurance. Replaying the same key with the same
body returns the original result instead of sending twice; the same key with a different
body is a `409`.

## 4. Wait for the reply

This is the part that makes the rest worth having. `events.wait` blocks until a matching
event is appended, or until the timeout — no polling loop.

```ts title="agent.ts"
// Reply to the message you just sent, from your own inbox.
const { event, timedOut } = await client.events.wait({
  type: "mail.received",
  identityId: agent.id,
  filter: { threadId: message.threadId },
  timeoutMs: 55_000,
});

if (!timedOut) {
  const thread = await client.mail.getThread(agent.id, event!.payload.threadId);
  console.log(thread.messages.at(-1)?.textBody);
}
```

A wait matches only events created **after the wait starts**, unless you pass `since`.
That is the most common surprise here: send, *then* wait, and a reply that lands in
between is missed. For a long-lived agent, use `client.events.subscribe`, which owns the
cursor and reconnection for you — see the [SDK guide](/docs/sdk#a-complete-agent).

## Same thing without the SDK

Every SDK call is one HTTP request. Bodies are `snake_case`.

```bash title="terminal"
# Create the agent
curl -sS -X POST https://api.agent-identity.dev/v1/identities \
  -H "authorization: Bearer $AID_KEY" \
  -H 'content-type: application/json' \
  -d '{"handle":"support-agent","display_name":"Support Agent"}'

# Send as that agent (returns 202)
curl -sS -X POST "https://api.agent-identity.dev/v1/identities/$AID_AGENT/mail/send" \
  -H "authorization: Bearer $AID_KEY" \
  -H 'content-type: application/json' \
  -H "idempotency-key: quickstart-1" \
  -d '{"to":["you@acme.com"],"subject":"Hello","text":"First message."}'

# Long-poll for the reply (up to 55s)
curl -sS "https://api.agent-identity.dev/v1/events/wait?type=mail.received&identity_id=$AID_AGENT&timeout_ms=30000" \
  -H "authorization: Bearer $AID_KEY"
```

A timed-out wait is `200` with `{"event": null, "timed_out": true}`, so branch on the
body, not the status code.

## Check it worked

| Check | Expected |
| --- | --- |
| `await client.whoami()` | Your organization; `actorType` is `"user"` for the org key |
| `await client.identities.list()` | An array containing `support-agent` with a non-empty `mailboxAddress` |
| `await client.mail.send(…)` | `status` of `queued` or `sent` |
| `await client.events.list()` | The `mail.sent` event in the log |

## Troubleshooting

**`401` on every call.** The header is `Authorization: Bearer <key>`; the SDK sets it from
`apiKey`. A revoked key, or one from a different organization, reads as no actor at all,
so the API returns `401`, not `403`.

**Sends are rejected before you verify.** An unverified organization can only email its
owner. Run `verifyOwner` with the code from the inbox.

**`timedOut: true` and no event.** Expected when nothing matched in the window. Waits are
capped at 55 seconds (the default is 25). Pass `since` to look back.

**A send is accepted but nothing arrives.** A `202` means queued. Look at the events for
that message: `mail.bounced` carries the reason. See [Sending mail](/docs/mail/sending).

## Then what

- [TypeScript SDK](/docs/sdk) — the full client: every namespace, errors, retries, and a complete agent loop.
- [Authentication](/docs/getting-started/authentication) — org keys versus agent keys, and why your running agent should hold the second kind.
- [Receiving mail](/docs/mail/receiving) — threading and what a `mail.received` payload carries.
- [Core concepts](/docs/concepts) — agents, events, and what is actually durable.
- [Phone](/docs/phone) — give the agent a number; Aid Voice answers calls with no server to run.
- [Agent2Agent](/docs/a2a) — let it send work to other agents and take work from them.

Sequencing that matters, and is easy to get wrong from the prose above:

- `POST /v1/organizations` is the only unauthenticated write. Capture `admin_api_key` from
  its response immediately; it is hashed at rest and no endpoint returns it again. If it is
  lost, that organization cannot be recovered — create a new one with a different slug.
- Pass `owner_email` (SDK: `ownerEmail`) and finish with `POST /v1/auth/verify` (SDK:
  `verifyOwner`) using the six-digit code the human receives. Ask the human for the code;
  do not guess it.
- `POST /v1/identities` provisions the mailbox inside the same transaction. There is no
  separate mailbox-creation endpoint.
- The wait's default window is `after = now` when the request is handled. When scripting
  send-then-wait, capture a timestamp before the send and pass it as `since` (RFC 3339), or
  run the wait concurrently with the send.
- `timeout_ms` is coerced from the query string: minimum `0`, maximum `55000`, default
  `25000`. Values above the maximum are a `400`, not a clamp.
- A timed-out wait is `200` with `{"event": null, "timed_out": true}`, not `204` and not an
  error. Branch on `timed_out`, not on the status code.

Do not hardcode the endpoint list from this page. Read `GET https://api.agent-identity.dev/v1/openapi.json`
for the authoritative schema of every route.

## Sitemap

See the full [sitemap](/sitemap.md) for all pages.
Docs-scoped sitemap: [/docs/sitemap.md](/docs/sitemap.md).
Well-known sitemap: [/.well-known/sitemap.md](/.well-known/sitemap.md).
