# What Makes an API Safe for AI Agents: Idempotency, Dry Runs, Fixable Errors, and Human Claim

> Four mechanisms make an API safe for AI agents: idempotency keys, dry runs, errors that name the fix, and human claim. Exact codes from the WarpLink API.

_Last updated: 2026-10-21_

> **TL;DR:** An agent retries, guesses, and acts without a person watching. An agent-ready API gives it four things: safe retries (idempotency), a preview (dry run), errors that name the fix, and a way to start work that a person then claims. This post shows how WarpLink implements each, with the exact codes.

WarpLink is link infrastructure for mobile teams: deep linking, install attribution, and real-time analytics in one SDK. Its [MCP server](https://warplink.app/docs/ai-agents) lets a coding agent register an app, create links, and verify a setup. An agent works differently from a person at a keyboard. It retries on a timeout, it tries a call to see what happens, and it cannot read a screenshot of an error. The mechanisms below are how we designed for that. Every behavior listed is in the live API docs.

## Safe retries: idempotency keys

A retry of a create call must not make a second resource. Send an `Idempotency-Key` header on `POST /v1/links` or `POST /v1/apps`, or pass `idempotency_key` to the MCP tools `create_link` and `create_app`. The key is 1 to 255 printable ASCII characters you choose, such as a UUID.

| Situation | Response |
|---|---|
| Same key, same request | The first response, with its original status, and the header `Idempotent-Replayed: true`. MCP results carry `_meta["warplink/idempotent_replayed"]: true`. |
| Same key, different request | `422 IDEMPOTENCY_KEY_REUSED` |
| Retry while the first call still runs | `409 IDEMPOTENCY_IN_PROGRESS`. Wait a second and retry with the same key. |
| First response was a `5xx` or `429` | Not remembered, so the retry runs again. |

Keys are remembered for 24 hours, scoped to your organization, the API key, and the endpoint. The full rules are in [the API reference](https://warplink.app/docs/api#safe-retries-and-dry-runs).

## Dry runs: see the result before it exists

An agent should be able to ask "what would happen" without saving anything. Add `?dry_run=true` to `POST /v1/links`, `POST /v1/apps`, and `PATCH /v1/links/{id}`, or pass `dry_run: true` to `create_link`, `create_app`, and `update_link`.

WarpLink runs every check a real call runs: authentication, role, validation, plan limits, link safety, and the slug, handle, and duplicate checks. The answer is `200` with the result the call would have returned:

```json
{ "dry_run": true, "result": { "...": "what the real call would return" } }
```

Nothing is saved, no audit entry is written, and no idempotency key is stored. An agent previews, checks the result with the person if the change matters, then repeats the call without `dry_run`.

## Errors that say how to fix them

The error shape did not change: `{ "error": { "code", "message" } }`. When we know the remedy, the error gains `details.fix` and `details.docs_url`:

```json
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid request body",
    "details": {
      "fields": [{ "field": "destination_url", "message": "Invalid URL" }],
      "fix": "Fix each field listed in `details.fields` and send the request again.",
      "docs_url": "https://warplink.app/docs/errors#validation_error"
    }
  }
}
```

The MCP server puts `Fix: ...` in the error text and returns the same fields in `structuredContent.error`, so a client that reads text and a client that reads structure both get the remedy. Every code in the [errors reference](https://warplink.app/docs/errors) carries a fix, and the table on that page reads the same catalog that fills the response. An agent applies the fix, then retries. This is the cheapest mechanism on the list and it removes most guessing.

## Start now, a person claims later

An agent often starts before its user has an account. `POST /v1/agent/signup` needs no credential. The agent sends `email` and `agent_name`, and WarpLink creates a Free workspace, emails the claim link to the person (never to the agent), and returns an MCP access token. The response never contains the claim link, so the agent cannot claim the workspace itself.

| Until the person claims the workspace | Behavior |
|---|---|
| Apps and links | 1 app, 10 links |
| Link state | Every link stays paused. `activate_link` and `update_link` with `is_active: true` return `CLAIM_REQUIRED`. |
| Keys and domains | App-tied SDK keys only. API keys and custom domains return `CLAIM_REQUIRED`. |
| Token | Works on the MCP server only, not on the REST API. |
| Lifetime | The workspace is removed after 7 days, with one reminder email about 2 days before. |
| Rate limits | 3 workspaces an hour and 10 a day per client address, and one claim email a day per email address. |

The design is simple to state: the agent can build, and only a person can make links live. At the claim, the workspace becomes the person's, its links turn on, and the agent's connection moves into **Settings** > **Connected AI**, where the person can revoke it. The endpoint is documented in [Agent sign-up](https://warplink.app/docs/api/agent-signup).

## Setup that verifies itself

An agent needs to know when it is done and when to stop. `verify_setup` returns a list of checks, each with a status of `pass`, `fail`, `warn`, `needs_human`, or `skipped`, and a `fix` on each check that needs one. The agent applies each fix and calls the tool again. When only `needs_human` checks remain, it stops and reports them: a device tap, the first click, the SDK connecting from a real device, or claiming the workspace. The agent knows exactly which steps are the person's. The walkthrough is in [set up deep linking with Claude Code](https://warplink.app/blog/set-up-deep-linking-with-claude-code).

## Keys an agent can hold

Credentials need the same care. An agent connected over OAuth can create an SDK key tied to one app with `create_key`. The key has the fixed SDK scopes, works only on the SDK routes, and never expires. An API key made by an assistant never gets `keys:read` or `keys:write` scopes and lives at most 90 days. `list_keys` and `GET /v1/keys` show `created_via`, so the person can see which keys an agent made. The MCP server refuses SDK keys with `403 SDK_KEY_ROUTE_FORBIDDEN`. See [Authentication](https://warplink.app/docs/authentication) for scopes and [rate limiting](https://warplink.app/docs/rate-limiting) for the 60 requests a minute per credential.

## Docs agents can read

An agent that cannot read your docs guesses. Every docs page is also available as Markdown at `/docs/<page>.md`, and every blog post at `/blog/<slug>.md`. The site serves [llms.txt](https://warplink.app/llms.txt) and `llms-full.txt`. The MCP server offers resources such as `warplink://docs/errors` and `warplink://docs/setup-guide`, which a client can attach as context.

## The checklist

| Mechanism | Why an agent needs it | WarpLink endpoint or tool |
|---|---|---|
| Idempotency keys | A retry must not create a second resource | `Idempotency-Key` on `POST /v1/links` and `POST /v1/apps`, `idempotency_key` on `create_link` and `create_app` |
| Dry run | Preview the result before it is saved | `?dry_run=true` on three REST calls, `dry_run` on `create_link`, `create_app`, `update_link` |
| Fixable errors | Apply the remedy instead of guessing | `details.fix` and `details.docs_url`, `Fix:` in MCP text |
| Human claim | The agent builds, a person makes it live | `POST /v1/agent/signup`, `CLAIM_REQUIRED` |
| Self-verifying setup | Know when to stop and what is left | `verify_setup`, `GET /v1/apps/{id}/setup-check` |
| Scoped keys | A leaked key reaches little | App-tied SDK keys, assistant API keys without `keys:*`, `created_via` |
| Readable docs | Read the rule instead of guessing it | `/docs/<page>.md`, `/blog/<slug>.md`, llms.txt, MCP resources |

## Frequently Asked Questions

### What makes an API agent-ready?

An agent-ready API gives an AI agent four things: safe retries through idempotency keys, a preview through dry runs, errors that name the fix, and a way to start work that a person then claims. WarpLink implements all four on the REST API and the MCP server, and adds setup checks that tell the agent when to stop.

### Why do AI agents need idempotency keys?

An agent retries when a connection drops or a response is slow, and a plain retry of a create call makes a second resource. With an idempotency key, a repeat of the same request returns the first response instead. WarpLink accepts an `Idempotency-Key` header on `POST /v1/links` and `POST /v1/apps`, and an `idempotency_key` argument on the `create_link` and `create_app` MCP tools. Keys last 24 hours.

### What does a dry run return in the WarpLink API?

A dry run returns `200` with the result the call would have returned, wrapped as `{ "dry_run": true, "result": ... }`, and saves nothing. WarpLink runs every check a real call runs: authentication, role, validation, plan limits, link safety, and the slug, handle, and duplicate checks. Add `?dry_run=true` to `POST /v1/links`, `POST /v1/apps`, and `PATCH /v1/links/{id}`, or pass `dry_run: true` to `create_link`, `create_app`, and `update_link`.

### How can an AI agent sign up for WarpLink without the person's password?

The agent calls `POST /v1/agent/signup` with the person's email address and the agent's name. WarpLink creates a Free workspace, emails the claim link to the person and never to the agent, and returns an MCP access token. The person opens the email, signs in or creates an account, and chooses **Claim workspace**. The agent never sees a password.

### What can an unclaimed workspace do?

An unclaimed workspace holds 1 app and 10 links, and every link stays paused. Activating a link, creating an API key, or adding a custom domain returns `CLAIM_REQUIRED`. The agent can still create app-tied SDK keys. The workspace is removed after 7 days, with one reminder email about 2 days before.

### How does an agent know how to fix a failed API call?

Every error keeps the shape `{ error: { code, message } }` and, when WarpLink knows the remedy, adds `details.fix` and `details.docs_url`. The MCP server puts `Fix:` followed by the same text in the error message and returns the fields in `structuredContent.error`. The agent applies the fix before it retries.

## Related Guides

- [WarpLink for AI agents](https://warplink.app/ai)
- [Set up deep linking with Claude Code](https://warplink.app/blog/set-up-deep-linking-with-claude-code)
- [AI agents guide](https://warplink.app/docs/ai-agents)
- [Agent sign-up API](https://warplink.app/docs/api/agent-signup)
- [Errors reference](https://warplink.app/docs/errors)

[Create a free account](https://dash.warplink.app/auth/signup) and connect your agent.
