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

# Twin API

> Upload agent traces and download their synthetic twins.

The twin API creates a synthetic twin of an agent trace. A twin replaces every value that identifies a person, a private organisation, an account or a secret with a consistent synthetic value of the same kind, at every occurrence. Structure and format match the trace. Each trace is twinned independently.

| Input | Endpoint |
| - | - |
| One trace | [`POST /v1/kennet/twins`](https://docs.metaphi.ai/kennet/api/twins#create-a-twin) |
| Many traces | [`POST /v1/kennet/files`](https://docs.metaphi.ai/kennet/api/files#upload-a-file), then [`POST /v1/kennet/batches`](https://docs.metaphi.ai/kennet/api/batches#create-a-batch) |
| An agent on the kennet MCP server | [MCP tools](https://docs.metaphi.ai/kennet/api/mcp.md) |

**Once created, a twin completes** unless its input causes a failure, such as a trace that is not text. Errors on our side are retried automatically. If the organization's [balance](https://docs.metaphi.ai/account/credit.md) runs out, the twin or batch pauses and resumes once credit is added. Only completed twins are charged.

## Quickstart

Create a twin, wait for `status: completed`, and download its content.

```sh curl theme={"dark"}
# 1. Create the twin. Returns 202 with status: queued.
curl https://api.metaphi.ai/v1/kennet/twins \
  -H "Authorization: Bearer $API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -F file=@trace.jsonl
# {"id": "twin_4FFTGxX_b40dXo8F-ObejckZj1DsFKPhqvJFo25CNH0", "object": "kennet.twin", "status": "queued", ...}

# 2. Retrieve it until status is completed, or use a webhook.
curl https://api.metaphi.ai/v1/kennet/twins/twin_4FFTGxX_b40dXo8F-ObejckZj1DsFKPhqvJFo25CNH0 \
  -H "Authorization: Bearer $API_KEY"

# 3. Download the twin.
curl https://api.metaphi.ai/v1/kennet/twins/twin_4FFTGxX_b40dXo8F-ObejckZj1DsFKPhqvJFo25CNH0/content \
  -H "Authorization: Bearer $API_KEY" -o trace.twin.jsonl
```

```python Python theme={"dark"}
import os, time, uuid
import requests

API = "https://api.metaphi.ai/v1/kennet"
s = requests.Session()
s.headers["Authorization"] = f"Bearer {os.environ['API_KEY']}"

# 1. Upload the trace
with open("trace.jsonl", "rb") as f:
    r = s.post(f"{API}/twins", files={"file": ("trace.jsonl", f)},
               headers={"Idempotency-Key": str(uuid.uuid4())})
r.raise_for_status()
twin = r.json()

# 2. Wait for it to end
delay = 5
while twin["status"] not in ("completed", "failed"):
    time.sleep(delay)
    delay = min(delay * 2, 60)
    twin = s.get(f"{API}/twins/{twin['id']}").json()
if twin["status"] == "failed":
    raise SystemExit(twin["error"]["message"])

# 3. Download the twin
with s.get(f"{API}/twins/{twin['id']}/content", stream=True) as r:
    r.raise_for_status()
    with open("trace.twin.jsonl", "wb") as out:
        for chunk in r.iter_content(1 << 20):
            out.write(chunk)
```

Poll with backoff, or use [webhooks](https://docs.metaphi.ai/kennet/api/webhooks.md).

## Base URL

```
https://api.metaphi.ai/v1/kennet
```

Requests and responses are JSON, except uploads (`multipart/form-data`) and twin content (the file). The OpenAPI document is at [`https://api.metaphi.ai/v1/kennet/openapi.json`](https://api.metaphi.ai/v1/kennet/openapi.json), with no authentication.

## Authentication

Authenticate with your secret key as a bearer token:

```sh theme={"dark"}
curl https://api.metaphi.ai/v1/kennet/twins -H "Authorization: Bearer $API_KEY"
```

| Key | Starts with | Mode |
| - | - | - |
| Live | `mai_sk_` | Real twins, paid from your organization's prepaid balance |
| Test | `mai_sk_test_` | Real twins, free, with the limits below |

Owners and admins create [organization keys](https://docs.metaphi.ai/account/keys.md) in the console, or with `kennet keys create`. A key without twin access gets `403 insufficient_scope`. Keys with delivery access also call the [deliveries](https://docs.metaphi.ai/kennet/api/deliveries.md) endpoints.

## Versioning

The current version is `2026-10-11`. Responses include the `Metaphi-Version` header, and events include `api_version`. To pin a version, send the `Metaphi-Version` header; an unknown value returns `400 unknown_api_version`. Without the header, requests use the current version.

## Test mode

Test keys create real twins at no charge, with these limits:

* Files up to 2 MiB.
* 5 twins per organization per 24 hours. Further creates return `403 test_quota_exhausted`.
* Objects have `"livemode": false`.
* Test and live objects (twins, files, batches, webhook endpoints, idempotency keys) are separate.

## Limits

| Limit | Value |
| - | - |
| File size | 50 MiB (2 MiB in test mode). Split a larger trace and upload each part |
| Requests in one batch | 10,000. Send more as several batches |
| Batch request body | 4 MiB |
| Webhook endpoints | 16 per organization and mode |

There is no limit on concurrent twins. Each twin is queued and starts as capacity allows.

## Going to production

* **Batch many traces.** A [batch](https://docs.metaphi.ai/kennet/api/batches.md) takes up to 10,000 traces with no pacing required.
* **Use webhooks.** [Events](https://docs.metaphi.ai/kennet/api/webhooks.md) report each twin and batch status change. If you poll, poll batches, not twins.
* **Send an `Idempotency-Key` on every create.** Reuse it on retries. See [Idempotency](https://docs.metaphi.ai/kennet/api/idempotency.md).
* **Handle errors by `code`.** Retry only `retryable` errors, after `Retry-After`. See [Errors](https://docs.metaphi.ai/kennet/api/errors.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.