{"openapi":"3.1.0","info":{"title":"foldr.space API","version":"1.0.0","summary":"Agent-native file storage, sharing, and URL infrastructure.","description":"Programmatic API for AI agents and developers. Upload files, host images, mint short URLs, manage swappable images, and pay per request via pre-funded credits. Onboarding is 3 HTTP calls, no browser required.\n\n**Auth:** `Authorization: Bearer fs_...` - get a key from `POST /api/v1/auth/register`.\n\n**Idempotency:** Send `Idempotency-Key: <opaque-string>` on any write endpoint to make retries safe. Same key + same body replays the original response. 24h TTL.\n\n**Per-request cost:** Every response includes `X-Foldr-Cost` and `X-Foldr-Balance-Remaining` headers.\n\nAgent manifest: https://foldr.space/.well-known/agent.json","contact":{"name":"foldr.space","url":"https://foldr.space/developers"},"license":{"name":"Proprietary"}},"servers":[{"url":"https://foldr.space","description":"Production"}],"components":{"securitySchemes":{"ApiKey":{"type":"http","scheme":"bearer","bearerFormat":"fs_...","description":"API key in the form `fs_` followed by 32 alphanumeric characters. Get one from `POST /api/v1/auth/register`."}},"parameters":{"IdempotencyKey":{"name":"Idempotency-Key","in":"header","required":false,"description":"Opaque string (8–255 chars) the agent generates per logical operation. Retrying with the same key + same body replays the original response. Different body + same key returns 422. 24h TTL.","schema":{"type":"string","minLength":8,"maxLength":255},"example":"agent-job-7f3c2-attempt-1"}},"headers":{"XFoldrCost":{"description":"Cost of this request in micro-cents (1/10000 of a cent).","schema":{"type":"integer","minimum":0}},"XFoldrBalanceRemaining":{"description":"Remaining account balance in cents after this request.","schema":{"type":"integer","nullable":true}},"XFoldrProcessingMs":{"description":"Server processing time in milliseconds.","schema":{"type":"integer"}},"XFoldrRequestId":{"description":"Unique UUID for this request - include in support tickets.","schema":{"type":"string","format":"uuid"}},"XRateLimitLimit":{"description":"Rate limit (requests per minute) for this endpoint.","schema":{"type":"integer"}},"XRateLimitRemaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"XRateLimitReset":{"description":"Unix timestamp (seconds) when the rate limit window resets.","schema":{"type":"integer"}},"IdempotentReplayed":{"description":"Set to \"true\" if this response was replayed from a cached idempotent operation.","schema":{"type":"string","enum":["true"]}},"RetryAfter":{"description":"Seconds the agent should wait before retrying.","schema":{"type":"integer"}}},"schemas":{"Error":{"type":"object","required":["success","error"],"properties":{"success":{"type":"boolean","enum":[false]},"error":{"type":"string","description":"Human-readable message."},"code":{"type":"string","description":"Stable machine-readable code. Agents should branch on this, not the message.","examples":["UNAUTHORIZED","API_KEY_INACTIVE","FORBIDDEN","RATE_LIMIT_EXCEEDED","INSUFFICIENT_CREDITS","PAYMENT_REQUIRED","BAD_REQUEST","PAYLOAD_TOO_LARGE","UNSUPPORTED_MEDIA_TYPE","NOT_FOUND","CONFLICT","RESOURCE_LIMIT_REACHED","IDEMPOTENCY_KEY_INVALID","IDEMPOTENCY_KEY_MISMATCH","IDEMPOTENCY_IN_FLIGHT","INTERNAL_ERROR","SERVICE_UNAVAILABLE"]},"retryable":{"type":"boolean","description":"Whether the agent should retry. Combine with Retry-After when applicable."},"action":{"type":"string","description":"Concrete next-step hint for the agent (e.g. \"POST /api/v1/agent/account/top-up to add credits\")."},"details":{"type":"object","additionalProperties":true,"description":"Optional context (e.g. maxSize, retryAfter, allowedTypes)."},"_meta":{"$ref":"#/components/schemas/AgentMeta"}}},"AgentMeta":{"type":"object","description":"Per-request cost + timing - also exposed as X-Foldr-* headers.","properties":{"cost_micro_cents":{"type":"integer"},"balance_remaining_cents":{"type":"integer","nullable":true},"processing_ms":{"type":"integer"},"request_id":{"type":"string","format":"uuid"}}},"File":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"shareToken":{"type":"string","description":"Public share token used in /f/{shareToken} URLs."},"downloadUrl":{"type":"string","format":"uri"},"publicUrl":{"type":"string","format":"uri"},"originalName":{"type":"string"},"size":{"type":"integer","description":"Bytes."},"mimeType":{"type":"string"},"isPermanent":{"type":"boolean"},"expiresAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"}}},"ShortUrl":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"shortCode":{"type":"string"},"shortUrl":{"type":"string","format":"uri"},"destinationUrl":{"type":"string","format":"uri"},"isCustomSlug":{"type":"boolean"},"expiresAt":{"type":"string","format":"date-time","nullable":true},"isActive":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time"}}},"SwappableImage":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"token":{"type":"string","description":"Public token used in /img/{token} URLs."},"serveUrl":{"type":"string","format":"uri"},"embedUrl":{"type":"string","format":"uri"},"label":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"version":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"number":{"type":"integer"},"size":{"type":"integer"},"mimeType":{"type":"string"}}}}},"AgentAccount":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"ownerEmail":{"type":"string","format":"email"},"balanceCents":{"type":"integer"},"balanceFormatted":{"type":"string","example":"$12.34"},"spendingLimitCents":{"type":"integer","nullable":true},"autoChargeEnabled":{"type":"boolean"}}},"AgentFolder":{"type":"object","description":"Per-API-key organizable folder. Slug is unique within (api_key, parent_folder) - siblings under different parents may share a slug. 1–64 chars, lowercase letters, digits, hyphens.","properties":{"id":{"type":"string","format":"uuid"},"slug":{"type":"string","example":"research-papers"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"metadata":{"type":"object","additionalProperties":true,"nullable":true},"parentSlug":{"type":"string","nullable":true,"description":"Slug of parent folder; null for root."},"fileCount":{"type":"integer"},"totalSizeBytes":{"type":"integer"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Capability":{"type":"object","description":"Short-lived, opaque, single-file token. Authenticates downloads with bounded expiry, optional use-count cap, and revocability.","properties":{"id":{"type":"string","format":"uuid"},"token":{"type":"string","description":"Raw token (cap_…). Returned only at mint time.","nullable":true},"tokenPrefix":{"type":"string"},"scope":{"type":"string","enum":["read"]},"description":{"type":"string","nullable":true},"expiresAt":{"type":"string","format":"date-time"},"maxUses":{"type":"integer","nullable":true},"usesCount":{"type":"integer"},"revokedAt":{"type":"string","format":"date-time","nullable":true},"lastUsedAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"}}}},"responses":{"Unauthorized":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"BadRequest":{"description":"Validation error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Rate limit exceeded. Retry after the seconds indicated in the Retry-After header.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"IdempotencyConflict":{"description":"A previous request with this Idempotency-Key is still processing - retry after the indicated delay.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"IdempotencyMismatch":{"description":"Same Idempotency-Key was reused with a different request body. Use a fresh key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"PaymentRequired":{"description":"Payment required (x402). Send X-PAYMENT header with a signed USDC-on-Base payload that satisfies one of the requirements in `accepts[]`, or top up your account credits. Same Idempotency-Key + same body replays without re-charging.","content":{"application/json":{"schema":{"type":"object","properties":{"x402Version":{"type":"integer","example":1},"accepts":{"type":"array","items":{"type":"object","properties":{"scheme":{"type":"string","example":"exact"},"network":{"type":"string","example":"base"},"maxAmountRequired":{"type":"string","example":"50000"},"resource":{"type":"string"},"description":{"type":"string"},"payTo":{"type":"string","example":"0x0fF790fBBF1D26E533cCaf9c9C01EBE728509f74"},"asset":{"type":"string","example":"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"},"maxTimeoutSeconds":{"type":"integer","example":60}}}},"error":{"type":"string"}}}}}}}},"security":[{"ApiKey":[]}],"tags":[{"name":"discovery","description":"No-auth endpoints for agent discovery and health."},{"name":"auth","description":"Programmatic signup and tier upgrade."},{"name":"files","description":"File upload, download, and management."},{"name":"images","description":"Image upload with CDN delivery."},{"name":"swappable-images","description":"Permanent URLs whose backing image can be swapped."},{"name":"urls","description":"Short URL creation and analytics."},{"name":"agent","description":"Agent account, credits, payment methods, capabilities."},{"name":"folders","description":"Per-API-key organizable folders for files. Slug-addressed, hierarchical via parent_slug."},{"name":"capabilities","description":"Short-lived signed file shares for agent-to-agent handoff. Time-bounded, optionally use-capped, revocable."}],"paths":{"/api/v1/info":{"get":{"tags":["discovery"],"summary":"Platform discovery","description":"No-auth onboarding endpoint. Returns auth instructions, capabilities, links to docs and the agent manifest.","security":[],"responses":{"200":{"description":"Platform info.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}}},"/api/v1/health":{"get":{"tags":["discovery"],"summary":"Service health","description":"Real-time uptime, latency p50/p95/p99, throughput, success rate. Use this to route around degradation.","security":[],"responses":{"200":{"description":"Current health snapshot.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}}},"/api/v1/agent/capabilities":{"get":{"tags":["agent","discovery"],"summary":"List capabilities","description":"Enumerate what the API supports - file types, max sizes per tier, available tools. Safe to call without auth.","security":[],"responses":{"200":{"description":"Capability map.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}}},"/api/v1/auth/register":{"post":{"tags":["auth"],"summary":"Register an API key","description":"Programmatic signup. No browser required. Returns an `fs_...` key the agent can use immediately.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email"}}}}}},"responses":{"200":{"description":"API key issued.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"apiKey":{"type":"string","example":"fs_abc..."},"role":{"type":"string","example":"free"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"}}}},"/api/v1/files":{"post":{"tags":["files"],"summary":"Upload a file","description":"Upload a file. All uploads are permanent. Charged 5¢ USDC per upload via x402 (or via account credits). Up to 5 GB on Enterprise (use the streaming flow for files >100 MB).","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary"},"folder_slug":{"type":"string","description":"Optional: place this file in a folder you own. Folder must already exist (POST /api/v1/agent/folders).","example":"research-papers"},"metadata":{"type":"string","description":"Optional JSON string of arbitrary metadata to attach to the file."}}}}}},"responses":{"201":{"description":"File uploaded.","headers":{"X-Foldr-Cost":{"$ref":"#/components/headers/XFoldrCost"},"X-Foldr-Balance-Remaining":{"$ref":"#/components/headers/XFoldrBalanceRemaining"},"X-Foldr-Request-Id":{"$ref":"#/components/headers/XFoldrRequestId"},"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}},"content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"file":{"$ref":"#/components/schemas/File"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"409":{"$ref":"#/components/responses/IdempotencyConflict"},"413":{"description":"File too large.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"415":{"description":"Blocked file type.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/IdempotencyMismatch"},"429":{"$ref":"#/components/responses/RateLimited"}}},"get":{"tags":["files"],"summary":"List files","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},{"name":"offset","in":"query","schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"Files for this API key.","content":{"application/json":{"schema":{"type":"object","properties":{"files":{"type":"array","items":{"$ref":"#/components/schemas/File"}},"pagination":{"type":"object","properties":{"limit":{"type":"integer"},"offset":{"type":"integer"},"total":{"type":"integer"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v1/files/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["files"],"summary":"Download a file","description":"Default: public download by ID, no auth.\n\nOptional capability auth via `Authorization: Capability cap_xxx` or `?capability=cap_xxx` - incrementing the token use count, enforcing expiry / max_uses / revocation.\n\nSupports `Range: bytes=A-B` for resumable / streaming downloads (responds 206 + `Content-Range`).","security":[],"parameters":[{"name":"Range","in":"header","required":false,"description":"Optional byte range - see RFC 7233.","schema":{"type":"string","example":"bytes=0-1023"}},{"name":"capability","in":"query","required":false,"schema":{"type":"string","example":"cap_…"}},{"name":"download","in":"query","required":false,"description":"\"1\" forces Content-Disposition: attachment.","schema":{"type":"string"}}],"responses":{"200":{"description":"Full file binary.","headers":{"Accept-Ranges":{"schema":{"type":"string","enum":["bytes"]}}},"content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"206":{"description":"Partial content.","headers":{"Content-Range":{"schema":{"type":"string"}}},"content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"401":{"description":"Capability token rejected (expired / revoked / use limit).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"416":{"description":"Range not satisfiable.","headers":{"Content-Range":{"schema":{"type":"string"}}}}}},"delete":{"tags":["files"],"summary":"Delete a file","responses":{"200":{"description":"Deleted.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/files/presigned-url":{"post":{"tags":["files"],"summary":"Get a presigned upload URL for streaming uploads","description":"First step of the streaming upload flow for large files. Client PUTs the file directly to the returned URL (Supabase Storage), then calls `POST /api/v1/files/register` with the storage path. Use this for files larger than ~100MB on Pro or up to 5GB on Enterprise.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["filename"],"properties":{"filename":{"type":"string"},"mimeType":{"type":"string"}}}}}},"responses":{"200":{"description":"Presigned URL valid 30 minutes. The 5¢ charge happens at /register, not here.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"uploadUrl":{"type":"string","format":"uri"},"path":{"type":"string"},"token":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v1/files/register":{"post":{"tags":["files"],"summary":"Register a streamed-upload file","description":"Second step of the streaming upload flow. Call after the file is uploaded to the presigned URL. Charged 5¢ per file (same as inline upload). Accepts the same `folder_slug` and `metadata` as inline upload.","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["path","originalName","size","mimeType"],"properties":{"path":{"type":"string","description":"Storage path returned by /presigned-url."},"originalName":{"type":"string"},"size":{"type":"integer","description":"File size in bytes. Must be ≤ tier maxFileSize."},"mimeType":{"type":"string"},"folder_slug":{"type":"string"},"metadata":{"type":"object","additionalProperties":true,"nullable":true}}}}}},"responses":{"201":{"description":"Registered.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"file":{"$ref":"#/components/schemas/File"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"409":{"$ref":"#/components/responses/IdempotencyConflict"},"422":{"$ref":"#/components/responses/IdempotencyMismatch"}}}},"/api/v1/files/batch":{"post":{"tags":["files"],"summary":"Apply one operation to many files","description":"Up to 50 items per call. Operations: delete, move, update_metadata. Per-item results - partial failures don't fail the request. Each file must be owned by the calling key.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["operation","items"],"properties":{"operation":{"type":"string","enum":["delete","move","update_metadata"]},"items":{"type":"array","minItems":1,"maxItems":50,"items":{"type":"object","required":["file_id"],"properties":{"file_id":{"type":"string","format":"uuid"},"folder_slug":{"type":"string","nullable":true,"description":"Required for operation=move."},"metadata":{"type":"object","additionalProperties":true,"nullable":true,"description":"For operation=update_metadata."},"original_name":{"type":"string","description":"Optional rename for operation=update_metadata."}}}}}}}}},"responses":{"200":{"description":"Per-item results.","content":{"application/json":{"schema":{"type":"object","properties":{"operation":{"type":"string"},"succeeded":{"type":"integer"},"failed":{"type":"integer"},"total":{"type":"integer"},"results":{"type":"array","items":{"type":"object","additionalProperties":true}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v1/agent/rate-limit":{"get":{"tags":["agent"],"summary":"Inspect quota + current usage","description":"Returns the calling key's tier role, request budget, size limits, and current resource counts. Use before expensive calls to avoid 429s.","responses":{"200":{"description":"Quota snapshot.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v1/agent/webhooks":{"post":{"tags":["agent"],"summary":"Subscribe to lifecycle events","description":"Returns an HMAC secret ONCE - store it. Outgoing deliveries are signed with `X-Foldr-Signature: sha256=<hmac>`. Failed deliveries retry with 1m/5m/15m backoff via the cron drainer.","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url","event_types"],"properties":{"url":{"type":"string","format":"uri"},"event_types":{"type":"array","items":{"type":"string","enum":["file.uploaded","file.downloaded","capability.minted","capability.redeemed","capability.revoked"]}},"description":{"type":"string","maxLength":1000}}}}}},"responses":{"201":{"description":"Created with secret.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Subscription limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"tags":["agent"],"summary":"List webhook subscriptions","responses":{"200":{"description":"Subscriptions.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v1/files/{id}/move":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"tags":["files","folders"],"summary":"Move a file between folders","description":"Pass `folder_slug: \"name\"` to move into that folder, `folder_slug: null` to move to root. Stats on source and destination folders are updated automatically.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["folder_slug"],"properties":{"folder_slug":{"type":"string","nullable":true}}}}}},"responses":{"200":{"description":"Moved.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Not the file owner.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"File not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/files/{id}/capability-tokens":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"tags":["capabilities","files"],"summary":"Mint a capability token for this file","description":"Returns an opaque `cap_…` token plus a ready-to-use download URL. Token is bound to this file_id. Default expiry 1h, max 7d. Optional `max_uses` caps redemptions.","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"expires_in":{"type":"integer","minimum":1,"maximum":604800,"default":3600,"description":"Seconds. Capped at 7 days."},"max_uses":{"type":"integer","minimum":1,"description":"Optional cap on number of redemptions."},"description":{"type":"string","maxLength":1000}}}}}},"responses":{"201":{"description":"Capability minted.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"capability":{"$ref":"#/components/schemas/Capability"},"downloadUrl":{"type":"string","format":"uri"},"authorizationHeader":{"type":"string"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Not the file owner.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"File not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"tags":["capabilities","files"],"summary":"List capability tokens for this file","responses":{"200":{"description":"Capabilities.","content":{"application/json":{"schema":{"type":"object","properties":{"capabilities":{"type":"array","items":{"$ref":"#/components/schemas/Capability"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Not the file owner.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"File not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/capability-tokens/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"delete":{"tags":["capabilities"],"summary":"Revoke a capability token","description":"Idempotent - revoking an already-revoked token returns 200.","responses":{"200":{"description":"Revoked.","content":{"application/json":{"schema":{"type":"object","properties":{"revoked":{"type":"boolean"},"id":{"type":"string","format":"uuid"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Not the issuer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/images":{"post":{"tags":["images"],"summary":"Upload an image","description":"JPEG, PNG, GIF, WebP, SVG, AVIF. CDN-served via /i/{publicId}.","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["image"],"properties":{"image":{"type":"string","format":"binary"}}}}}},"responses":{"201":{"description":"Uploaded.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"413":{"description":"Image too large.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"415":{"description":"Unsupported image format.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"tags":["images"],"summary":"List images","responses":{"200":{"description":"Images for this API key.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v1/swappable-images":{"post":{"tags":["swappable-images"],"summary":"Create a swappable image","description":"Returns a permanent URL whose backing image can be swapped without changing the URL - useful for embeds, link previews, and A/B tests.","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["image"],"properties":{"image":{"type":"string","format":"binary"},"label":{"type":"string","maxLength":255}}}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"image":{"$ref":"#/components/schemas/SwappableImage"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Resource limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"$ref":"#/components/responses/IdempotencyConflict"},"422":{"$ref":"#/components/responses/IdempotencyMismatch"}}},"get":{"tags":["swappable-images"],"summary":"List swappable images","responses":{"200":{"description":"Images for this API key.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v1/swappable-images/{token}/swap":{"parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["swappable-images"],"summary":"Swap the backing image","description":"Replace the image without changing the public URL. Bumps version_number; previous version is preserved.","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["image"],"properties":{"image":{"type":"string","format":"binary"}}}}}},"responses":{"200":{"description":"Swapped.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Image not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"$ref":"#/components/responses/IdempotencyConflict"}}}},"/api/v1/urls":{"post":{"tags":["urls"],"summary":"Create a short URL","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["destinationUrl"],"properties":{"destinationUrl":{"type":"string","format":"uri"},"customSlug":{"type":"string","minLength":3,"maxLength":64},"password":{"type":"string","description":"Optional password to gate access."},"expiresAt":{"type":"string","format":"date-time"}}}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"url":{"$ref":"#/components/schemas/ShortUrl"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Resource limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Custom slug already taken or idempotency conflict.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/IdempotencyMismatch"}}},"get":{"tags":["urls"],"summary":"List short URLs","responses":{"200":{"description":"URLs for this API key.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v1/urls/{shortCode}/meta":{"parameters":[{"name":"shortCode","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["urls"],"summary":"Get URL metadata + analytics","security":[],"responses":{"200":{"description":"Click count, geo, referrers.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"404":{"description":"Not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/agent/account":{"get":{"tags":["agent"],"summary":"Get account + balance","responses":{"200":{"description":"Account state.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"account":{"$ref":"#/components/schemas/AgentAccount"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v1/agent/account/top-up":{"post":{"tags":["agent"],"summary":"Top up credits","description":"Add credits via Stripe Checkout (default) or charge a saved card. Send `Idempotency-Key` to make retries safe - without it a flaky network can double-charge.","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["amount_cents"],"properties":{"amount_cents":{"type":"integer","minimum":500,"maximum":50000,"description":"Amount in cents. Min 500 ($5), max 50000 ($500)."},"use_saved_card":{"type":"boolean","default":false,"description":"Charge default saved card immediately instead of returning a Checkout URL."}}}}}},"responses":{"200":{"description":"Checkout URL or charge result.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"description":"Card declined or auth required.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"$ref":"#/components/responses/IdempotencyConflict"},"422":{"$ref":"#/components/responses/IdempotencyMismatch"}}}},"/api/v1/agent/folders":{"post":{"tags":["folders"],"summary":"Create a folder","description":"Slug is unique per API key. If omitted, derived from name (lowercase, hyphenated). If the derived slug collides, suffixes -2, -3, … are tried automatically.","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":255},"slug":{"type":"string","pattern":"^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$","description":"Optional explicit slug. 1–64 chars, lowercase, digits, hyphens, no leading/trailing hyphen."},"description":{"type":"string","maxLength":1000},"metadata":{"type":"object","additionalProperties":true}}}}}},"responses":{"201":{"description":"Folder created.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"folder":{"$ref":"#/components/schemas/AgentFolder"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Folder limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Slug already in use for this API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/IdempotencyMismatch"}}},"get":{"tags":["folders"],"summary":"List folders","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},{"name":"offset","in":"query","schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"Folders for this API key.","content":{"application/json":{"schema":{"type":"object","properties":{"folders":{"type":"array","items":{"$ref":"#/components/schemas/AgentFolder"}},"pagination":{"type":"object","properties":{"limit":{"type":"integer"},"offset":{"type":"integer"},"total":{"type":"integer"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v1/agent/folders/{slug}":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["folders"],"summary":"Get folder metadata","responses":{"200":{"description":"Folder.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"folder":{"$ref":"#/components/schemas/AgentFolder"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["folders"],"summary":"Delete a folder","description":"Folder must be empty (file_count = 0). Move or delete files first.","responses":{"200":{"description":"Deleted.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"deleted":{"type":"boolean"},"slug":{"type":"string"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Folder not empty.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/agent/folders/{slug}/files":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["folders","files"],"summary":"List files in a folder","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},{"name":"offset","in":"query","schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"Files in folder + folder summary.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Folder not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/agent/account/transactions":{"get":{"tags":["agent"],"summary":"List credit transactions","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},{"name":"offset","in":"query","schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"Transactions.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}}}}