{"openapi":"3.1.0","info":{"title":"Printags Studio public endpoints","version":"2026-11-05","summary":"The anonymous endpoints of the Printags Studio shop that software can call.","description":"Printags Studio (https://studio.printags.com) sells printed PVC, NFC, MIFARE and wood cards, from a single card, with no account.\n\nEvery endpoint in this document is public and anonymous: there is no API key, no OAuth and no scope (`security: []`).\nThe shop has no REST ordering API. Orders are placed by a person on the shop, or prepared for them through the MCP server (`POST /mcp`): the assistant never pays, never takes an address and never approves the proof; the person opens the `claim_url` it returns (valid 48 hours, single use), approves the proof, enters the address and pays.\nFor integrations on a Printags account (API key or OAuth), see https://www.printags.com/auth.md and https://docs.printags.com.","contact":{"name":"Printags","email":"contact@printags.com","url":"https://www.printags.com"},"termsOfService":"https://studio.printags.com/cgv"},"servers":[{"url":"https://studio.printags.com","description":"Production"}],"security":[],"tags":[{"name":"Search","description":"Ask the shop a question (NLWeb)."},{"name":"Catalogue","description":"Prices and product feeds."},{"name":"Agents","description":"Documents and the MCP server for AI agents."}],"externalDocs":{"description":"Guide for AI agents","url":"https://studio.printags.com/llms.txt"},"paths":{"/ask":{"get":{"operationId":"askShop","tags":["Search"],"summary":"Ask the shop a question (NLWeb v0.55)","description":"Returns the products (with their live price tiers), FAQ answers and pages of the shop that answer a question in natural language, in the language of the question or of `accept-language`. A question nothing answers is a failure with the code NO_RESULTS. Answers are not cached. Same behaviour as POST /ask.","security":[],"parameters":[{"name":"query","in":"query","required":true,"description":"The question, 1 to 500 characters. `q` is accepted as an alias.","schema":{"type":"string","minLength":1,"maxLength":500},"example":"Can I order a single card?"},{"name":"mode","in":"query","required":false,"description":"Comma-separated: `list` (default) and/or `summarize` (adds a SearchSummary first).","schema":{"type":"string","default":"list"},"example":"list,summarize"},{"name":"streaming","in":"query","required":false,"description":"`true` answers with server-sent events. `Accept: text/event-stream` does the same.","schema":{"type":"boolean","default":false}},{"name":"accept-language","in":"query","required":false,"description":"fr, en, de, it or es. Without it, the language that best matches the question is used.","schema":{"type":"string","enum":["fr","en","de","it","es"]}}],"responses":{"200":{"description":"An answer, or a failure that is not a client error (NO_RESULTS, UNSUPPORTED_MODE). With streaming the same content comes as server-sent events: `start`, one `result` per item, then `complete`; a failure arrives as an `error` event, then `complete`.","headers":{"Cache-Control":{"schema":{"type":"string"},"description":"Always `no-store`."}},"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/AskAnswer"},{"$ref":"#/components/schemas/AskFailure"}]},"examples":{"answer":{"summary":"An answer","value":{"_meta":{"response_type":"answer","response_format":"conversational_search","version":"0.55","processing_time_ms":9},"results":[{"@type":"Question","@context":"https://schema.org","name":"Can I order a single card?","url":"https://studio.printags.com/en/products/custom-pvc-card","description":"Yes, from one card. The price per card then drops by tier, based on the total quantity of the order, even with several designs.","acceptedAnswer":{"@type":"Answer","text":"Yes, from one card. The price per card then drops by tier, based on the total quantity of the order, even with several designs."}}]}},"noResults":{"summary":"Nothing answers the question","value":{"_meta":{"response_type":"failure","version":"0.55"},"error":{"code":"NO_RESULTS","message":"No product or page of Printags Studio answers this question."}}}}},"text/event-stream":{"schema":{"type":"string"},"example":"event: start\ndata: {\"_meta\":{\"response_type\":\"answer\",\"version\":\"0.55\",\"streaming\":true}}\n\nevent: result\ndata: {\"index\":0,\"item\":{\"@type\":\"Question\",\"name\":\"Can I order a single card?\"}}\n\nevent: complete\ndata: {\"_meta\":{\"response_type\":\"answer\",\"version\":\"0.55\",\"processing_time_ms\":9}}\n\n"}}},"400":{"description":"The question is missing, empty or longer than 500 characters (code INVALID_QUERY).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AskFailure"},"example":{"_meta":{"response_type":"failure","version":"0.55"},"error":{"code":"INVALID_QUERY","message":"query.text is required: a question of 1 to 500 characters."}}}}}}},"post":{"operationId":"askShopPost","tags":["Search"],"summary":"Ask the shop a question (NLWeb v0.55), JSON body","description":"The JSON form of GET /ask. `prefer.streaming: true` (or `Accept: text/event-stream`) answers with server-sent events.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AskRequest"},"example":{"query":{"text":"Can I order a single card?"},"prefer":{"mode":"list, summarize"}}}}},"responses":{"200":{"description":"An answer, or a failure that is not a client error (NO_RESULTS, UNSUPPORTED_MODE). With streaming the same content comes as server-sent events: `start`, one `result` per item, then `complete`; a failure arrives as an `error` event, then `complete`.","headers":{"Cache-Control":{"schema":{"type":"string"},"description":"Always `no-store`."}},"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/AskAnswer"},{"$ref":"#/components/schemas/AskFailure"}]},"examples":{"answer":{"summary":"An answer","value":{"_meta":{"response_type":"answer","response_format":"conversational_search","version":"0.55","processing_time_ms":9},"results":[{"@type":"Question","@context":"https://schema.org","name":"Can I order a single card?","url":"https://studio.printags.com/en/products/custom-pvc-card","description":"Yes, from one card. The price per card then drops by tier, based on the total quantity of the order, even with several designs.","acceptedAnswer":{"@type":"Answer","text":"Yes, from one card. The price per card then drops by tier, based on the total quantity of the order, even with several designs."}}]}},"noResults":{"summary":"Nothing answers the question","value":{"_meta":{"response_type":"failure","version":"0.55"},"error":{"code":"NO_RESULTS","message":"No product or page of Printags Studio answers this question."}}}}},"text/event-stream":{"schema":{"type":"string"},"example":"event: start\ndata: {\"_meta\":{\"response_type\":\"answer\",\"version\":\"0.55\",\"streaming\":true}}\n\nevent: result\ndata: {\"index\":0,\"item\":{\"@type\":\"Question\",\"name\":\"Can I order a single card?\"}}\n\nevent: complete\ndata: {\"_meta\":{\"response_type\":\"answer\",\"version\":\"0.55\",\"processing_time_ms\":9}}\n\n"}}},"400":{"description":"The question is missing, empty or longer than 500 characters (code INVALID_QUERY).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AskFailure"},"example":{"_meta":{"response_type":"failure","version":"0.55"},"error":{"code":"INVALID_QUERY","message":"query.text is required: a question of 1 to 500 characters."}}}}}}},"options":{"operationId":"askShopPreflight","tags":["Search"],"summary":"CORS preflight of /ask","description":"Answers 204 with `Access-Control-Allow-Origin: *`.","security":[],"responses":{"204":{"description":"Preflight accepted: no body.","headers":{"Access-Control-Allow-Origin":{"description":"Always `*`.","schema":{"type":"string"}}}}}}},"/pricing.md":{"get":{"operationId":"getShopPricing","tags":["Catalogue"],"summary":"The price grid, as Markdown","description":"Unit prices excluding VAT of every card sold, by order size, read from the live catalogue. Answers 200 with a note instead of figures when the catalogue cannot be read.","security":[],"responses":{"200":{"description":"Markdown with front matter (`title`, `description`, `canonical`).","content":{"text/markdown":{"schema":{"type":"string"},"example":"---\ntitle: \"Printags Studio pricing\"\ndescription: \"Unit prices of the cards sold on the Printags Studio shop, excluding VAT, by order size.\"\ncanonical: \"https://studio.printags.com/pricing.md\"\n---\n\n# Printags Studio pricing\n"}}}}}},"/feeds/google-merchant.xml":{"get":{"operationId":"getMerchantFeed","tags":["Catalogue"],"summary":"Google Merchant Center product feed","description":"RSS 2.0 with the `g:` namespace: one item per product sold, in French, with the VAT-inclusive price of one card, built from the live catalogue. Answers 503 (not cached) when the catalogue cannot be read.","security":[],"responses":{"200":{"description":"The feed.","content":{"application/xml":{"schema":{"type":"string"},"example":"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<rss version=\"2.0\" xmlns:g=\"http://base.google.com/ns/1.0\">\n  <channel>\n    <title>Printags Studio</title>\n    <item>\n      <g:id>carte-pvc-sans-nfc</g:id>\n      <g:price>9.48 EUR</g:price>\n    </item>\n  </channel>\n</rss>\n"}}},"503":{"description":"The catalogue is not available.","content":{"text/plain":{"schema":{"type":"string"},"example":"The product catalogue is not available right now."}}}}}},"/llms.txt":{"get":{"operationId":"getLlmsTxt","tags":["Agents"],"summary":"Guide for AI agents (llms.txt)","description":"What the shop offers and how an agent should use it, in the llmstxt.org format.","security":[],"responses":{"200":{"description":"The guide.","content":{"text/plain":{"schema":{"type":"string"},"example":"# Printags Studio\n\n> Online shop for printed PVC, NFC, MIFARE and wood cards, from a single card, with no account needed.\n"}}}}}},"/auth.md":{"get":{"operationId":"getAuthGuide","tags":["Agents"],"summary":"How an agent gets access","description":"States that the shop needs no account and no credential, how the claim link works, and points to the account API and MCP server for integrators.","security":[],"responses":{"200":{"description":"Markdown.","content":{"text/markdown":{"schema":{"type":"string"},"example":"# Printags Studio: how an agent gets access\n"}}}}}},"/mcp":{"get":{"operationId":"describeMcp","tags":["Agents"],"summary":"Describe the MCP server","description":"A short Markdown description of the shop MCP server. With `Accept: text/event-stream` the endpoint answers 405, as the Streamable HTTP transport allows.","security":[],"responses":{"200":{"description":"Markdown with front matter.","content":{"text/markdown":{"schema":{"type":"string"},"example":"---\ntitle: \"Printags Studio MCP server\"\ncanonical: \"https://studio.printags.com/mcp\"\n---\n\n# Printags Studio MCP server\n"}}},"405":{"description":"Asked for an event stream: the server only takes POST.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"},"example":{"jsonrpc":"2.0","error":{"code":-32000,"message":"Method Not Allowed: this is the Printags shop MCP server (streamable HTTP, anonymous, no account). Send MCP JSON-RPC messages with POST."},"id":null}}}}}},"post":{"operationId":"callMcp","tags":["Agents"],"summary":"MCP endpoint (Streamable HTTP, JSON-RPC 2.0)","description":"The Printags Studio MCP server, anonymous: no account, no API key. Its card is at /.well-known/mcp/server-card.json.\nSend MCP JSON-RPC messages with `Content-Type: application/json` and `Accept: application/json, text/event-stream` (a request that does not accept both is refused with \"Not Acceptable\").\n`tools/list` returns the tools below; `tools/call` runs one with `params: { name, arguments }`. The tool result is in `result.content[0].text` as JSON text; a failure sets `result.isError`.\nTools: `list_products` (catalogue, minimum quantity, price tiers excluding VAT), `quote_cart` (indicative total), `create_upload`, `upload_file`, `upload_visual` (stage artwork), `prepare_cart` (returns the `claim_url` for the person), `get_order_status` (by tracking token). No tool pays, takes an address, approves a proof or creates an order.","security":[],"externalDocs":{"description":"MCP server card","url":"https://studio.printags.com/.well-known/mcp/server-card.json"},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcRequest"},"examples":{"toolsList":{"summary":"List the tools","value":{"jsonrpc":"2.0","id":1,"method":"tools/list"}},"listProducts":{"summary":"Call list_products","value":{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"list_products","arguments":{"locale":"en"}}}},"quoteCart":{"summary":"Call quote_cart","value":{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"quote_cart","arguments":{"lines":[{"product":"carte-pvc-sans-nfc","quantity":10}],"country_code":"FR"}}}}}}}},"responses":{"200":{"description":"A JSON-RPC response (as JSON, or as one server-sent event when the client prefers a stream).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"},"examples":{"toolsList":{"summary":"tools/list","value":{"result":{"tools":[{"name":"list_products","description":"The Studio catalogue...","inputSchema":{"type":"object"}}]},"jsonrpc":"2.0","id":1}},"validationError":{"summary":"A tool called with invalid arguments","value":{"result":{"content":[{"type":"text","text":"MCP error -32602: Input validation error: Invalid arguments for tool get_order_status"}],"isError":true},"jsonrpc":"2.0","id":1}}}}}},"406":{"description":"The client does not accept both application/json and text/event-stream.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"},"example":{"jsonrpc":"2.0","error":{"code":-32000,"message":"Not Acceptable: Client must accept both application/json and text/event-stream"},"id":null}}}},"429":{"description":"Rate limited by the upstream server; wait for `Retry-After` seconds.","headers":{"Retry-After":{"description":"Seconds to wait.","schema":{"type":"integer"}}}}}}}},"components":{"schemas":{"AskRequest":{"type":"object","required":["query"],"properties":{"query":{"type":"object","required":["text"],"properties":{"text":{"type":"string","minLength":1,"maxLength":500}}},"prefer":{"type":"object","properties":{"streaming":{"type":"boolean","description":"Answer with server-sent events."},"mode":{"type":"string","description":"Comma-separated: list, summarize.","default":"list"},"accept-language":{"type":"string","enum":["fr","en","de","it","es"]}}}}},"AskMeta":{"type":"object","properties":{"response_type":{"type":"string","enum":["answer","failure"]},"response_format":{"type":"string","example":"conversational_search"},"version":{"type":"string","example":"0.55"},"processing_time_ms":{"type":"integer"}}},"AskResult":{"type":"object","description":"A schema.org item (Question, Product, WebPage) or a SearchSummary.","properties":{"@type":{"type":"string","enum":["Question","Product","WebPage","SearchSummary"]},"@context":{"type":"string","example":"https://schema.org"},"name":{"type":"string"},"url":{"type":"string","format":"uri"},"description":{"type":"string"},"text":{"type":"string","description":"SearchSummary only."},"acceptedAnswer":{"type":"object","description":"Question only.","properties":{"@type":{"type":"string","example":"Answer"},"text":{"type":"string"}}}}},"AskAnswer":{"type":"object","required":["_meta","results"],"properties":{"_meta":{"$ref":"#/components/schemas/AskMeta"},"results":{"type":"array","items":{"$ref":"#/components/schemas/AskResult"}}}},"AskFailure":{"type":"object","required":["_meta","error"],"properties":{"_meta":{"$ref":"#/components/schemas/AskMeta"},"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["INVALID_QUERY","UNSUPPORTED_MODE","NO_RESULTS"]},"message":{"type":"string"}}}}},"JsonRpcRequest":{"type":"object","required":["jsonrpc","method"],"properties":{"jsonrpc":{"type":"string","const":"2.0"},"id":{"oneOf":[{"type":"integer"},{"type":"string"}],"description":"Omit for a notification."},"method":{"type":"string","description":"An MCP method, for example `initialize`, `tools/list`, `tools/call`.","example":"tools/list"},"params":{"type":"object","description":"For `tools/call`: `name` and `arguments`.","properties":{"name":{"type":"string","enum":["list_products","quote_cart","create_upload","upload_file","upload_visual","prepare_cart","get_order_status"]},"arguments":{"description":"The tool arguments; the shape depends on `name`.","oneOf":[{"$ref":"#/components/schemas/ListProductsArguments"},{"$ref":"#/components/schemas/QuoteCartArguments"},{"$ref":"#/components/schemas/CreateUploadArguments"},{"$ref":"#/components/schemas/UploadFileArguments"},{"$ref":"#/components/schemas/UploadVisualArguments"},{"$ref":"#/components/schemas/PrepareCartArguments"},{"$ref":"#/components/schemas/GetOrderStatusArguments"}]}}}}},"JsonRpcResponse":{"type":"object","required":["jsonrpc"],"properties":{"jsonrpc":{"type":"string","const":"2.0"},"id":{"oneOf":[{"type":"integer"},{"type":"string"},{"type":"null"}]},"result":{"type":"object","description":"For tools/call: `content` (an array with one `text` item holding JSON text) and `isError`."},"error":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"}}}}},"ListProductsArguments":{"type":"object","properties":{"locale":{"type":"string","enum":["fr","en","de","it","es"],"description":"Language of the person: product names, and the Studio page they will land on."}},"required":[],"additionalProperties":false,"description":"list_products"},"QuoteCartArguments":{"type":"object","properties":{"lines":{"type":"array","minItems":1,"maxItems":10,"items":{"$ref":"#/components/schemas/CartLineQuote"}},"country_code":{"type":"string","minLength":2,"maxLength":2,"description":"ISO 3166-1 alpha-2 delivery country (default FR). Shipping is free in France; fees apply elsewhere."},"locale":{"type":"string","enum":["fr","en","de","it","es"],"description":"Language of the person: product names, and the Studio page they will land on."}},"required":["lines"],"additionalProperties":false,"description":"quote_cart"},"CartLineQuote":{"type":"object","properties":{"product":{"type":"string","minLength":1,"maxLength":80,"description":"The slug returned by list_products."},"quantity":{"type":"integer","minimum":1,"maximum":10000}},"required":["product","quantity"],"additionalProperties":false},"CreateUploadArguments":{"type":"object","properties":{"file_name":{"type":"string","minLength":1,"maxLength":200},"content_type":{"type":"string","enum":["image/png","image/jpeg","application/pdf"]}},"required":["file_name","content_type"],"additionalProperties":false,"description":"create_upload: returns an upload_id and a PUT URL valid 15 minutes (25 MB max)."},"UploadFileArguments":{"type":"object","properties":{"file_name":{"type":"string","minLength":1,"maxLength":200},"content_type":{"type":"string","enum":["image/png","image/jpeg","application/pdf"]},"content_base64":{"type":"string","minLength":1,"description":"The file, base64-encoded (3 MB max)."}},"required":["file_name","content_type","content_base64"],"additionalProperties":false,"description":"upload_file: returns the upload_id to cite in prepare_cart."},"UploadVisualArguments":{"type":"object","properties":{"file":{"type":"object","properties":{"download_url":{"type":"string","format":"uri","maxLength":2048},"file_id":{"type":"string","maxLength":200},"mime_type":{"type":"string","maxLength":100},"file_name":{"type":"string","maxLength":200}},"required":["download_url","file_id"],"additionalProperties":false}},"required":["file"],"additionalProperties":false,"description":"upload_visual: the file a person attached in ChatGPT; the fields are filled by the client and never invented."},"PrepareCartArguments":{"type":"object","properties":{"lines":{"type":"array","minItems":1,"maxItems":10,"items":{"$ref":"#/components/schemas/CartLine"}},"locale":{"type":"string","enum":["fr","en","de","it","es"],"description":"Language of the person: product names, and the Studio page they will land on."}},"required":["lines"],"additionalProperties":false,"description":"prepare_cart: returns the claim_url to give to the person. Nothing is ordered or paid."},"CartLine":{"type":"object","properties":{"product":{"type":"string","minLength":1,"maxLength":80,"description":"The slug returned by list_products."},"quantity":{"type":"integer","minimum":1,"maximum":10000,"description":"At least the min_quantity of the product."},"front_upload_id":{"type":"string","format":"uuid","description":"An upload_id returned by create_upload or upload_file."},"back_upload_id":{"type":"string","format":"uuid","description":"An upload_id returned by create_upload or upload_file."},"blank_back":{"type":"boolean","description":"true: blank back, no back file."},"encoding":{"type":"object","properties":{"type":{"type":"string","enum":["uri","text","empty"],"description":"`uri`: an address (in `content`), `text`: a text, `empty`: chip not encoded."},"content":{"type":"string","maxLength":1000,"description":"The full address (https://...) or the text, given by the person."},"lock":{"type":"boolean","description":"Default true: the chip is locked read-only after writing, which cannot be undone."}},"required":["type"],"additionalProperties":false,"description":"One chip content for all the cards of the line."},"encodings":{"type":"array","maxItems":10000,"items":{"type":"object","properties":{"type":{"type":"string","enum":["uri","text","empty"],"description":"`uri`: an address (in `content`), `text`: a text, `empty`: chip not encoded."},"content":{"type":"string","maxLength":1000,"description":"The full address (https://...) or the text, given by the person."},"lock":{"type":"boolean","description":"Default true: the chip is locked read-only after writing, which cannot be undone."}},"required":["type"],"additionalProperties":false},"description":"One chip content per card, same artwork; does not combine with `encoding`."},"cards":{"type":"array","minItems":2,"maxItems":50,"description":"One artwork (and chip content) per card, for a team; does not combine with the other artwork and encoding fields.","items":{"type":"object","properties":{"front_upload_id":{"type":"string","format":"uuid","description":"An upload_id returned by create_upload or upload_file."},"back_upload_id":{"type":"string","format":"uuid","description":"An upload_id returned by create_upload or upload_file."},"encoding":{"type":"object","properties":{"type":{"type":"string","enum":["uri","text","empty"],"description":"`uri`: an address (in `content`), `text`: a text, `empty`: chip not encoded."},"content":{"type":"string","maxLength":1000,"description":"The full address (https://...) or the text, given by the person."},"lock":{"type":"boolean","description":"Default true: the chip is locked read-only after writing, which cannot be undone."}},"required":["type"],"additionalProperties":false}},"additionalProperties":false}}},"required":["product","quantity"],"additionalProperties":false},"GetOrderStatusArguments":{"type":"object","properties":{"tracking_token":{"type":"string","format":"uuid","description":"The tracking token of the order, from the confirmation e-mail or the tracking page address."}},"required":["tracking_token"],"additionalProperties":false,"description":"get_order_status"}}}}