# Aura Integration Guide

How an external system integrates with Aura. This covers the **inbound** surface — the MCP server, the tenant B2B2C API, the public agent directory, and webhooks.

> Base URLs: prod `https://www.moreaura.ai`, dev `https://dev.moreaura.ai`. Always use `www.` in prod — `moreaura.ai` issues a 307 that drops the `Authorization` header.
>
> A raw machine-fetchable copy of this guide is served at `/docs/integration/raw` (`text/markdown`).

---

## 1. Which surface should I use?

| You want to… | Use | Auth |
|---|---|---|
| Let an AI client / agent discover & read Aura agent cards over MCP | **Aura MCP server** (`POST /api/mcp`) | Bearer `AURA_MCP_TOKEN` |
| Pre-register your end users & send them transactional texts (B2B2C tenant) | **Tenant API** (`/api/tenant/*`) | Bearer `aura_prov_…` provisioning key |
| Browse the public agent directory (no auth) | `GET /.well-known/agents` | none |
| Have an Aura agent **use** your MCP server | **MCP Servers setting** (`/api/settings/mcp-servers`) | dashboard session |
| Receive/send iMessage, billing, Slack, voice events | **Webhooks** (`/api/webhooks/*`) | per-provider signature |

The three families a third-party backend typically integrates with are the **Tenant API**, the **MCP server**, and the **public directory**.

---

## 2. Aura as an MCP server

Aura exposes a read-only [Model Context Protocol](https://modelcontextprotocol.io) server so MCP-capable clients (Claude, other agents) can discover and read public Aura agent cards.

- **Endpoint:** `POST https://www.moreaura.ai/api/mcp`
- **Transport:** Streamable HTTP, stateless JSON-RPC 2.0 (no SSE, no session id). `GET` returns `405`.
- **Auth:** `Authorization: Bearer <AURA_MCP_TOKEN>` (a secret distinct from any internal secret).
- **Required header:** `MCP-Protocol-Version: 2025-06-18` (also accepts `2025-03-26`).
- **Gates (in order):** server must be enabled (else `404`) → `Origin` must be allowlisted (else `403`) → protocol version (else `400`) → bearer token (else `401` + `WWW-Authenticate: Bearer`) → IP rate limit (else `429`).

### Methods

`initialize`, `ping`, `tools/list`, `tools/call`, and `notifications/*` (acked with `202`). `serverInfo`: `{ "name": "aura", "version": "0.1.0" }`.

### Tools (read-only — public directory only)

**`aura.search_agents`**

```json
{ "query": "real estate", "capability": "scheduling" }
```

Returns matching directory-listed agents. `capability` is optional.

**`aura.read_agent_card`**

```json
{ "handle": "nova" }
```

Returns the agent card with addresses:

```json
{
  "email":     "nova@moreaura.ai",
  "subdomain": "nova.moreaura.ai",
  "mcp":       "https://www.moreaura.ai/api/mcp"
}
```

Private and nonexistent handles return an identical "not found" result (enumeration-proof). Only agents that are directory-listed and active are ever returned.

### Example

```bash
curl -X POST https://www.moreaura.ai/api/mcp \
  -H "Authorization: Bearer $AURA_MCP_TOKEN" \
  -H "MCP-Protocol-Version: 2025-06-18" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call",
       "params":{"name":"aura.search_agents","arguments":{"query":"real estate"}}}'
```

---

## 3. Public agent directory (no auth)

A no-auth, A2A-style discovery endpoint — summaries only, no address block.

- **`GET /.well-known/agents`** — query params `q`, `capability`, `limit` (1–100).
- **Response:** `{ "agents": [{ "handle", "name", "description", "capabilities" }], "count" }`
- **Caching:** `Cache-Control: public, max-age=60`. IP rate-limited.

Also public: `GET /api/vcard/{slug}` returns a vCard 3.0 (`text/vcard`) for branded agents (404 otherwise, non-enumerable; 503 if the agent has no phone number).

---

## 4. Tenant API (B2B2C partners)

For partner backends that run assistants on Aura. Both endpoints authenticate with a **tenant provisioning key** and can only act through that tenant's own active assistants — a tenant can never send from another tenant's number.

- **Auth:** `Authorization: Bearer aura_prov_<base62>`. Only the SHA-256 hash of the key is stored server-side; mint/rotate via Aura ops. Missing/invalid key → `401 {"error":"Unauthorized"}`.

### `POST /api/tenant/end-users` — invisible linking

Pre-register a verified end user so their first text "just works".

```json
{
  "phoneNumber": "+15551234567",
  "name": "Jane Doe",
  "metadata": { "inkdApiKey": "…" }
}
```

- `phoneNumber` E.164, required. `name` optional (≤200). `metadata` optional; secret keys are sealed at rest.
- **`201 {"endUserId","created":true}`** (new) / **`200 {"endUserId","created":false}`** (existing — metadata merged).
- Consent provenance is stamped automatically (`source: "tenant_provisioning"`).
- Errors: `400 {"error":"Invalid JSON body"}` or first validation issue.

### `POST /api/tenant/messages` — outbound transactional text (e.g. OTP)

Sends from the assistant's own number.

```json
{
  "phoneNumber": "+15551234567",
  "text": "Your code is 123456",
  "assistantSlug": "nova"
}
```

- `phoneNumber` E.164, required. `text` 1–1600 chars, required. `assistantSlug` required only if the tenant has more than one assistant with a number.
- **`200 {"messageId","status"}`**.
- Errors:
  - `400` invalid body
  - `404 {"error":"assistant_not_found"}`
  - `409 {"error":"no_sender_number"}` / `{"error":"ambiguous_assistant","assistantSlugs":[…]}`
  - `403 {"error":"recipient_opted_out"}` — STOP/compliance, applies even to OTPs
  - `429 {"error":"rate_limited","retryAfterSeconds":N}` + `Retry-After` header (~1 msg/sec/number)
  - `503 {"error":"messaging_not_configured"}`, `502 {"error":"send_failed"}`
- The message is recorded into the active conversation thread when one exists. Phone numbers and message bodies are never logged.

---

## 5. Letting an Aura agent use YOUR MCP server

This is the reverse direction — configuring an Aura agent as a *client* of an external MCP server. Managed from the dashboard (Settings → MCP Servers), backed by:

- **`POST /api/settings/mcp-servers`** (auth: dashboard session)

```json
{
  "agentId": "<uuid>",
  "name": "My Server",
  "serverUrl": "https://…",
  "authMethod": "none",
  "token": "…"
}
```

- `name` 1–80. `serverUrl` ≤2048, https, SSRF-validated (DNS + private-IP blocklist). `authMethod` `"none"` or `"bearer"`; `token` required if `bearer`.
- `201 {server, runtimeSync}` on success.
- **`422 "Custom MCP servers are not supported for this agent's runtime"`** — custom MCP is available only on legacy VM-runtime agents; serverless-orchestrator agents are rejected.
- `502` if saved but the hot-push to the running agent failed.
- Also: `GET /api/settings/mcp-servers`, `PUT/DELETE /api/settings/mcp-servers/{id}`.

`authMethod: "oauth"` is recognized but not supported (skipped). Only `none` and `bearer`.

---

## 6. Webhooks (inbound, signed)

Each webhook verifies a provider-specific signature. Send `Content-Type: application/json` unless noted.

| Route | Signature | Header(s) |
|---|---|---|
| `POST /api/webhooks/stripe` | Stripe `constructEvent` | `stripe-signature` |
| `POST /api/webhooks/stripe/issuing-authorizations` | Stripe `constructEvent` | `stripe-signature` |
| `POST /api/webhooks/slack/events` | HMAC-SHA256 `v0=`, base `v0:{ts}:{body}` | `x-slack-signature` + `x-slack-request-timestamp` |
| `POST /api/webhooks/composio/triggers` | HMAC-SHA256 base64 (Standard-Webhooks/Svix) | `webhook-signature`, `webhook-id`, `webhook-timestamp` |
| `POST /api/webhooks/twilio/voice` · `/status` · `/livekit-sip` | HMAC-SHA1 base64 | `x-twilio-signature` |
| `POST /api/webhooks/sendblue/inbound/<token>` | path token (+ empirical scheme hardening) | — |

Contract quirks worth knowing:

- **Sendblue (iMessage)** uses a drop-and-ack model — after verification, every branch returns `200`; the body signals state (`{"ok":true}`, `{"ok":true,"duplicate":true}`, `{"ok":true,"rate_limited":…}`). Status callbacks are ignored for inbound routing; the AI reply runs asynchronously. Pre-auth errors: `401` (bad token/signature).
- **Slack** answers the `url_verification` challenge (`200 {"challenge"}`) before signature check. Only `app_mention` and IM `message` events are handled; routed by `team_id`.
- **Stripe issuing-authorizations** never returns non-200 for business outcomes — always `{"approved":boolean}` with header `Stripe-Version: 2025-03-31.basil` (real-time card-auth contract).
- **Twilio voice/status** respond with TwiML XML (`text/xml`), and return `200` even on internal error to suppress retries.

---

## 7. Authentication reference

Aura uses several **separate** secrets — do not conflate them:

| Secret / scheme | Header | Used for |
|---|---|---|
| `AURA_MCP_TOKEN` | `Authorization: Bearer` | the MCP server (`/api/mcp`) only |
| Tenant provisioning key (`aura_prov_…`) | `Authorization: Bearer` | tenant API (`/api/tenant/*`) |
| Dashboard session | cookie | dashboard / settings routes |
| Webhook signing secrets | provider header | inbound webhooks |

Bearer checks are constant-time and fail closed when the corresponding secret is unset.

---

*Questions or need a provisioning key? Contact the Aura team.*
