Skip to content

feat: auto-inject deterministic dedup key in api-request.sh for create endpoints - #49

Open
Jinyi-S wants to merge 1 commit into
spotify:mainfrom
Jinyi-S:jinyis/ADS-3000-idempotency-reference
Open

feat: auto-inject deterministic dedup key in api-request.sh for create endpoints#49
Jinyi-S wants to merge 1 commit into
spotify:mainfrom
Jinyi-S:jinyis/ADS-3000-idempotency-reference

Conversation

@Jinyi-S

@Jinyi-S Jinyi-S commented Aug 11, 2026

Copy link
Copy Markdown

Description

Auto-inject a deterministic dedup key in the API request wrapper for the 6 supported create endpoints. The key is a SHA-256 hash of the canonical request fingerprint — same request always produces the same key, so retries are automatically detected by the server. No client-side cache or state.

How it works

  1. Build a fingerprint from the base URL, uppercase method, resolved path, and canonical JSON body
  2. Canonicalize JSON via canonicaljson library (sorts keys, strips whitespace, handles unicode) with json.dumps fallback
  3. Hash the fingerprint with SHA-256 — the hash IS the key
  4. Send in the Idempotency-Key header

Fallback chain

  1. python3 canonical-hash.py — fastest (~50ms), uses canonicaljson if installed, otherwise json.dumps
  2. uv run canonical-hash.py — auto-installs canonicaljson if python3 fails entirely
  3. shasum — raw string hash, last resort

Changes

File Change
scripts/canonical-hash.py NEW — standalone script with uv inline deps for canonical JSON hashing
scripts/api-request.sh Call canonical-hash.py via fallback chain. --no-dedup-key flag for opt-out
skills/api-reference/references/create-retry-safety.md NEW — internal reference for retry safety and error handling
tests/test-scenarios.md 7 new scenarios (36-42) testing deterministic hash, JSON normalization, exclusions, and opt-out

Design decisions

  • No UUID — the key is the SHA-256 hash of the request fingerprint
  • No client-side cache or state — server handles duplicate detection
  • Canonical JSONcanonicaljson library for proper normalization, json.dumps fallback
  • python3 first — ~50ms per call vs ~200-500ms with uv run first
  • exec curl unchanged — no response capture, no buffering change
  • Skills unchanged — zero skill file modifications
  • --no-dedup-key — opt-out for cases like toggling a PATCH field multiple times

Checklist

  • Tested against the Spotify Ads API with Codex, Claude Code, or Antigravity CLI
  • Existing skills still work as expected
  • SKILL.md frontmatter is valid (name, description, allowed-tools)
  • README or CHANGELOG updated (if user-facing change)
  • Curl commands follow API conventions documented in AGENTS.md

@Jinyi-S
Jinyi-S force-pushed the jinyis/ADS-3000-idempotency-reference branch from e89e8fc to 756baab Compare August 12, 2026 00:03

@amurph491 amurph491 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Architecture Review

This PR has the right intent but the wrong architecture. Three significant issues:

1. The -H passthrough won't work with api-request.sh

The api wrapper only accepts positional args: <skill> <METHOD> <path> [json_body] (scripts/api-request.sh lines 108–117). It doesn't parse -H flags. So when a skill does:

api POST "ad_accounts/{ad_account_id}/ad_sets" \
  -H "Idempotency-Key: <uuid>" \
  '{...}'

-H gets slotted into $4 (the BODY variable), and the actual JSON body becomes $5 and is silently ignored. Every create command in this PR would send -H as the request body instead of the JSON payload. This is a breaking change.

2. Header management belongs in the wrapper, not scattered across skills

The whole point of api-request.sh is to centralize header management — Authorization, X-Spotify-Ads-Sdk, X-Spotify-Ads-Skill, and Content-Type are all handled there. Skills just call api POST "path" '{body}' and don't think about headers. This PR breaks that pattern by requiring every skill to manually add a header, creating 6+ places to maintain the same logic.

The correct fix: add automatic key generation to api-request.sh for POST requests on the 6 supported create endpoints. Skills wouldn't change at all — the wrapper detects that the path matches a create endpoint, generates a UUID, and includes the header automatically. Zero skill changes needed.

3. The term "idempotency" must not be user-facing

The term appears in AGENTS.md, 5 skill files, the agent instructions, 9 test scenario titles, and the reference doc. Non-technical users will encounter this term and be confused by it. This protection should be completely invisible — on by default, with no user-facing terminology. Advanced users can opt out only if they specifically ask for it.


Recommended Approach

  1. api-request.sh — detect POST to the 6 create endpoints and auto-inject the key header via uuidgen. Add a flag (e.g. --no-dedup-key) or read a setting so advanced users can disable it if they specifically ask. On by default, no user action needed.

  2. Skills — no changes. They keep calling api POST "path" '{body}' exactly as they do today.

  3. AGENTS.md retry safety — keep the existing guidance about not auto-retrying POST/PATCH. The wrapper handles the header; retry semantics can stay documented in an internal reference doc renamed to something like references/create-retry-safety.md (not "idempotency").

  4. Test scenarios — rewrite to test that the wrapper adds the header automatically (not that each skill manually includes it), and remove technical terminology from scenario names.

@amurph491 amurph491 added this to the v1.9.0 milestone Aug 13, 2026
@Jinyi-S
Jinyi-S force-pushed the jinyis/ADS-3000-idempotency-reference branch from 756baab to dd75ed6 Compare August 17, 2026 00:49
@Jinyi-S Jinyi-S changed the title feat: add idempotency key protocol, skill updates, and test scenarios feat: auto-inject dedup key in api-request.sh for create endpoints Aug 17, 2026
@Jinyi-S
Jinyi-S force-pushed the jinyis/ADS-3000-idempotency-reference branch 2 times, most recently from 973d752 to 92ef3ad Compare August 17, 2026 01:18
@Jinyi-S

Jinyi-S commented Aug 17, 2026

Copy link
Copy Markdown
Author

Thanks for the review @amurph491
I've reworked the PR:

  1. Wrapper handles everything: auto-injects the key in api-request.sh for the 6 create endpoints via uuidgen. Single regex: ^ad_accounts/[^/]+/(drafts/)?(campaigns|ad_sets|ads)$
  2. Skills unchanged
  3. No user-facing terminology: reference doc renamed to create-retry-safety.md, test scenarios use dedup key

Also added --no-dedup-key flag for per-request opt-out.
Thanks!

Comment thread scripts/api-request.sh Outdated
@Jinyi-S
Jinyi-S requested a review from amurph491 August 17, 2026 22:23
@Jinyi-S
Jinyi-S force-pushed the jinyis/ADS-3000-idempotency-reference branch 3 times, most recently from 8e7a8a2 to 2b0411d Compare August 19, 2026 23:01
@Jinyi-S Jinyi-S changed the title feat: auto-inject dedup key in api-request.sh for create endpoints feat: auto-inject deterministic dedup key in api-request.sh for create endpoints Aug 19, 2026
@Jinyi-S
Jinyi-S force-pushed the jinyis/ADS-3000-idempotency-reference branch from 2b0411d to c55bc7e Compare August 20, 2026 05:07
…e endpoints

Generate a SHA-256 hash from the canonical request fingerprint (base URL,
uppercase method, resolved path, canonical JSON body) as the Idempotency-Key.
Uses canonicaljson library via uv for JSON normalization, with json.dumps
and shasum fallbacks. Same request always produces the same key. No
client-side cache or state. Add --no-dedup-key flag for opt-out.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Jinyi-S
Jinyi-S force-pushed the jinyis/ADS-3000-idempotency-reference branch from c55bc7e to a18ad0a Compare August 20, 2026 10:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants