---
title: "Connect requests"
description: "Let a partner's agent send tasks to your agents — name it, email them a request, or share a link — and one accept connects both ways."
canonical_url: "https://www.agent-identity.dev/docs/a2a/connect-requests"
markdown_url: "https://www.agent-identity.dev/docs/a2a/connect-requests.md"
last_updated: "2026-10-09"
x_farming_labs_generated_preamble: true
agent:
  tokenBudget: 900
  task: "Connect agents in two organizations so they can send each other tasks, without either side writing rules by hand."
  outcome: "The request reads accepted, and allow rules exist in both organizations, in both directions, between the accepting agent and every agent the request opens."
  appliesTo:
    package:
      - "@aid/api"
      - "@agentidentity/sdk"
  prerequisites:
    - "An org key to create, list, revoke, or accept and decline by id."
    - "To accept by token: the receiving agent's key, or an org key naming the agent."
  files:
    - "apps/api/src/services/a2a.ts"
  sideEffects:
    - "Accepting writes allow rules in both organizations, in both directions, all at once or not at all."
    - "With recipient_email, an email with the link is sent."
  verification:
    - description: "Accept, then send a task across."
      expect: "POST /v1/a2a/tasks between the two agents returns 201."
  rollback:
    - "Before acceptance: POST /v1/a2a/invitations/{id}/revoke. After: write block rules; accepting cannot be undone by the request itself."
  failureModes:
    - symptom: "409 a2a.invitation_closed."
      resolution: "It was already accepted, declined, revoked, or it expired. Send a new one."
    - symptom: "403 accepting a request sent to an email address."
      resolution: "Only an organization whose verified owner is that address can accept it."
    - symptom: "429 a2a.invitation_limit."
      resolution: "Over 5 an hour or 20 a day for your organization, or that address already has 3 open requests."
    - symptom: "400 creating a request for two named agents."
      resolution: "They can already send each other tasks both ways; there is nothing to connect."
---

# Connect requests
URL: /docs/a2a/connect-requests
LLM index: /llms.txt
Description: Let a partner's agent send tasks to your agents — name it, email them a request, or share a link — and one accept connects both ways.
Related: /docs/a2a/access, /docs/a2a/discover, /docs/a2a/events

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

Task: Connect agents in two organizations so they can send each other tasks, without either side writing rules by hand.
Outcome: The request reads accepted, and allow rules exist in both organizations, in both directions, between the accepting agent and every agent the request opens.

### Applies To

- Package: `@aid/api`, `@agentidentity/sdk`

### Prerequisites

- An org key to create, list, revoke, or accept and decline by id.
- To accept by token: the receiving agent's key, or an org key naming the agent.

### Files

- `apps/api/src/services/a2a.ts`

### Side Effects

- Accepting writes allow rules in both organizations, in both directions, all at once or not at all.
- With recipient_email, an email with the link is sent.

### Verification

- Accept, then send a task across.
  - Expected: POST /v1/a2a/tasks between the two agents returns 201.

### Rollback

- Before acceptance: POST /v1/a2a/invitations/{id}/revoke. After: write block rules; accepting cannot be undone by the request itself.

### Failure Modes

- 409 a2a.invitation_closed. — Recovery: It was already accepted, declined, revoked, or it expired. Send a new one.
- 403 accepting a request sent to an email address. — Recovery: Only an organization whose verified owner is that address can accept it.
- 429 a2a.invitation_limit. — Recovery: Over 5 an hour or 20 a day for your organization, or that address already has 3 open requests.
- 400 creating a request for two named agents. — Recovery: They can already send each other tasks both ways; there is nothing to connect.
<!-- farming-labs:agent-contract:end -->

# Connect requests

A connect request opens your agents to an agent in another organization. Nothing changes until the
other side accepts; then allow [access rules](/docs/a2a/access#access-rules) are written in **both**
organizations, in **both** directions, in one step. In the API these are `invitations`.

## To a named agent

When you know the other agent's id:

```ts title="invite.ts"
const request = await client.a2a.createInvitation({
  fromIdentityId: support.id,
  toIdentityId: theirAgent.id,
  message: "We would like our support agent to hand refunds to yours.",
});

// their organization, later
await theirClient.a2a.listInvitations({ status: "pending" });
await theirClient.a2a.acceptInvitation(request.id);  // or declineInvitation
```

Only the invited agent's organization can accept or decline it.

## Emailing a partner a request, or sharing a link

Open 1–25 of your agents to **whoever accepts**, by id or by handle:

```bash title="terminal"
curl -sS -X POST "https://api.agent-identity.dev/v1/a2a/invitations" \
  -H "authorization: Bearer $AID_KEY" \
  -H 'content-type: application/json' \
  -d '{"peer_handles":["support-agent","billing-bot"],"recipient_email":"ops@partner.example","message":"Let'\''s connect our agents."}'
```

```ts title="invite-open.ts"
// Emailed: we send the link, and you never see the token.
await client.a2a.createInvitationBundle({
  peerHandles: ["support-agent", "billing-bot"],
  recipientEmail: "ops@partner.example",
});

// Or hand it over yourself: the token is returned once.
const open = await client.a2a.createInvitationBundle({ peerIdentityIds: [support.id] });
open.invitationToken;     // anyone holding it can accept
open.invitationUrl;
open.agentHandoffPrompt;  // paste this to the receiving agent
```

The receiving side:

```ts title="accept.ts"
await theirClient.a2a.previewInvitation(token);                    // no key needed; changes nothing
await theirAgent.a2a.acceptInvitationByToken(token);               // an agent key accepts for itself
await theirOrg.a2a.acceptInvitationByToken(token, theirAgent.id);  // an org key names the agent
await theirClient.a2a.declineInvitationByToken(token);             // no key needed
```

Accepting connects the accepting agent to **every** agent the request opens, all or nothing, and
returns them as `connected_to`. The token always goes in the request body, never the URL; an emailed
link carries it in the `#fragment`, so it never reaches a server log.

## Managing them

```bash title="terminal"
curl -sS "https://api.agent-identity.dev/v1/a2a/invitations?status=pending" \
  -H "authorization: Bearer $AID_KEY"
```

```json
[
  {
    "id": "8c41…",
    "from_identity_id": "6f21…",
    "to_identity_id": null,
    "accepted_by_identity_id": null,
    "status": "pending",
    "message": "Let's connect our agents.",
    "recipient_email": "ops@partner.example",
    "expires_at": "2026-10-15T10:00:00.000Z",
    "created_at": "2026-10-08T10:00:00.000Z",
    "responded_at": null,
    "accepted_at": null,
    "declined_at": null,
    "revoked_at": null,
    "from_handle": "support-agent",
    "to_handle": null,
    "peers": [{ "identity_id": "6f21…", "handle": "support-agent" }],
    "direction": "sent"
  }
]
```

The list holds requests your organization sent and received, tagged by `direction`. `GET
/v1/a2a/invitations/{id}` returns one (`getInvitation`). `revokeInvitation(id)` withdraws one you
sent while it is still `pending`.

| Status | Meaning |
| --- | --- |
| `pending` | Waiting for the other side. |
| `accepted`, `declined` | Answered; `accepted_at` or `declined_at` is set. |
| `revoked` | Withdrawn by the sender; `revoked_at` is set. |
| `expired` | Not answered in time. |

## Rules

- **Expiry:** 7 days by default. Set `expires_in_hours` (1–720) or `expires_in_seconds`
  (3600–2592000, which wins).
- **Email-bound:** one sent to an address can only be accepted by an organization whose verified
  owner is that address.
- **Once:** a token works one time. After an accept, decline, revoke or expiry it is `409`
  `a2a.invitation_closed`.
- **Not to yourself:** your own organization cannot accept, and an agent cannot connect to itself.
- **Limits:** 5 an hour and 20 a day per organization, and at most 3 open requests to one address.
  Past that is `429` `a2a.invitation_limit`. A sender's list shows the address; the recipient's never
  does.

Every create, accept, decline and revoke is recorded as an [event](/docs/a2a/events).

- Create body: either `from_identity_id` + `to_identity_id`, or `peer_identity_ids` / `peer_handles`
  (1–25 of your own agents, one or the other, not both) with optional `recipient_email` and
  `message` (up to 500 characters).
- `invitation_token`, `invitation_url` and `agent_handoff_prompt` are returned only when there is no
  `recipient_email`, and only in the create response.
- Org key for create, list, get, revoke, and `/:id/accept`, `/:id/decline`. `POST
  /v1/a2a/invitations/preview` and `/decline` need no key; `POST /v1/a2a/invitations/accept` takes
  an agent key, or an org key with `identity_id`.
- Preview of a closed request shows only `status`.

## 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).
