{"openapi":"3.1.0","info":{"title":"imgd.dev","version":"0.1.0","description":"Image hosting your agent can sign up for. No email, no captcha, no dashboard. $1 buys 1 GB of storage, one-time and stacking. Uploads are content-addressed by sha256 and idempotent. Every 4xx carries a 'fix' field in plain language.","contact":{"email":"abuse@imgd.dev"}},"servers":[{"url":"https://imgd.dev"}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Your imgd_ API key."}},"schemas":{"Error":{"type":"object","required":["error","fix"],"properties":{"error":{"type":"string","description":"Stable machine-readable error code."},"fix":{"type":"string","description":"Plain-language instruction telling the caller what to do next."}}}}},"paths":{"/health":{"get":{"summary":"Liveness probe","security":[],"responses":{"200":{"description":"Service is healthy."}}}},"/llms.txt":{"get":{"summary":"Agent-oriented quickstart","security":[],"responses":{"200":{"description":"Plain text guide."}}}},"/evaluate.md":{"get":{"summary":"Paste-at-an-agent evaluation brief","description":"Markdown written for an agent deciding whether imgd.dev fits its user's use case, including an explicit list of cases it does not fit. Public, no auth.","responses":{"200":{"description":"text/markdown"}}}},"/integrate.md":{"get":{"summary":"Paste-at-an-agent integration procedure","description":"Markdown giving the ordered integration steps, real response shapes, key-safety checks and a verification checklist. Public, no auth.","responses":{"200":{"description":"text/markdown"}}}},"/v1/accounts":{"post":{"summary":"Create an account","description":"No email, no captcha. Returns the API key exactly once plus a Stripe activation link. Optionally accepts a coupon code, which grants its storage immediately.","security":[],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"coupon":{"type":"string","description":"Optional coupon code to redeem on creation."}}}}}},"responses":{"201":{"description":"Account created.","content":{"application/json":{"schema":{"type":"object","properties":{"account_id":{"type":"string"},"api_key":{"type":"string","description":"Shown once; never recoverable."},"status":{"type":"string","enum":["unpaid"]},"activation_url":{"type":"string","nullable":true},"storage_quota_bytes":{"type":"integer","description":"Zero until a payment grants storage."}}}}}}}}},"/v1/me":{"get":{"summary":"Quota, usage, remaining storage and strikes","description":"Returns storage_bytes, storage_quota_bytes and storage_remaining_bytes so a caller can decide whether a file will fit before uploading it.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Account state.","content":{"application/json":{"schema":{"type":"object","properties":{"account_id":{"type":"string"},"status":{"type":"string","enum":["unpaid","active","suspended"],"description":"'unpaid' accounts have zero quota and cannot upload."},"storage_bytes":{"type":"integer"},"storage_quota_bytes":{"type":"integer"},"storage_remaining_bytes":{"type":"integer"},"storage_used_pct":{"type":"number"},"topup_url":{"type":"string"}}}}}},"401":{"description":"Missing or invalid key.","content":{"application/json":{"schema":{"type":"object","required":["error","fix"],"properties":{"error":{"type":"string","description":"Stable machine-readable error code."},"fix":{"type":"string","description":"Plain-language instruction telling the caller what to do next."}}}}}}}}},"/v1/images/{hash}/publish":{"post":{"summary":"Set, change or clear an image's expiry (and republish)","description":"Schedules when the public link stops serving, or clears the schedule to make it permanent. Also republishes an expired image: the URL is content-addressed, so the identical link works again. Bytes are retained while unpublished and still count toward the storage quota.","security":[{"bearerAuth":[]}],"parameters":[{"name":"hash","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"unpublish_after_hours":{"type":"number","minimum":0,"exclusiveMinimum":true,"maximum":8760,"description":"Hours until the link stops serving. Omit both fields for a permanent link."},"unpublish_at":{"type":"string","description":"Absolute expiry as epoch seconds or an ISO-8601 instant."}}}}}},"responses":{"200":{"description":"Publish state updated."},"400":{"description":"The expiry window is invalid, in the past, or too far away.","content":{"application/json":{"schema":{"type":"object","required":["error","fix"],"properties":{"error":{"type":"string","description":"Stable machine-readable error code."},"fix":{"type":"string","description":"Plain-language instruction telling the caller what to do next."}}}}}},"403":{"description":"The image was removed by moderation and cannot be published.","content":{"application/json":{"schema":{"type":"object","required":["error","fix"],"properties":{"error":{"type":"string","description":"Stable machine-readable error code."},"fix":{"type":"string","description":"Plain-language instruction telling the caller what to do next."}}}}}},"404":{"description":"No image with that hash is linked to your account.","content":{"application/json":{"schema":{"type":"object","required":["error","fix"],"properties":{"error":{"type":"string","description":"Stable machine-readable error code."},"fix":{"type":"string","description":"Plain-language instruction telling the caller what to do next."}}}}}}}}},"/v1/images/{hash}/unpublish":{"post":{"summary":"Unpublish an image now, keeping the bytes","description":"Stops the public link serving immediately. The bytes are retained and still count toward the storage quota; DELETE is what frees space.","security":[{"bearerAuth":[]}],"parameters":[{"name":"hash","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Unpublished."},"404":{"description":"No image with that hash is linked to your account.","content":{"application/json":{"schema":{"type":"object","required":["error","fix"],"properties":{"error":{"type":"string","description":"Stable machine-readable error code."},"fix":{"type":"string","description":"Plain-language instruction telling the caller what to do next."}}}}}}}}},"/v1/redeem":{"post":{"summary":"Redeem a coupon code for storage","description":"Adds the code's storage to the account and activates it. No payment is involved. A code works at most once per account.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["code"],"properties":{"code":{"type":"string","description":"Case-insensitive coupon code."}}}}}},"responses":{"200":{"description":"Redeemed.","content":{"application/json":{"schema":{"type":"object","properties":{"redeemed":{"type":"boolean"},"gb":{"type":"integer"},"granted_bytes":{"type":"integer"},"storage_quota_bytes":{"type":"integer"}}}}}},"400":{"description":"Code is invalid, expired, exhausted, or already redeemed by this account.","content":{"application/json":{"schema":{"type":"object","required":["error","fix"],"properties":{"error":{"type":"string","description":"Stable machine-readable error code."},"fix":{"type":"string","description":"Plain-language instruction telling the caller what to do next."}}}}}},"401":{"description":"Missing or invalid key.","content":{"application/json":{"schema":{"type":"object","required":["error","fix"],"properties":{"error":{"type":"string","description":"Stable machine-readable error code."},"fix":{"type":"string","description":"Plain-language instruction telling the caller what to do next."}}}}}},"429":{"description":"Too many redemption attempts.","content":{"application/json":{"schema":{"type":"object","required":["error","fix"],"properties":{"error":{"type":"string","description":"Stable machine-readable error code."},"fix":{"type":"string","description":"Plain-language instruction telling the caller what to do next."}}}}}}}}},"/v1/credit":{"post":{"summary":"Buy storage ($1 = 1 GB, stacking)","description":"Returns both payment options as JSON. 'checkout_url' is a hosted Stripe card page and needs a human; 'crypto_options' is machine-payable — pick ONE entry and follow its 'pay' field, which names the network, exact amount and address together, as an ordinary transfer an agent can do unaided. Entries span several chains and tokens, so pay from whichever chain holds your stablecoins; larger purchases also offer ethereum. Each entry's address exists only on that entry's network — addresses are derived per chain, so mixing an address from one entry with the network of another is unrecoverable. Each quote expires about 15 minutes out. Works for already-active accounts, so a paying caller can top up at any time. Purchases stack and never expire.","security":[{"bearerAuth":[]}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"gb":{"type":"integer","minimum":1,"maximum":100,"default":1,"description":"Gigabytes to buy, priced at $1 each."}}}}}},"responses":{"200":{"description":"Checkout session created.","content":{"application/json":{"schema":{"type":"object","properties":{"checkout_url":{"type":"string","description":"Hosted Stripe card page. Requires a human with a browser."},"crypto_checkout_url":{"type":["string","null"],"description":"Hosted stablecoin payment page. Absent when the crypto rail is not configured; 'checkout_url' is always present."},"crypto_options":{"type":"array","description":"Complete payment instructions, one per accepted (chain, token) pair. No human, no card, no account: an ordinary ERC-20 transfer settles it. Pick ONE entry and use its fields together — they are different destinations, not different renderings of one.","items":{"type":"object","properties":{"network":{"type":"string","description":"The chain to send on. This entry's address exists on THIS network and nowhere else; sending on another chain is unrecoverable by anyone."},"chain_id":{"type":"integer","description":"Numeric id of the same network."},"token":{"type":"string"},"token_address":{"type":"string","description":"The token contract on this entry's network."},"decimals":{"type":"integer","description":"Read this; not every stablecoin uses 6. DAI uses 18, and assuming 6 for it underpays by a factor of 10^12."},"amount":{"type":"string","description":"Exact amount in the token's smallest unit. Send verbatim."},"amount_display":{"type":"string","description":"The same amount in whole token units, for a human."},"address":{"type":"string","description":"Destination, valid only on this entry's network."},"pay":{"type":"string","description":"The whole instruction in one sentence: network, amount and address together. Safe to show a human verbatim."},"quote_expires_at":{"type":"integer","description":"Unix seconds; about 15 minutes out."}}}},"gb":{"type":"integer"},"price_usd":{"type":"string"},"grants_bytes":{"type":"integer"}}}}}},"400":{"description":"'gb' outside the accepted range.","content":{"application/json":{"schema":{"type":"object","required":["error","fix"],"properties":{"error":{"type":"string","description":"Stable machine-readable error code."},"fix":{"type":"string","description":"Plain-language instruction telling the caller what to do next."}}}}}},"401":{"description":"Missing or invalid key.","content":{"application/json":{"schema":{"type":"object","required":["error","fix"],"properties":{"error":{"type":"string","description":"Stable machine-readable error code."},"fix":{"type":"string","description":"Plain-language instruction telling the caller what to do next."}}}}}}}}},"/v1/upload":{"post":{"summary":"Upload an image","description":"Multipart (field 'file') or raw bytes with an image/* Content-Type. Returns 202 while moderation runs, or 200 when identical bytes already exist. Re-uploading identical bytes REPLACES any existing expiry with whatever this request specifies, including none at all.","security":[{"bearerAuth":[]}],"parameters":[{"name":"unpublish_after_hours","in":"query","description":"Hours until the link stops serving. Must be > 0; omit for a permanent link.","schema":{"type":"number"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"},"unpublish_after_hours":{"type":"number","description":"Optional. Hours until the link stops serving; omit for a permanent link."},"unpublish_at":{"type":"string","description":"Optional absolute expiry."}},"required":["file"]}},"image/*":{"schema":{"type":"string","format":"binary"}}}},"responses":{"200":{"description":"Deduplicated; identical bytes already stored."},"202":{"description":"Accepted; moderation pending."},"402":{"description":"Payment required: the account has no storage yet, or this upload exceeds the quota. The body carries topup_url, a Stripe checkout_url for a human, and machine-payable crypto_options, so an agent can pay from the error itself and retry with nobody in the loop.","content":{"application/json":{"schema":{"type":"object","required":["error","fix"],"properties":{"error":{"type":"string","description":"Stable machine-readable error code."},"fix":{"type":"string","description":"Plain-language instruction telling the caller what to do next."}}}}}},"403":{"description":"Account suspended, or this content was already blocked.","content":{"application/json":{"schema":{"type":"object","required":["error","fix"],"properties":{"error":{"type":"string","description":"Stable machine-readable error code."},"fix":{"type":"string","description":"Plain-language instruction telling the caller what to do next."}}}}}},"413":{"description":"File too large.","content":{"application/json":{"schema":{"type":"object","required":["error","fix"],"properties":{"error":{"type":"string","description":"Stable machine-readable error code."},"fix":{"type":"string","description":"Plain-language instruction telling the caller what to do next."}}}}}},"415":{"description":"Unsupported media type.","content":{"application/json":{"schema":{"type":"object","required":["error","fix"],"properties":{"error":{"type":"string","description":"Stable machine-readable error code."},"fix":{"type":"string","description":"Plain-language instruction telling the caller what to do next."}}}}}},"429":{"description":"Rate limited.","content":{"application/json":{"schema":{"type":"object","required":["error","fix"],"properties":{"error":{"type":"string","description":"Stable machine-readable error code."},"fix":{"type":"string","description":"Plain-language instruction telling the caller what to do next."}}}}}}}}},"/v1/images":{"get":{"summary":"List your images","description":"Returns {images, count, next_cursor}. `images` is the array; each entry carries hash, url, status, alt_text, published and unpublish_at. Keep requesting with ?cursor=<next_cursor> until next_cursor is null.","security":[{"bearerAuth":[]}],"parameters":[{"name":"limit","in":"query","description":"Page size, 1-100. Values outside the range are clamped.","schema":{"type":"integer","default":25,"minimum":1,"maximum":100}},{"name":"cursor","in":"query","description":"Offset from the previous response's next_cursor. Omit for the first page.","schema":{"type":"integer","default":0}}],"responses":{"200":{"description":"Paginated image list: {images, count, next_cursor}.","content":{"application/json":{"schema":{"type":"object","properties":{"images":{"type":"array","items":{"type":"object"}},"count":{"type":"integer"},"next_cursor":{"type":["integer","null"]}}}}}}}}},"/v1/images/{hash}":{"get":{"summary":"Image metadata including alt_text","description":"Includes status (live | review | blocked) and, when the status is review or blocked, a `moderation` object stating the outcome, whether the state is terminal, and how to contest it. `review` means the automated check was not confident: the image is served blurred, the state does NOT resolve on its own, and it carries no abuse strike.","security":[{"bearerAuth":[]}],"parameters":[{"name":"hash","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Metadata."},"404":{"description":"Not linked to your account.","content":{"application/json":{"schema":{"type":"object","required":["error","fix"],"properties":{"error":{"type":"string","description":"Stable machine-readable error code."},"fix":{"type":"string","description":"Plain-language instruction telling the caller what to do next."}}}}}}}},"delete":{"summary":"Unlink an image from your account","security":[{"bearerAuth":[]}],"parameters":[{"name":"hash","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted."},"404":{"description":"Not found.","content":{"application/json":{"schema":{"type":"object","required":["error","fix"],"properties":{"error":{"type":"string","description":"Stable machine-readable error code."},"fix":{"type":"string","description":"Plain-language instruction telling the caller what to do next."}}}}}}}}},"/i/{hash}":{"get":{"summary":"Serve an image","description":"Immutable cache headers. Optional transforms via ?w= and ?fmt=.","security":[],"parameters":[{"name":"hash","in":"path","required":true,"schema":{"type":"string"}},{"name":"w","in":"query","schema":{"type":"integer","maximum":4096}},{"name":"fmt","in":"query","schema":{"type":"string","enum":["webp","avif","jpeg","png"]}}],"responses":{"200":{"description":"Image bytes."},"404":{"description":"Unknown, removed by moderation, or not currently published. An unpublished image can be brought back with POST /v1/images/{hash}/publish.","content":{"application/json":{"schema":{"type":"object","required":["error","fix"],"properties":{"error":{"type":"string","description":"Stable machine-readable error code."},"fix":{"type":"string","description":"Plain-language instruction telling the caller what to do next."}}}}}},"409":{"description":"Still clearing moderation. Only returned when the request asks for JSON; image clients receive a blurred placeholder instead, so the link works immediately.","content":{"application/json":{"schema":{"type":"object","required":["error","fix"],"properties":{"error":{"type":"string","description":"Stable machine-readable error code."},"fix":{"type":"string","description":"Plain-language instruction telling the caller what to do next."}}}}}}}}}}}