# Idempotency in the API

There is no `Idempotency-Key` header on any Connect interface, and no key lifetime to plan around. Every published operation is a read, so repeating one has no effect beyond spending an allowance. The interesting question is not safety but sameness — whether a repeated call returns the same answer.

- **Status:** Reference
- **Audience:** developer
- **Last verified:** 2026-09-10
- **Canonical:** https://connectbyjbrh.com/developers/api-idempotency/

## What an idempotency key is for, and why there is none

A key exists so that a client which cannot tell whether its request arrived can send it again without creating a second charge, a second message or a second record. It matters exactly where a request has an effect. Everything Connect publishes is a `GET` over generated documentation, or a JSON-RPC read; there is no public write endpoint, no record to duplicate and nothing to charge. A key would be ceremony around an operation that already cannot go wrong twice.

So no header is accepted. Sending `Idempotency-Key` is harmless and is ignored, like any other unrecognised header, and there is no store of keys, no replay window and no conflict response to handle.

## Does Connect use this?

**Public HTTP API** — No key. All five endpoints are `GET`; HTTP already defines them as safe and idempotent.
**MCP tools** — No key. Every tool declares `idempotentHint: true` and `readOnlyHint: true` in its annotations, which is a hint to a client rather than a contract, but here it happens to be exact.
**A2A skills** — No key. A skill has no side effect, though see the caveat below about identifiers.
**Inside the product** — Retried provider callbacks are a real problem that Connect has to solve, and it is solved inside the product rather than through a client-supplied key. [Idempotency for retried telephony webhooks](/research/idempotent-telephony-webhooks/) is the engineering note on that; nothing about it is exposed on a public interface.

## Where sameness actually stops

*Safe* and *identical* are different promises, and only the first is unconditional here. Two calls a second apart return the same page; two calls either side of a deploy may not, because the corpus is regenerated and a page's prose, summary or status can change. The `updated` date on every page is how you detect that, and it is the right cache key.

Search is the other place a repeat can differ. Results are ranked, and ranking depends on the index — which rebuilds when the manifest's modification time changes. Ordering is deterministic for a given index and query, with ties broken by path so it never wobbles for equal scores, but a new deploy can reorder results. Pin to a `url` or an `id` when you need the same page tomorrow, never to a position in a list.

> **Note** An A2A reply is not identical even when its content is: `messageId` is freshly generated on every call, and `contextId` too unless you send one. Deduplicate on your own request key rather than on the reply's identifiers.

## Retrying safely

1. Retry `429` and `5xx` freely — there is no double-effect risk to weigh against it.
   - Result: The only cost of an extra attempt is the allowance it spends.
2. Do not retry `404` or a validation failure.
   - Result: The request was wrong; the same request will be wrong again.
3. Key your own cache on `url` plus `updated`, not on the response body.
   - Result: You get correct invalidation across a deploy without comparing prose.
4. If you need a result set to stay stable across a job, snapshot it.
   - Result: Re-running a search mid-job can reorder it; a snapshot cannot.

## Questions

### Will an `Idempotency-Key` header break anything?

No. It is not read and not rejected. Nothing stores it, so nothing can go stale or conflict.

### If a write API ever exists, will this page describe it?

This page describes what is published now, which is read-only. It makes no claim about anything else, and neither should a client built from it.

### Is a repeated MCP `tools/call` guaranteed to return byte-identical JSON?

Within one deployment, in practice yes for a fetch by id or path. Across deployments, no — the corpus is regenerated. Compare `updated`, not bytes.

## Related

- [The public API](https://connectbyjbrh.com/developers/public-api/)
- [Pagination](https://connectbyjbrh.com/developers/api-pagination/)
- [Designing a resilient client](https://connectbyjbrh.com/developers/errors-and-retries/)
- [Stable identifiers](https://connectbyjbrh.com/developers/entity-ids/)
- [Idempotency for retried telephony webhooks](https://connectbyjbrh.com/research/idempotent-telephony-webhooks/)

## What this page is based on

- `backend/app/public_developer_api.py` — every route is a GET
- `backend/app/mcp_server.py` — tool annotations
- `backend/app/public_docs.py` — index rebuild on manifest mtime, tie-break by path
- `backend/app/a2a_server.py` — messageId and contextId generation
