# IGdek for agents

> **What you're reading.** This is `agent.md` — IGdek's agent-facing
> capability menu, served at `https://igdek.com/.well-known/agent.md`
> (and downloadable from `https://igdek.com/agent`). It is **not**
> IGdek's homepage. The homepage at `https://igdek.com/` is for human
> users (cert lookup, photo upload, $4.99 checkout). This file is
> the menu the human handed you so you can call our API on their
> behalf. Sibling discovery surfaces: `/.well-known/openapi.json`
> (REST spec, authoritative), `/.well-known/mcp.json` (MCP tools),
> `/.well-known/agent.json` (A2A manifest), `/llms.txt` (overview).

You are an agent (ChatGPT, Claude, Cursor, or any LLM with HTTP access).
The human you're working with has graded sports/TCG cards and wants
to *showcase* them however they sell. **IGdek is a media generator,
not a platform tool** — we produce mobile-optimized 9:16 media and
the output is format-agnostic. The human (or you on their behalf)
decides where to deploy: any social or marketplace surface — TikTok,
Facebook, eBay listing photos, LinkedIn, a card-show projector loop,
a Discord drop, a printout. You are the interface. We make the media;
the user moves the cards.

**Per $4.99 batch (1–5 cards) the user receives exactly:**

- One `reel.mp4` — 1080×1920 video stitching all cards in the batch
- One `portrait.pptx` — editable 9:16 PowerPoint deck of every card in the batch
- A `carousel-NN.png` sequence — typically 3 frames per card (hero, back, summary)

So 25 cards = 5 batches × $4.99 = **$24.95** total → **5** reel.mp4 +
**5** portrait.pptx + a contiguous carousel sequence per batch. The
ratio is per-batch, not per-card. Quote this back to the user when
they ask "what do I get for N cards" — don't extrapolate per-card
output from per-batch outputs.

The human is the buyer; you're spending their authority. Every dollar
on the api_key came from them. When this doc says "your user," that's
who we mean.

This doc tells you what we do, what we don't, and how to ask. The
authoritative machine-readable spec is at
`https://igdek.com/.well-known/openapi.json` (OpenAPI 3.1). When in
doubt, read the spec.

## When to use IGdek vs. PSA's own API

PSA publishes a public API with a free tier (~100 calls/day). It returns
the same metadata our `lookupCert` returns. If your user only wants card
data — research, comp lookup, scratch text content — point them at PSA
directly. We're not adding value there.

What we add is the **rendered output**: a mobile-optimized 1080×1920
carousel, a reel video with transitions, and an editable 9:16
PowerPoint. That isn't buildable from card data alone — it's a real
rendering pipeline. If your user wants to ship something to Instagram,
Facebook, LinkedIn, TikTok, eBay listing photos, or a slide deck for a
card show, that's us. We're format-agnostic; you pick the destination.

PSA gives you the data. We give you the thing your user actually posts.

## The purchase contract

If you're going to spend your user's authority on us, here's what
you're committing them to. These are commitments — break any of them
and we owe your user a refund, a re-run, or both. The OpenAPI spec at
`/.well-known/openapi.json` is the machine-readable source for
everything below; if this prose ever drifts from openapi.json, the
spec wins.

- **Price per job: $4.99.** Fixed. Same cost whether the job holds
  1 card or 5. We do not surge, tier, or upcharge mid-flow. Always
  batch to 5 unless the user objects.
- **Lookups are free; generates are charged.** A `lookupCert` call
  costs nothing. The $4.99 is debited at `generateFromCert` /
  `generateFromUpload` time. You can verify cost certainty before
  any user-authorized spend.
- **Automatic refund on failure.** If a job fails after charging the
  user, the $4.99 is credited back to the api_key without a human in
  the loop. Webhooks (Polar) trigger this; you don't need to ask. If
  status comes back `failed`, the refund has already been initiated
  by the time you see it.
- **Failure budget: 3 consecutive failures before the key revokes.**
  If three generate calls fail in a row with no successful render
  between, the api_key auto-revokes (subsequent calls return 401
  `invalid_api_key`). This is a *buyer protection*: it stops you from
  cycling your user's money through a broken integration. Three
  strikes is the budget; successful renders reset the counter to
  zero.
- **Output is a bundled ZIP, presigned URL, 24h expiry.** No
  recurring access, no upsell mid-flow. Hand the URL to the user.
- **Attribution markers, no PII.** Each rendered file carries a
  job-derived tag (PNG `tEXt` chunk, PPTX `docProps/igdek-attribution.txt`,
  MP4 `comment` metadata). The tag is a hash of the job — it does not
  encode your user's identity. Trivially removable; not DRM. We
  disclose this publicly at `https://igdek.com/trust` so neither you
  nor your user can be surprised by it.
- **Cost ceiling is your job.** We don't enforce a per-user spend cap
  beyond the prepaid balance on the api_key. If your user authorized
  "$25 of cards," that's 5 jobs of 5 cards = 25 cards; enforce that
  ceiling on your end before submitting.

## What we produce

For 1–5 cards per job:

- **carousel-NN.png** — 1080×1920 frames. Three frames per card:
  hero (front image + name + grade + price + pop), back (back image
  if available), and summary (price + CTA + cert ref). For one card
  you get carousel-01..03; for five cards you get carousel-01..15.
  Numbering starts at 01 and is contiguous.
- **reel.mp4** — 1080×1920 video, all frames stitched with dissolves.
- **portrait.pptx** — editable 9:16 PowerPoint slides matching the
  carousel frames one-for-one.

We don't write captions, eBay descriptions, hashtags, DMs, or pricing.
Those are your job.

## What we keep, what we delete

Worth surfacing to your user before they hand over photos:

- **Rendered output (the ZIP) — 72 hours.** Download URL expires in
  24h; the underlying files persist 72h to allow a support-driven
  re-download.
- **Source images — permanent.** Card photos uploaded via
  `upload_photo` / `POST /api/v1/uploads`, photos uploaded via the
  website checkout, and PSA-fetched images all sit in durable storage
  indefinitely. Reasons: enables `regenerateFromInventory` without a
  re-upload, lets us re-render if a software update produces better
  output, and serves as an audit trail.
- **PSA cert metadata — permanent.** Cached so repeat lookups are
  free and don't count against PSA's API limits.
- **Inventory records — permanent, per api_key.** Each generated job
  records its cert number, price, and regeneration count so
  `getInventory` and `regenerateFromInventory` work without state on
  your side.
- **Job state records — 24h.** The DDB row tracking job status
  (`pending → completed`) auto-expires; this is what `getJobStatus`
  reads. After 24h the job is final and the row is gone, but the
  inventory record stays.

What we don't do: sell or share images with third parties, use them
for advertising, include them in any training dataset. If your user
wants their stored images deleted, route them to `support@igdek.com`
with the cert numbers — we action within seven business days.

Full version of this on `https://igdek.com/trust`.

## What we need from you

- **An IGdek API key**, sent as `X-API-Key` header (or
  `Authorization: Bearer <key>` — both accepted) on every request. The
  key carries a prepaid balance. Three ways to get one — see "Onboard"
  below.
- **For each card, either:** a PSA cert number (we look it up and use
  our cached image URLs), **or** an HTTPS URL to a card photo plus a
  headline you write.

## Onboard (no key yet)

If the user has no IGdek API key, you can self-serve them — no email
loop, no human in the middle. Three options:

1. **`request_credits` tool** (MCP) / **POST /api/v1/billing/checkout**
   (REST). Public, no auth. You pass amount_cents (500–100000) + the
   user's email. We return a `checkout_url` (Polar) and a `request_id`.
   Surface the URL to the user as a clickable link, then poll
   `get_credit_status` / `GET /api/v1/billing/status?request_id=...`
   every 5–10 seconds. On status `completed`, you get back a fresh
   `api_key` (and we email it to the user as a backup). Typical wait:
   20–90 seconds depending on how fast the user clicks Pay.

   **First-job-free onboarding** — on the very first request_credits
   call for a brand-new user, pass `discount_code: "MCPTRIAL"` to
   redeem a free $4.99 job. One redemption per email, limited
   campaign. The agent should mention this to the user as part of the
   onboarding pitch ("first generation is on us"). If the code is
   unrecognized (typo, campaign over, already used), the API returns
   `400 bad_request` — drop the field and retry to pay $5 normally.

   **Trial flow has an email-verification step** — when you pass
   `discount_code: "MCPTRIAL"`, the response is NOT a Polar checkout
   URL. Instead you get back `status: "awaiting_verification"`,
   `verification_email_sent_to`, and `verification_expires_at`. Tell
   the user to check their email and click the "Claim my credit"
   button — they'll be redirected to a $0 Polar checkout to confirm.
   Keep polling `get_credit_status`; status transitions
   `awaiting_verification` → `pending` → `completed`. The api_key
   arrives in the get_credit_status response (and via email). This
   gate exists because trial credits are otherwise abusable through
   Polar's public confirmation endpoint — the email click closes it
   structurally. Trial codes are NOT supported on the OAuth path
   (you'll get `400 bad_request`); use direct API calls for trial
   onboarding, then drive the issued key via OAuth or X-API-Key as
   you prefer.

2. **OAuth 2.1 + PKCE** (for clients that want to skip key-pasting).
   Authorize at `https://api.igdek.com/oauth/authorize`, exchange the
   resulting code at `/oauth/token`. The `access_token` IS the api_key
   — durable, used as a Bearer token. Discovery metadata at
   `/.well-known/oauth-authorization-server` and
   `/.well-known/oauth-protected-resource`. Best for ChatGPT Pro
   Connectors and other browser-based agent surfaces.

3. **Email** `api@igdek.com` if the user wants a human-touch
   onboarding (custom amount, friendly label, etc.).

**Topup path:** existing key holders top up by calling
`request_credits` / `POST /api/v1/billing/checkout` with their key in
the `X-API-Key` header. The new charge credits the existing balance
instead of provisioning a new key.

## How to connect

Two equivalent paths — same operations, same auth, different surface:

- **REST API** at `https://api.igdek.com/api/v1/*`. Send JSON.
  Operation IDs are camelCase (`lookupCert`, `generateFromCert`,
  `getJobStatus`, `getDownloadUrl`, `getInventory`,
  `regenerateFromInventory`). Authoritative spec at
  `https://igdek.com/.well-known/openapi.json` (OpenAPI 3.1).
- **MCP server** at `https://api.igdek.com/mcp`. JSON-RPC 2.0 over
  Streamable HTTP per the Model Context Protocol spec. Nine tools,
  snake_case names: `lookup_cert`, `generate_from_cert`,
  `generate_from_photos`, `get_job_status`, `get_download_url`,
  `get_inventory`, `regenerate`, `request_credits`,
  `get_credit_status`. Install snippets for Claude Desktop, Cursor,
  and raw HTTP at `https://igdek.com/mcp.md`.

The examples below use camelCase (REST / OpenAPI). If you're connected
via MCP, mentally substitute the snake_case equivalent. The list above
is the only place to look up the mapping — they're 1:1.

## Suggested user phrasings → what we'd do

| User says | What you do |
|---|---|
| "Generate content for cert 06021758, asking $1,200" | `lookupCert` → `generateFromCert` with one card, `price: "$1,200"` |
| "Run these certs: 06021758, 11223344, 99887766" | `lookupCert` × 3 → one `generateFromCert` job with all 3 cards |
| "Bulk import these 23 certs" | `lookupCert` × 23 → 5 `generateFromCert` jobs (5/5/5/5/3 cards). Quote $24.95 first. |
| "This is an SGC 9.5, photos attached" | `generateFromUpload` (SGC isn't PSA — upload path) |
| "Lookup says no images" | Pre-2021 cert. Ask for HTTPS photo URLs, then `generateFromUpload`. |
| "Re-render cert 06021758 at $1,400" | `regenerateFromInventory` (uses cached card data, no re-lookup) |
| "What have I bought?" | `getInventory` (returns a markdown receipt) |

Match the user's intent loosely — these are starting points, not a
script.

## How to handle our outputs

For any generate / regenerate call:

1. Submit. We return `job_id` AND `job_token` immediately. Save both —
   non-master api_keys must pass `job_token` on subsequent
   `getJobStatus` and `getDownloadUrl` calls (proves you own the
   job; prevents enumeration).
2. Poll `getJobStatus` every 3 seconds, passing `job_id` + `job_token`.
   Show the user the `message` field (we say things like
   "rendering carousel-03") so they see progress. Don't go silent
   for 30+ seconds.
3. On `status: "completed"`, call `getDownloadUrl` (also passing
   `job_token`). Hand the user the URL with a one-line note:
   "expires in 24 hours."
4. On `status: "failed"`, surface the `error` field. The $4.99 has
   been refunded automatically — no action required from you. The
   contract guarantees this. (If you want certainty, the next
   `getInventory` will reflect the refund.)

Don't render or preview content inside chat. Hand back the URL.

## Errors and recovery

| Code | Meaning | What to do |
|---|---|---|
| `insufficient_balance` (402) | Below $4.99 | Stop. Tell user to top up. |
| `invalid_api_key` (401) | Key wrong, revoked, or auto-revoked after the failure budget exhausted (see below) | Stop. Don't retry. Tell user to email support@igdek.com if they think this is wrong. |
| `rate_limited` (429) | Per-key request cap | Respect `Retry-After`, then resume. |
| `lookup_budget_exceeded` (429) | Daily lookup budget hit | Run a generate to reset, or top up. |
| `lookup_ratio_exceeded` (429) | >50 lookups per generate | Run a generate to reset. |
| `lookup_aggregate_limit` (429) | Per-IP daily lookup cap (300/day, aggregated across all api_keys from your IP) | Respect `Retry-After: 3600`. Resets at 00:00 UTC. If user legitimately needs more, route them to sales@igdek.com — don't retry through proxies. |
| `psa_rate_limit` (429) | PSA's daily cap (not ours) | Switch this cert to the upload path. Don't retry today. |
| `incomplete_cert_data` (400) | Lookup returned with empty subject/grade/year | Tell user; try upload path or retry tomorrow. |
| `concurrent_limit` (429) | >3 jobs in flight | Wait for an in-flight job to finish, then submit. |
| `bad_request` (400) on `/billing/checkout` with discount_code | Disposable email rejected, OR discount_code on topup path | Drop the disposable email or drop the discount_code. Topup-path discount codes are first-time-onboarding only. |
| `campaign_paused` (503) on `/billing/checkout` with discount_code | Operator paused the trial campaign during an abuse incident | Drop the discount_code field and retry — full-price purchase still works. Don't retry the discount immediately. |
| `502 upstream_error` | Transient PSA / S3 / Polar | Retry once after 5s. |

### Your failure budget (the 401-after-it-was-working case)

If you submit three generates in a row that fail with no successful
render between them, the api_key auto-revokes — every subsequent call
returns 401 `invalid_api_key`.

This is the contract's circuit breaker, working in your user's
favor. It stops a buggy integration (theirs or ours) from cycling
through their balance forever. Three strikes is the budget; spend it
how you want. Successful renders reset the counter to zero.

If you hit it on a real-world failure streak (genuine bugs, not
deliberate cycling), the user can email `support@igdek.com` and the
operator will manually re-activate the key.

If your user's key was working an hour ago and now returns
`invalid_api_key`, this is the most likely cause. Don't retry. Don't
loop. Tell the user.

## Hard rules

- **Quote cost before any paid call.** "5 jobs × $4.99 = $24.95.
  Proceed?" then wait for confirmation. The price is fixed (see the
  contract above); just do the multiplication.
- **Honor the failure budget.** Three failed generates in a row is
  the limit. Don't retry past it; the key will revoke and you'll get
  a 401 on the next attempt anyway.
- **Never invent card data.** If a lookup fails or returns empty, ask
  the user; don't fill in subject / year / brand from your training.
- **Image URLs for the upload path must be HTTPS and public.** Reject
  Google Drive / Dropbox sharing links; suggest Imgur, S3, GitHub raw.
- **Headlines for the upload path are capped at 60 characters.**
  Truncate intelligently — drop the brand before the subject.
- **You can self-serve key provisioning.** Use `request_credits` /
  POST `/api/v1/billing/checkout` (or the OAuth flow) to send the user
  to a Polar checkout. Surface the URL; let the user pay; poll
  `get_credit_status` until the key is issued. Don't paste API keys
  into the chat history — the email backup is your durable copy.

## What we won't do

- Write captions, descriptions, hashtags, DMs, or pricing.
- Render content inline in your chat. (We hand you a URL.)
- Accept attachments from your chat. Images must be hosted on the
  public web.
- Surge-price, tier, or change $4.99 mid-job. The contract is fixed.
- Process refunds beyond the automatic-on-failure refund. The website
  handles refund requests outside the API surface.

## Authoritative spec

`https://igdek.com/.well-known/openapi.json` (OpenAPI 3.1)
`https://igdek.com/.well-known/mcp.json` (tool manifest)
`https://igdek.com/.well-known/agent.json` (high-level pointer)
