Skip to content

Add idempotency_key auto-generation and IdempotencyConflictError #181

@bokelley

Description

@bokelley

Tracking issue for the client-side ergonomics of AdCP #2308 — idempotency_key is now required on every mutating request in the spec.

Client-side changes

1. Auto-generate idempotency_key when caller omits it

Every mutating tool call should generate a UUID v4 if the caller didn't pass one:

# Caller doesn't think about it — SDK handles it
result = client.create_media_buy(account=..., brand=..., start_time=..., end_time=...)

# Caller passes their own (e.g., restored from DB) — SDK respects it
result = client.create_media_buy(idempotency_key=stored_key, ...)

The list of mutating tasks is in the spec — 26 total across media_buy, creative, signals, brand, governance, accounts, SI.

2. Retry-safety: reuse the same key

When the SDK retries internally (timeout, 5xx, network error), it MUST reuse the same key across retries. Re-generating a key on retry defeats the whole point.

3. Surface the key for external correlation

The key should be observable on both the sent request and the returned response so callers can log it, store it alongside their own objects, or correlate across retries:

result = client.create_media_buy(...)
print(result.idempotency_key, result.media_buy_id)

4. Typed IdempotencyConflictError

Map the new IDEMPOTENCY_CONFLICT error code to a dedicated exception class:

from adcp_client import IdempotencyConflictError

try:
    client.create_media_buy(idempotency_key=reused_key, ...different_payload)
except IdempotencyConflictError:
    # Caller reused a key by mistake — use a fresh one
    ...

5. Optional: use_idempotency_key(key) context manager for bring-your-own-key

For buyers who persist keys across process restarts (e.g., store key in their DB alongside a campaign row):

with client.use_idempotency_key(campaign.idempotency_key):
    client.create_media_buy(...)

Scope notes

  • SI send_message needs the key too but lives in session scope — SDK should auto-generate per turn.
  • si_terminate_session is naturally idempotent via session_id — SDK can continue to omit idempotency_key.

Acceptance

  • All mutating calls auto-generate idempotency_key when not provided
  • Internal retries reuse the same key
  • idempotency_key surfaced on the sent request for logging
  • IdempotencyConflictError exported as a typed exception
  • Docs/README updated with the "retry safety for free" story
  • Integration test against the idempotency compliance storyboard

Related: adcontextprotocol/adcp#2308, adcontextprotocol/adcp#2315

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions