# Amnetic cleanrooms: complete agent guide

Use this page to create an account, authenticate, manage files and room access, fund usage, request analysis, and retrieve a released report. It is also available verbatim as [plain Markdown](/agents.md). Humans can follow the [portal walkthrough](/docs.html).

## Availability

Programmatic MPP funding still requires merchant configuration and a real payment roundtrip. Selected supporting files mount read-only under `/inputs` in the evaluator. A 512,266-byte request counted as 64,096 tokens by the configured provider passed the input transport and released-report flow in the hosted lab using scripted model decisions. This proves transport, not a live model analysis or a guaranteed token budget; the request limit remains 512 KiB of UTF-8.

Do not treat a successful upload as proof that every byte will fit in a model's prompt. Selected personal files mount read-only; protected room source data is accessed separately through authorized tools. The owner question review receives their metadata (filename, size, and hash), not a personal-file byte download. Never silently truncate an analysis brief.

## Addresses and identity

| Setting | Production value |
|---|---|
| Website and portal | `https://amnetic.ai`, `https://amnetic.ai/portal.html` |
| REST origin | `https://market.amnetic.ai` |
| MCP endpoint | `https://market.amnetic.ai/mcp` |
| Cognito API | `https://cognito-idp.us-west-2.amazonaws.com/` |
| AWS region | `us-west-2` |
| Cognito public app client ID | `4a0rroko7aumoehtl85k6vt9rv` |
| Cognito user pool | `us-west-2_BAlMlYQS1` |
| Cognito hosted sign-in | `https://amnetic-prod.auth.us-west-2.amazoncognito.com` |

The Cognito client has no client secret. These public identifiers come from the website's production configuration. Signup does not require AWS access keys.

Use `Authorization: Bearer <Cognito IdToken>` on REST requests until an account API key has been created. Use the **ID token**, not Cognito's access token, for these requests. Identity is derived from verified credentials; never submit an account ID or email to choose which account a request operates on. Account files, credits, and keys are scoped to that credential. An API key does not bypass room membership.

## Signup

You need an email inbox you can read and a password with at least eight characters, including uppercase, lowercase, a number, and a symbol. Email confirmation remains required for agents. A wallet is not a substitute for verified identity.

Cognito operations are HTTPS POSTs with `Content-Type: application/x-amz-json-1.1` and the operation-specific `X-Amz-Target` below. These calls use the public client ID and no Amnetic authorization header.

### Create and confirm the identity

```http
POST https://cognito-idp.us-west-2.amazonaws.com/
Content-Type: application/x-amz-json-1.1
X-Amz-Target: AWSCognitoIdentityProviderService.SignUp

{"ClientId":"4a0rroko7aumoehtl85k6vt9rv","Username":"agent@example.com","Password":"YOUR_STRONG_PASSWORD","UserAttributes":[{"Name":"email","Value":"agent@example.com"}]}
```

The response contains `UserSub`, `UserConfirmed`, and confirmation delivery details. Read the email code, then:

```http
POST https://cognito-idp.us-west-2.amazonaws.com/
Content-Type: application/x-amz-json-1.1
X-Amz-Target: AWSCognitoIdentityProviderService.ConfirmSignUp

{"ClientId":"4a0rroko7aumoehtl85k6vt9rv","Username":"agent@example.com","ConfirmationCode":"CODE_FROM_EMAIL"}
```

An empty success response confirms signup. To request another code, use `AWSCognitoIdentityProviderService.ResendConfirmationCode` with `{"ClientId":"4a0rroko7aumoehtl85k6vt9rv","Username":"agent@example.com"}`. Do not log passwords or confirmation codes.

### Authenticate and renew

```http
POST https://cognito-idp.us-west-2.amazonaws.com/
Content-Type: application/x-amz-json-1.1
X-Amz-Target: AWSCognitoIdentityProviderService.InitiateAuth

{"AuthFlow":"USER_PASSWORD_AUTH","ClientId":"4a0rroko7aumoehtl85k6vt9rv","AuthParameters":{"USERNAME":"agent@example.com","PASSWORD":"YOUR_STRONG_PASSWORD"}}
```

On success, `AuthenticationResult` contains `IdToken`, `AccessToken`, `RefreshToken`, `ExpiresIn`, and `TokenType`. If Cognito returns a challenge instead, complete that challenge before calling Amnetic. Renew with the same operation and `{"AuthFlow":"REFRESH_TOKEN_AUTH","ClientId":"4a0rroko7aumoehtl85k6vt9rv","AuthParameters":{"REFRESH_TOKEN":"YOUR_REFRESH_TOKEN"}}`. Store tokens in a credential store.

### Provision the account and accept Terms

```http
POST /api/v1/accounts
Authorization: Bearer YOUR_ID_TOKEN
Content-Type: application/json

{"name":"Research agent","tos_version":"2026-09-24"}
```

`201` means a new account; `200` means it already exists. `name` is optional. `tos_version` is the provisioning field; it does **not** replace server-recorded Terms acceptance. The account response includes its account representation and a server-selected `portal` navigation projection, not a feature catalogue.

1. `GET /api/v1/account/tos-status` returns `current_version`, `accepted`, and `instrument_url` (and acceptance timestamp when applicable).
2. Read the Terms at `instrument_url`. Only proceed with the account operator's authorization to accept them.
3. `POST /api/v1/account/tos-acceptances` with `{"expected_version":"VERSION_FROM_STATUS"}` records acceptance. `201` is new acceptance; `200` is an existing acceptance. `412 tos_version_changed` means reread the current instrument before accepting again.

Provisioning attaches any active access grants addressed to this identity's verified email. There is no invitation token to exchange or acceptance API to call.

### Create an account API key

`POST /api/v1/accounts/api-keys` with `{"name":"hermes-cleanrooms"}` returns `201` and `{"id":"KEY_ID","plaintext":"SECRET","prefix":"PREFIX","name":"hermes-cleanrooms"}`. Save `plaintext` once; it is not returned again. Use `Authorization: Bearer SECRET` for supported account REST and MCP operations. List keys with `GET /api/v1/accounts/api-keys` (`{"keys":[...]}`); revoke with `DELETE /api/v1/accounts/api-keys/KEY_ID`.

Use an ordinary account key, not a scoped evaluator credential. Bootstrap and accepting Terms can always be completed with a fresh Cognito ID token. Never supply credentials inside an analysis request or file.

## REST contract

All paths below are relative to `https://market.amnetic.ai`. Authenticated requests require the bearer credential described above. Send `Content-Type: application/json` for JSON bodies, or a file's actual media type for raw file writes. UUIDs and timestamps are strings; JSON numbers representing money are integers unless explicitly formatted otherwise. Do not mix cents, microdollars, and decimal USD.

### Rooms and access

| Method and path | Input | Output and authority |
|---|---|---|
| `POST /api/v1/clean-rooms` | `{"title":"Research room"}` | Created room; caller owns it |
| `GET /api/v1/clean-rooms` | None | Accessible owner/member room projections |
| `GET /api/v1/clean-rooms/{id}` | None | Role-specific room projection; source metadata and roster only for owner |
| `POST /api/v1/clean-rooms/{id}/close` | None | Close own room to future work |
| `POST /api/v1/clean-rooms/{id}/members` | `{"email":"member@example.com"}` | Owner grants access; existing verified account active, otherwise pending |
| `DELETE /api/v1/clean-rooms/{id}/members/{grant_id}` | None | Owner revokes that grant |

Room creation/detail returns an object shaped as `{"room":{"id":"ROOM_ID","title":"Research room","status":"open","opened_at":"TIMESTAMP"},"role":"owner","member_count":0,"data":{},"access_grants":[]}`. The role-specific `data` projection carries metadata, never member-readable source bytes. Listing wraps projections in `{"data":[...]}`. A grant returns `201` with `id`, `room_id`, `email`, optional `member_id`, `state`, and `created_at`; use its `id` in revocation. Close and revoke return updated room projections.

Keep the returned room ID and access grant ID. A room projection includes its role, title, status, and member count where permitted. Owner detail includes access grants and notification delivery information. A notification email opens the room and prompts sign-in as needed; receiving or opening it does not confer authority. Pending means the account has not been associated yet, not awaiting invitation acceptance.

An owner controls data and human approval decisions. A member can request analysis and read its own released reports, not the owner's files or other members' identities. Being an owner does not imply a separate member account is provisioned for run submission.

## Files

Paths are absolute within their scope, case-sensitive, and use forward slashes, for example `/research/brief.md`. URL-encode each path segment when constructing REST URLs; keep `/` as the separator. The root listing path is `/`. No separate folder objects are needed: folder entries are derived from stored file paths. Reject `..`, backslashes, control characters, duplicate slashes, and empty file paths.

| Operation | Personal scope | Room scope |
|---|---|---|
| List folder | `GET /api/v1/files?path=/research` | `GET /api/v1/clean-rooms/{id}/files?path=/` |
| Read file | `GET /api/v1/files/research/brief.md` | `GET /api/v1/clean-rooms/{id}/files/source/data.csv` |
| Add/replace | `PUT /api/v1/files/research/brief.md` | `PUT /api/v1/clean-rooms/{id}/files/source/data.csv` |
| Remove | `DELETE /api/v1/files/research/brief.md` | `DELETE /api/v1/clean-rooms/{id}/files/source/data.csv` |

Listing returns `{"entries":[{"path":"/research/brief.md","type":"file","size_bytes":123,"updated_at":"2026-10-01T12:00:00Z"}]}`. `type` is `file` or `directory`. `PUT` sends **raw file bytes**, not multipart or JSON; a successful `200` returns file metadata after scanning/preparation. `GET` returns raw bytes with their content type. `DELETE` returns `204`. A failed replacement preserves the prior file.

Personal files are nonempty and limited to 25 MiB each. A run can select up to 20 personal files totaling 100 MiB. Room writes use the owner-only scanned preparation pipeline; members cannot list or read owner source bytes. Room MCP inline transfers retain smaller limits than REST; see the tool table.

A run selects personal paths through `files`, not attachment IDs. Those paths resolve to immutable file versions when the run is admitted. Replacing or deleting their current heads later cannot change the run. Room source inputs are also frozen. Only the selected personal-file versions are supplied to the evaluator. The owner question review includes their frozen metadata, not a personal-file byte download; the rest of the account filesystem remains private.

There is no public `evaluation-attachments` API in this contract. The evaluator mounts only explicitly selected, frozen supporting files read-only, with `/research/brief.md` becoming `/inputs/research/brief.md`. The files and parent directories prevent modification, deletion, or permission changes by the evaluator. Unselected account files are absent. Protected room source data retains its separate access boundary.

## Funding

`GET /api/v1/accounts/credits` returns `balance_cents`, `evaluation_reserved_micro`, `withdrawable_balance_cents`, and `recent_entries`. The spendable balance already excludes reservations. `balance_cents` is integer USD cents; `evaluation_reserved_micro` and run quote `expected_charge` use integer micro-USD (1 USD = 1,000,000 micro-USD). Purchased credits are spend-only, not withdrawable funds.

Human browser checkout remains available through `POST /api/v1/accounts/credits/topup` with `{"amount_cents":2500}`. It returns `checkout_url` and `session_id`; confirmed payment credits the account asynchronously. Do not infer crediting from opening or returning from checkout.

### Machine payments

Funding amount bounds default to 500–100,000 cents ($5–$1,000), with the server authoritative. A funding idempotency key is nonempty and at most 128 characters.

MPP requires a configured merchant and an authorized payment client. A funded wallet can permit unattended payment; a card route requiring human approval cannot. Merchant/payment-method availability must be verified before relying on machine funding in production.

```http
POST /api/v1/account/funding
Authorization: Bearer ACCOUNT_CREDENTIAL
Idempotency-Key: ONE_UNIQUE_KEY_FOR_THIS_FUNDING_REQUEST
Content-Type: application/json

{"amount_cents":2500}
```

Pending funding returns **402**, `WWW-Authenticate: Payment ...`, and a body containing `id`, `amount_cents`, `currency`, `status`, `created_at`, `expires_at`, `payment_url`, and `payment_challenge`. The returned `payment_url` is an API path relative to the REST origin. Have your payment client process the challenge. Keep the account bearer credential in `Authorization`; submit the resulting payment credential separately:

```http
POST /api/v1/account/funding/FUNDING_ID
Authorization: Bearer ACCOUNT_CREDENTIAL
Payment-Authorization: Payment WALLET_GENERATED_CREDENTIAL
```

Use the exact credential produced for this challenge. Never send a wallet private key, seed phrase, or raw card details. `GET /api/v1/account/funding/FUNDING_ID` checks and reconciles status; it can return `402` while still pending. Confirm `status == "funded"` and `GET /api/v1/accounts/credits` before starting a run. Provider confirmation and duplicate handling precede ledger crediting.

Retry the same amount with the same `Idempotency-Key`; a different amount under that key returns `409 funding_conflict`. Other errors include `400 invalid_funding_request`, `400 payment_verification_failed`, `404 not_found`, and `410 payment_challenge_expired`. An expired challenge requires a new intent; inspect the prior payment before paying again.

The [Hermes payment skill](https://github.com/NousResearch/hermes-agent/blob/main/website/docs/user-guide/skills/optional/payments/payments-mpp-agent.md) and [Stripe MPP documentation](https://docs.stripe.com/payments/machine/mpp) describe payment-client setup. All Amnetic request fields needed for funding are specified here.

## Runs

### Quote before submitting

Call `GET /api/v1/machine-sizes` to discover the accepted machine identifiers; do not invent one. Request `GET /api/v1/clean-rooms/ROOM_ID/run-price?machine_size=MACHINE&max_runtime_minutes=MINUTES` as a member with access.

A quote includes `currency`, `price_schedule_version`, `machine_size`, `max_runtime_minutes`, `expected_charge`, `flat_fee_micro`, `usage_cap_micro`, `tier`, `estimated_egress_usd`, `run_fee`, `usage`, `reserved_now`, `run_terms_url`, and `accept_run_terms_required`. Preserve numeric fields exactly. Review the instrument at `run_terms_url` before authorizing a run.

### Start analysis

```http
POST /api/v1/clean-rooms/ROOM_ID/runs
Authorization: Bearer ACCOUNT_CREDENTIAL
Content-Type: application/json

{"request_text":"Analyze the room data against the attached scenarios. State assumptions and produce a concise report.","files":["/research/brief.md","/research/scenarios.csv"],"machine_size":"MACHINE_FROM_QUOTE","max_runtime_minutes":30,"expected_charge":123456,"price_schedule_version":"VERSION_FROM_QUOTE","accept_run_terms":true}
```

The machine, minutes, charge, and version **must come from the current quote**, not the illustrative numbers above. `files` is optional. Request text is bounded by 512 KiB of UTF-8; the JSON transport allows escaping overhead. Input transport has been verified with a 64,096-token provider-counted request; a completed live model analysis at that size has not been verified. The selected model must also fit room data, system instructions, tools, and output.

Save the created run's `id`. Run creation does not have the funding endpoint's idempotency contract: after a timeout, inspect your run list before issuing another POST. A second POST can create another request and reservation.

### Status and released analysis

| Method and path | Purpose |
|---|---|
| `GET /api/v1/clean-rooms/{id}/runs` | List your runs |
| `GET /api/v1/clean-rooms/{id}/runs/{run_id}` | Read your run, status, billing, and released report |
| `GET /api/v1/clean-rooms/{id}/history` | Read permitted room history |
| `GET /api/v1/clean-rooms/{id}/decisions` | Owner's pending question/report review projections |
| `POST /api/v1/clean-rooms/{id}/runs/{run_id}/decision` | Owner's human question/release decision |

Member run fields: `id`, `room_id`, `request_text`, `request_sha256`, `attachments` (frozen selected file metadata), `status`, `stage`, `created_at`, `request_expires_at`, `machine_size`, `max_runtime_seconds`, `events`; optional `failure_code`, `owner_message`, `release_pending`, `next_deadline`, `report`, `receipt`, `cost`, and `billing`. A released `report` contains `text` and `sha256`. Raw reports are unavailable to members before release. Do not treat `report_ready` as permission to read protected report bytes.

Progress states are `requested`, `analysis_approved`, `running`, and `report_ready`. Success is `released`. Other terminal states are `denied_at_request`, `no_response_at_request`, `cancelled`, `listing_unavailable`, `listing_changed`, `failed`, `denied_at_release`, and `no_response_at_release`; some retained wire names use `listing` for the underlying protected source snapshot. Treat unknown states conservatively and inspect the response instead of assuming success.

Run lists, history, and decision lists use `{"data":[...]}`. Owner decision entries contain `run_id`, `member_email`, `gate`, `attachments`, applicable `request_text`/`request_sha256` or `report_text`/`report_sha256`, `pinned_content_sha256`, `screen_flags`, `released_bytes_this_handle`, `released_bytes_listing`, and optional `report_diff`. Frozen attachment metadata contains `attachment_id`, `filename`, `size_bytes`, and `sha256`; these are output metadata, not input IDs for run creation.

Poll at a modest interval with backoff and stop on terminal states. Waiting for human approval is expected; it is not an error to fix by resubmitting. The owner may review the frozen question/files, then the generated report and review aids.

An owner decision body is `{"gate":"question","decision":"approve","echoed_sha256":"REQUEST_HASH_FROM_DECISION","message":"Optional explanation"}` or `{"gate":"release","decision":"approve","echoed_sha256":"REPORT_HASH_FROM_DECISION"}`. Use `deny` to decline a gate. Echo the exact current hash: it prevents approving content that changed since review. Successful decisions return `{"id":"RUN_ID","room_id":"ROOM_ID","gate":"question","decision":"approve","release_pending":false}`. Owner decisions are human REST/portal actions, not exposed as an MCP approval tool.

## Other account operations

These shared account endpoints are also visible to cleanroom accounts:

| API | Request and response |
|---|---|
| `GET /api/v1/account/profile` | Own `account_id`, optional `business_description`, `website`, `display_name`, and `is_test` |
| `PUT /api/v1/account/profile` | `{"business_description":"Research","website":"https://example.com","display_name":"Agent"}`; own updated profile |
| `GET /api/v1/profile` | Shared Terms vocabulary: `profile`, `profile_sha256`, `terms`, `ordering`, `closure_rules`, `context`, `scenarios`, versions, and `instruments` |
| `PATCH /api/v1/accounts/api-keys/{id}` | Optional `publish_authority` and `spending_authority`; update own key's supported authorities |

Terms status/acceptance, API-key create/list/revoke, balance, browser checkout, machine funding, and machine sizes are specified above. These are account prerequisites, not additional product workflows. Availability is server-enforced; client navigation does not grant authority.

## MCP

Connect an HTTP MCP client to `https://market.amnetic.ai/mcp`. Use browser OAuth if the client supports it, or send an ordinary account API key as the bearer header. A generic MCP initialization uses JSON-RPC `initialize`, then `notifications/initialized`; discover the available schema with `tools/list` and invoke with `tools/call`. For example, send this initialization body over HTTP POST with `Authorization: Bearer YOUR_ACCOUNT_KEY`, `Content-Type: application/json`, and `Accept: application/json, text/event-stream`:

```json
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"cleanroom-client","version":"1.0"}}}
```

Read the negotiated `result.protocolVersion` and any `Mcp-Session-Id` response header. On subsequent POSTs send that `MCP-Protocol-Version` and session ID, the same bearer and content/accept headers, first with `{"jsonrpc":"2.0","method":"notifications/initialized"}`, then `{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}`. Honor JSON or event-stream responses. Do not hardcode tool discovery as authorization.

```json
{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"list_clean_rooms","arguments":{}}}
```

The complete cleanroom/account tool set is below. Required values are shown unless marked optional. Outputs follow the corresponding REST projection; MCP content is returned in the SDK's tool-result envelope and may include `structuredContent`. Check `isError` before consuming it.

| Tool | Arguments |
|---|---|
| `create_clean_room` | `title` |
| `list_clean_rooms` | `{}` |
| `get_clean_room` | `room_id` |
| `grant_clean_room_access` | `room_id`, `email` |
| `list_clean_room_decisions` | `room_id`; read-only, owner |
| `list_clean_room_files` | `room_id`, `path`; owner |
| `read_clean_room_file` | `room_id`, `path`; owner, base64 output up to 5 MiB |
| `write_clean_room_file` | `room_id`, `path`, `content_base64`; owner, at most 5 MiB encoded, use REST for larger writes |
| `delete_clean_room_file` | `room_id`, `path`; owner |
| `list_files` | `path` |
| `read_file` | `path`; output `path`, `content_type`, `content_base64` |
| `write_file` | `path`, `content_base64`, optional `content_type`; at most 25 MiB decoded |
| `delete_file` | `path`; output `{"deleted":true}` |
| `quote_clean_room_run` | `room_id`, `machine_size`, `max_runtime_minutes` |
| `start_clean_room_run` | `room_id` plus every field in the REST run creation body |
| `get_clean_room_run` | `room_id`, `run_id` |
| `get_account_credits` | `{}` |
| `create_account_funding` | `amount_cents`, `idempotency_key` |
| `get_account_funding` | `funding_id` |
| `pay_account_funding` | `funding_id`, `payment_credential` |
| `get_my_profile` | `{}` |
| `update_my_profile` | `business_description`, `website`, `display_name`; empty strings clear fields |
| `get_terms_vocabulary` | `{}` |

MCP does not provide account signup, human approvals, access revocation, room closure, or a complete run-list/history replacement; use the REST operations above for these. Scoped evaluator tools such as task/report submission are runtime-only and are not part of this integration flow.

## Errors and retries

| Response | Handling |
|---|---|
| `413 request_text_too_large` | Text exceeds `max_bytes:524288`; reduce it, never truncate silently |
| `400` / `422` | Fix the invalid field, path, size, or missing run consent; do not retry unchanged |
| `401` | Renew authentication; use a Cognito ID token or supported account key |
| `402` funding | Expected MPP payment challenge; process it with the payment client |
| `402 insufficient_credit` | Check balance/reservations and fund before resubmitting |
| `403` | Account not provisioned, scope, Terms, or authority failure; inspect error code |
| `404` | Resource absent or inaccessible; do not probe other identities |
| `409` | Idempotency/content/state conflict; reread current state before retrying |
| `412 tos_version_changed` | Read and accept the current Terms instrument |
| `429` | Back off; honor `Retry-After` when supplied |
| `5xx` / transport timeout | Back off; reconcile state before repeating money or run creation |

Error responses may carry `error`, `code`, and `message`; preserve the received code for diagnostics without logging credentials or source data. `409 price_stale` returns fresh `expected_charge` and `price_schedule_version`; fetch a complete fresh quote and authorize it. `409 evaluation_decision_stale` returns current content hashes and status; rereview content before deciding. Do not treat retries as permission to accept new Terms or prices automatically.

## Complete integration sequence

1. Call Cognito `SignUp`, read the email code, `ConfirmSignUp`, and `InitiateAuth` exactly as shown above.
2. Use `AuthenticationResult.IdToken` to `POST /api/v1/accounts`, read `/account/tos-status`, review its instrument, and post `/account/tos-acceptances` with that version.
3. Create and securely save an account API key; connect MCP or keep using REST.
4. Create a room as owner, add a room source file through raw `PUT`, and grant access to the requesting member's verified email. The member repeats steps 1–3 for its own account if needed.
5. As the member, write `/research/brief.md` through `PUT /api/v1/files/research/brief.md`. Do not write protected room data through a member credential.
6. Read credits. If insufficient, create one funding intent, pay its returned challenge with the authorized client, reconcile that same intent, and confirm the credited balance. Do not proceed while pending.
7. List machine sizes, obtain the room's current run quote, review its Terms, and submit one run using that quote plus `files:["/research/brief.md"]`.
8. As the human owner, read `/decisions` and approve the question with the current request hash. The member polls its saved run ID.
9. When the report is ready, the human owner reviews it and approves release with the current report hash.
10. The member reads `GET /api/v1/clean-rooms/ROOM_ID/runs/RUN_ID`, checks `status == "released"`, and consumes `report.text`. Keep `report.sha256` and the receipt if needed. Stop polling on success or another terminal status.

The same verified account can run an agent integration; the owner/member approval boundaries still apply. This sequence requires a working evaluator runtime and human approval, not merely successful HTTP requests.
