{"openapi":"3.0.3","info":{"title":"Pocket Factory card profiles for AI shopping agents","version":"1.24.1","description":"The public routes an AI shopping agent may call to prepare a Pocket Factory card that a human buys. Start with GET /v1/agent: its cardProfiles.enabled field says whether the card profile routes are on; while they are off they answer 404. The agent never pays and never enters contact or payment details: it hands the human the checkout link and the human pays at Shopify checkout."},"servers":[{"url":"https://pocketfactory-brand.bryton-p-zoz.workers.dev","description":"Pocket Factory"}],"paths":{"/v1/agent":{"get":{"operationId":"getAgentGuide","summary":"How an AI shopping agent makes a card a human can buy","description":"Always available; X-Robots-Tag noindex. Short JSON: what Pocket Factory sells, the five product handles (variant ids and prices must be read live from https://getpocketfactory.com/products/<handle>.js), the steps (create a profile, wait for status ready, preview PNGs, checkout link https://getpocketfactory.com/?card=<slug>&buy=1&variant=<variant id>&qty=1 that a human opens and pays), idempotency and pending answers, error codes, allowed destinations, limits, larger orders, and whether card profiles are enabled. Checkout is Shopify web checkout, completed by the human in a browser. Card lines carry personalization (the _Card profile and _Card profile rev line properties), so they are not eligible for native agentic checkout. This is a plain HTTP API, not a native ChatGPT or meemo integration.","responses":{"200":{"description":"Agent guide JSON"}}}},"/v1/card-profiles":{"post":{"operationId":"createCardProfile","summary":"Save a card profile with its own share link","description":"Answers 404 while card profiles are switched off: check cardProfiles.enabled in GET /v1/agent. Agents send { source: \"agent\", website } or { source: \"agent\", name, imageUrl }; a name alone returns 422 with needs [\"website\", \"image\"] and a hint (logos are never looked up by name). The website is looked up once on the server.\n\nIdempotent: a create is keyed on the normalized website (case, www and a trailing slash do not matter), imageUrl, name, purpose, destination and the design fields, plus the Idempotency-Key header when sent. The same key answers with the same slug and reused true: 200 with the profile when it is ready, 202 while it is pending, and the stored failure for 10 minutes after a failure (then the request is tried afresh). A reused answer makes no lookup, counts toward no limit, spends nothing and never repeats editToken. Identical requests sent at the same moment join one job and get one slug.\n\nReadiness: when the card is made within about 8 seconds the answer is 201 { slug, rev: 1, status: \"ready\", reused: false, editToken, shareUrl, profile }. Otherwise it is 202 { slug, status: \"pending\", poll, editToken, shareUrl, reused: false }: GET the poll link until status is ready or failed. Previews answer 409 not_ready until then.\n\nLimits per IP address: 30 requests a minute for creating, editing or copying card profiles and for drawing previews not cached yet (429 rate_limited), and at most 20 new card profiles an hour (429 create_limit). Profiles never ordered are deleted 90 days after their last revision. Prices are never in a card profile: read them live from https://getpocketfactory.com/products/<handle>.js. Checkout is Shopify web checkout, completed by the human in a browser. Card lines carry personalization (the _Card profile and _Card profile rev line properties), so they are not eligible for native agentic checkout. This is a plain HTTP API, not a native ChatGPT or meemo integration.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, up to 200 characters. Requests with the same key and the same card answer with the same slug; another key makes another card. Without it the card itself is the key.","schema":{"type":"string","maxLength":200}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CardProfileRequest"},"examples":{"website":{"value":{"source":"agent","website":"https://example.com","purpose":"Reviews","destination":"https://g.page/r/example/review"}},"image":{"value":{"source":"agent","name":"Joe's Plumbing","imageUrl":"https://example.com/logo.png","purpose":"Website","destination":"https://example.com"}}}}}},"responses":{"200":{"description":"reused: true. An earlier identical request made this card and it is ready: { slug, rev, status: \"ready\", reused, shareUrl, profile }, no editToken","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CardProfileEnvelope"}}}},"201":{"description":"Made now and ready: { slug, rev: 1, status: \"ready\", reused: false, editToken, shareUrl, profile }","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CardProfileEnvelope"}}}},"202":{"description":"Pending: { slug, status: \"pending\", poll, shareUrl, reused } (editToken too when this request made it). Poll GET /v1/card-profiles/{slug}","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CardProfileStatus"}}}},"400":{"description":"Body is not a JSON object","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"code not_found: card profiles are switched off","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"description":"Body over 16 KB","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"code invalid_website (not a link), platform_url (a social, video, link-in-bio or map page, not the business's own site), blocked_host (a private or local address), needs_website_or_image (a name alone; with needs and hint), nothing_found (the website gave no logo or name; needs [\"image\"]), image_unusable (imageUrl), invalid_fields or asset_missing (with fields). These are answered before any lookup or limit except nothing_found and image_unusable. A reused failure carries reused: true. Also: invalid_fields with fields [\"backMode\"] (a back other than Tap + QR), [\"backgroundArt.source\"] (the art's source is not what its stored file's record says) or [\"layers.logoMode\"] (layers on an Artwork Only card); needs_artwork (an Artwork Only card without backgroundArt; needs [\"backgroundArt\"], and for an agent a hint: this API can't add art, so the buyer makes that card on the homepage); asset_missing (a file that is not stored or belongs to another session)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"code rate_limited (30 writes a minute) or create_limit (20 new cards an hour)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Storage not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/card-profiles/{slug}":{"get":{"operationId":"getCardProfile","summary":"Read a card profile (public, noindex)","description":"Answers 404 while card profiles are switched off: check cardProfiles.enabled in GET /v1/agent. Anyone with the slug can read it: the card configuration, file links and preview links, never phone, email, address or the edit token. A read never makes anything: no website lookup, no AI call and no spend. Ready cards answer 200 { slug, status: \"ready\", profile } (Cache-Control public, max-age=60); pending cards 202 { slug, status: \"pending\", poll, shareUrl } (no-store); failed cards 200 { slug, status: \"failed\", reason, error, needs? }. X-Robots-Tag noindex, nofollow. Expired or unknown slugs are 404 (code expired or not_found).","parameters":[{"name":"slug","in":"path","required":true,"description":"<name-part>-<8 Crockford base32 characters>, for example joes-plumbing-7k2q9xmd.","schema":{"type":"string","pattern":"^[a-z0-9](?:[a-z0-9-]{0,22}[a-z0-9])?-[0-9abcdefghjkmnpqrstvwxyz]{8}$"}},{"name":"rev","in":"query","required":false,"description":"A revision number. Omit for the latest.","schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"{ slug, status: \"ready\", profile }, or { slug, status: \"failed\", reason, error, needs? }","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CardProfileEnvelope"}}}},"202":{"description":"{ slug, status: \"pending\", poll, shareUrl }: poll again in a few seconds","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CardProfileStatus"}}}},"404":{"description":"code not_found or expired; not_found also while switched off","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"put":{"operationId":"updateCardProfile","summary":"Edit a card profile: saves a new immutable revision","description":"Answers 404 while card profiles are switched off: check cardProfiles.enabled in GET /v1/agent. Requires Authorization: Bearer <editToken> (401 without it, 403 when it does not match). Fields sent replace the latest revision's; earlier revisions never change. The new revision's back is always Tap + QR.","parameters":[{"name":"slug","in":"path","required":true,"description":"<name-part>-<8 Crockford base32 characters>, for example joes-plumbing-7k2q9xmd.","schema":{"type":"string","pattern":"^[a-z0-9](?:[a-z0-9-]{0,22}[a-z0-9])?-[0-9abcdefghjkmnpqrstvwxyz]{8}$"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CardProfileRequest"},"examples":{"website":{"value":{"source":"agent","website":"https://example.com","purpose":"Reviews","destination":"https://g.page/r/example/review"}},"image":{"value":{"source":"agent","name":"Joe's Plumbing","imageUrl":"https://example.com/logo.png","purpose":"Website","destination":"https://example.com"}}}}}},"responses":{"200":{"description":"{ slug, rev, shareUrl, profile }","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CardProfileEnvelope"}}}},"401":{"description":"No edit token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Wrong edit token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found, expired or switched off","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"code rev_conflict (edited elsewhere at the same time, or baseRev is not the latest; latestRev says which is) or not_ready (still pending, or failed)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"description":"Body over 16 KB","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"{ error, code, fields }: invalid_fields with fields [\"backMode\"] (a back other than Tap + QR), [\"backgroundArt.source\"] (the art's source is not what its stored file's record says) or [\"layers.logoMode\"] (layers on an Artwork Only card); needs_artwork (an Artwork Only card without backgroundArt; needs [\"backgroundArt\"], and for an agent a hint: this API can't add art, so the buyer makes that card on the homepage); asset_missing (a file that is not stored or belongs to another session)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"code rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/card-profiles/{slug}/fork":{"post":{"operationId":"forkCardProfile","summary":"Copy a card profile to a new slug with its own edit token","description":"Answers 404 while card profiles are switched off: check cardProfiles.enabled in GET /v1/agent. No token needed. Starts from the latest revision (or rev), applies any fields sent, and returns a new slug with forkedFrom set; the copy's back is always Tap + QR. Counts toward the 20 new profiles an hour.","parameters":[{"name":"slug","in":"path","required":true,"description":"<name-part>-<8 Crockford base32 characters>, for example joes-plumbing-7k2q9xmd.","schema":{"type":"string","pattern":"^[a-z0-9](?:[a-z0-9-]{0,22}[a-z0-9])?-[0-9abcdefghjkmnpqrstvwxyz]{8}$"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CardProfileRequest"},"examples":{"website":{"value":{"source":"agent","website":"https://example.com","purpose":"Reviews","destination":"https://g.page/r/example/review"}},"image":{"value":{"source":"agent","name":"Joe's Plumbing","imageUrl":"https://example.com/logo.png","purpose":"Website","destination":"https://example.com"}}}}}},"responses":{"201":{"description":"{ slug, rev, editToken, shareUrl, profile, forkedFrom }","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CardProfileEnvelope"}}}},"404":{"description":"Not found, expired or switched off","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"code not_ready: the source card is still pending, or failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"{ error, code, fields }: invalid_fields with fields [\"backMode\"] (a back other than Tap + QR), [\"backgroundArt.source\"] (the art's source is not what its stored file's record says) or [\"layers.logoMode\"] (layers on an Artwork Only card); needs_artwork (an Artwork Only card without backgroundArt; needs [\"backgroundArt\"], and for an agent a hint: this API can't add art, so the buyer makes that card on the homepage); asset_missing (a file that is not stored or belongs to another session)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"code rate_limited or create_limit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/card-profiles/{slug}/preview/front.png":{"get":{"operationId":"getCardProfilePreviewFront","summary":"Front of the card as a PNG","description":"Answers 404 while card profiles are switched off: check cardProfiles.enabled in GET /v1/agent. Drawn by the same renderer as order files and kept per revision. With rev the image is immutable; without it, cached 60 s.","parameters":[{"name":"slug","in":"path","required":true,"description":"<name-part>-<8 Crockford base32 characters>, for example joes-plumbing-7k2q9xmd.","schema":{"type":"string","pattern":"^[a-z0-9](?:[a-z0-9-]{0,22}[a-z0-9])?-[0-9abcdefghjkmnpqrstvwxyz]{8}$"}},{"name":"rev","in":"query","required":false,"description":"A revision number. Omit for the latest.","schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"PNG"},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"code not_ready: the card is pending (poll it) or failed. Previews are only drawn for ready cards","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Nothing to draw yet: no logo or business name (needs [\"website\", \"image\"]), or code needs_artwork for an Artwork Only card without its art (needs [\"backgroundArt\"])","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Renderer unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/card-profiles/{slug}/preview/back.png":{"get":{"operationId":"getCardProfilePreviewBack","summary":"Back of the card as a PNG","description":"Answers 404 while card profiles are switched off: check cardProfiles.enabled in GET /v1/agent. The tap side, with the QR for the card's link when the card has Tap + QR. Same caching as the front.","parameters":[{"name":"slug","in":"path","required":true,"description":"<name-part>-<8 Crockford base32 characters>, for example joes-plumbing-7k2q9xmd.","schema":{"type":"string","pattern":"^[a-z0-9](?:[a-z0-9-]{0,22}[a-z0-9])?-[0-9abcdefghjkmnpqrstvwxyz]{8}$"}},{"name":"rev","in":"query","required":false,"description":"A revision number. Omit for the latest.","schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"PNG"},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"code not_ready: the card is pending (poll it) or failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Nothing to draw yet","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Renderer unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}},"components":{"schemas":{"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"type":"string","example":"Unable to resolve this website"},"domain":{"type":"string"},"code":{"type":"string"},"reason":{"$ref":"#/components/schemas/LookupReason"}}},"LookupReason":{"type":"string","enum":["not_found","timeout","blocked","platform_url"],"description":"Present only when the lookup produced no brand (no logo and no business name read from the site; a name guessed from the domain alone does not count). not_found: the site was read or could not be reached and has no usable name or logo. timeout: the site or the reader took too long. blocked: the site refused the reader (401/403/429/451 or a bot challenge page) or the address is private. platform_url: a social, video, link-in-bio or map page. Absent on success and on request errors (rate limit, bad JSON, misconfiguration)."},"CardProfileStatus":{"type":"object","description":"A card that is not ready: pending (202, being made; poll the poll link) or failed (200, never ready; reason says why, and needs says what to send instead).","properties":{"slug":{"type":"string"},"status":{"type":"string","enum":["pending","failed"]},"poll":{"type":"string","description":"Pending only: GET this link until status is ready or failed."},"shareUrl":{"type":"string"},"reason":{"type":"string","description":"Failed only: the error code: nothing_found (the website gave no logo or name; needs [\"image\"]), needs_website_or_image, image_unusable, invalid_fields, asset_missing or create_failed."},"error":{"type":"string","description":"Failed only: a sentence to show."},"needs":{"type":"array","items":{"type":"string"},"description":"Failed only, when the website gave no usable logo: [\"image\"] or [\"website\", \"image\"]."},"reused":{"type":"boolean","description":"Create only: true when the answer is for an earlier identical request."}}},"CardProfileEnvelope":{"type":"object","properties":{"slug":{"type":"string"},"rev":{"type":"integer"},"status":{"type":"string","enum":["ready"],"description":"Create and read: ready means rev 1 (or later) exists and previews can be drawn."},"reused":{"type":"boolean","description":"Create only: true when an earlier identical request already made this card. No lookup, limit or spend was used, and editToken is not repeated."},"editToken":{"type":"string","description":"Create (when it makes the card, 201 or 202) and fork only. Shown once; only its SHA-256 is stored."},"shareUrl":{"type":"string"},"profile":{"$ref":"#/components/schemas/CardProfile"},"forkedFrom":{"type":"string"},"lookup":{"type":"object","description":"Agent website lookups only: status logo, name, none or unavailable."}}},"CardProfile":{"type":"object","properties":{"schema":{"type":"string","example":"pf.card-profile.v1"},"slug":{"type":"string"},"rev":{"type":"integer"},"latestRev":{"type":"integer"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"revisionCreatedAt":{"type":"string","format":"date-time"},"source":{"type":"string","enum":["human","agent"]},"forkedFrom":{"type":"string","nullable":true},"ordered":{"type":"boolean"},"expiresAt":{"type":"string","format":"date-time","nullable":true,"description":"Null once an order names the profile; ordered profiles are kept."},"shareUrl":{"type":"string","example":"https://getpocketfactory.com/?card=joes-plumbing-7k2q9xmd"},"card":{"$ref":"#/components/schemas/CardProfileCard"},"assets":{"type":"array","items":{"type":"object"}},"preview":{"type":"object","properties":{"front":{"type":"string"},"back":{"type":"string"}}},"buy":{"type":"object","properties":{"url":{"type":"string"},"variants":{"type":"string"}}}}},"CardProfileCard":{"type":"object","description":"Schema pf.card-profile.v1. Only what the card needs is stored: unknown fields (phone, email, address, contact details, lookup results) are dropped. Missing fields take their defaults; invalid values return 422 with fields. At most 16 KB.","properties":{"businessName":{"type":"string","maxLength":120,"description":"Name to print. Text that looks like an email address or phone number is refused."},"nameSource":{"type":"string","enum":["typed","lookup","domain"]},"domain":{"type":"string","description":"Website host, for example example.com. website is accepted as an alias."},"purpose":{"type":"string","enum":["Reviews","Contact","Booking","Social","Website","Other"]},"platform":{"type":"string","description":"Reviews or Social only, for example Google."},"destination":{"type":"string","format":"uri","description":"The permanent link the card opens: https:// or http:// only."},"orientation":{"type":"string","enum":["Horizontal","Vertical"],"default":"Horizontal"},"baseColor":{"type":"string","enum":["Light","Brand Color","Dark"],"default":"Brand Color"},"logoMode":{"type":"string","enum":["Logo Mark","Wordmark","Logo Mark + Wordmark","Artwork Only"],"default":"Logo Mark + Wordmark","description":"What the front shows: the logo, the business name, or both. Artwork Only: the front is backgroundArt alone, with no logo and no name; layers must be empty and backgroundArt is required (422 needs_artwork without it). The business name may still be saved; it is not printed."},"backMode":{"type":"string","enum":["Tap + QR","Tap Only"],"default":"Tap + QR","description":"The back: the tap symbol and a QR code (Tap + QR). Every new card and every edit or copy gets Tap + QR, and a write that asks for anything else is 422 with fields [\"backMode\"]. Tap Only is only read on older saved cards, whose saved revisions stay as they were."},"finish":{"type":"string","enum":["Matte","Glossy"],"default":"Matte"},"logoSize":{"type":"integer","minimum":1,"maximum":100,"default":100},"primaryColor":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$"},"backgroundColor":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$"},"backArtVersion":{"type":"integer","enum":[1,2],"default":2},"pack":{"type":"integer","enum":[1,2,3,5,10,25,50],"default":2,"description":"Cards in the pack. Variant ids and prices are read live from the storefront, never stored."},"artworkState":{"type":"string","enum":["ready","manual_review","pending"]},"artwork":{"type":"object","description":"Logo files on offer by role (mark, wordmark, combined): { assetId, originalAssetId? } or an asset id. Kept so a reopened card can switch logo style without another lookup; layers say which ones are drawn.","additionalProperties":false,"properties":{"mark":{"type":"object","properties":{"assetId":{"type":"string"},"originalAssetId":{"type":"string"}}},"wordmark":{"type":"object","properties":{"assetId":{"type":"string"},"originalAssetId":{"type":"string"}}},"combined":{"type":"object","properties":{"assetId":{"type":"string"},"originalAssetId":{"type":"string"}}}}},"backgroundArt":{"type":"object","description":"The card's full-card art: an image the customer uploaded (source customer-upload) or AI artwork Pocket Factory made (source ai-generated). It lies under the logo or name, or is the whole front when logoMode is Artwork Only. A write may set it to a stored file whose own record says the same source (anything else is 422), keep it unchanged, or send null to remove it. Its orientation must match the card's.","required":["assetId","source","orientation"],"properties":{"assetId":{"type":"string","pattern":"^asset_[a-f0-9]{32}$"},"source":{"type":"string","enum":["ai-generated","customer-upload"],"description":"ai-generated: AI artwork Pocket Factory made. customer-upload: an image the customer uploaded."},"description":{"type":"string","maxLength":300,"description":"AI artwork: the customer's description, trimmed (required; 3 to 300 characters with no links, contact details, brands, logos or trademarks). An uploaded image: optional."},"orientation":{"type":"string","enum":["Horizontal","Vertical"]},"fit":{"type":"string","enum":["fill","fit"],"default":"fill","description":"fill covers the whole card and its bleed, cut at the centre; fit shows the whole image on the base colour. Saved and returned only when it is fit."},"url":{"type":"string","description":"On read only."}}},"uploads":{"type":"array","maxItems":50,"description":"Every file the customer uploaded for this card, in the order they were picked (the storefront sends it). It only grows: an edit adds files and marks earlier ones, and an entry a revision has is never dropped by a later one. assetId is the file as they picked it (original false when it is the picture made from it), copyAssetId the picture made for the card; removed is a file taken off or replaced, consumed one an AI design was made from. A file must be a customer upload the caller may read; anything else is left out without failing the write. A card that holds an upload never expires.","items":{"type":"object","required":["role","assetId"],"properties":{"role":{"type":"string","enum":["logo_mark","wordmark","profile_photo","background_art","art_reference"]},"assetId":{"type":"string","pattern":"^asset_[a-f0-9]{32}$"},"name":{"type":"string","maxLength":120},"size":{"type":"integer","minimum":1},"sha256":{"type":"string","pattern":"^[a-f0-9]{64}$"},"copyAssetId":{"type":"string","pattern":"^asset_[a-f0-9]{32}$"},"original":{"type":"boolean","enum":[false]},"pages":{"type":"integer","minimum":2},"removed":{"type":"boolean","enum":[true]},"consumed":{"type":"boolean","enum":[true]}}}},"layers":{"type":"array","maxItems":3,"items":{"type":"object","required":["role","kind"],"properties":{"role":{"type":"string","enum":["mark","wordmark","combined"]},"kind":{"type":"string","enum":["image","business-name-text"]},"assetId":{"type":"string","pattern":"^asset_[a-f0-9]{32}$","description":"A stored file (POST /v1/assets/upload). Must be readable by the caller."},"originalAssetId":{"type":"string","pattern":"^asset_[a-f0-9]{32}$"},"text":{"type":"string","maxLength":120}}}}}},"CardProfileRequest":{"type":"object","description":"card holds the configuration. Shortcuts for agents: name, website, imageUrl, purpose, platform, destination, finish, orientation, pack, backMode, baseColor, logoSize, primaryColor, backgroundColor. Fields sent replace the base card's (a fork's source or the latest revision); card wins over shortcuts. backMode, as a shortcut or in card, accepts only Tap + QR: every new revision has it. Body at most 16 KB.","properties":{"source":{"type":"string","enum":["human","agent"],"description":"agent tags the profile as made by an AI agent. A request without a storefront Origin is tagged agent."},"card":{"$ref":"#/components/schemas/CardProfileCardWrite"},"name":{"type":"string","description":"Business name to print."},"website":{"type":"string","description":"For agents: the website is looked up once on the server and the logo, name and colour found are stored in the profile."},"imageUrl":{"type":"string","format":"uri","description":"Public https link to the logo: PNG, JPEG, WebP or GIF up to 10 MB, or SVG up to 512 KB. Fetched once on the server through the SSRF guard and stored; SVG is sanitized."},"baseRev":{"type":"integer","description":"PUT only: refuse with 409 unless this is still the latest revision."},"rev":{"type":"integer","description":"Fork only: fork this revision instead of the latest."}}},"CardProfileCardWrite":{"description":"CardProfileCard as a create, edit or fork sends it: backMode accepts only Tap + QR (it may be left out).","allOf":[{"$ref":"#/components/schemas/CardProfileCard"},{"type":"object","properties":{"backMode":{"type":"string","enum":["Tap + QR"],"default":"Tap + QR"}}}]}}}}