# QRTap — full capability description for LLMs and AI agents QRTap (https://app.qrtap.kg) is a managed QR code, short link and public-page platform operated in Kyrgyzstan (service language: Russian; API and this file: English). This document is the extended, self-contained description of what an agent can do with the QRTap REST API v1. Русская сводка: QRTap — управляемые QR-коды, короткие ссылки и страницы-визитки. Постоянная короткая ссылка в коде + сменяемое назначение (без перепечатки), сборка публичной страницы клиенту, отзывы и заявки на запись, умные правила редиректа, анонимная статистика сканов, брендированные артефакты, вебхуки в реальном времени, MCP-сервер для ИИ-агентов. Ключ API выпускается владельцем кабинета на платном тарифе: https://app.qrtap.kg/workspace/api ## Core concept Every QRTap code is a stable short URL: https://app.qrtap.kg/{slug}. The QR image always encodes that short URL, never the destination. The destination is a versioned redirect target that can be changed at any time with one PATCH request — printed materials never go stale. All destination versions are kept as an audit history. Privacy invariant: scan analytics are aggregated and anonymous. Visitor IP addresses and user-agent strings are never stored. Bots are excluded from counts. Webhook payloads carry no personal data. ## Authentication - HTTP header: `Authorization: Bearer ` - Keys are per-organization, shown once at issue time, stored only as SHA-256 hashes, revocable (rotation = issue new + revoke old; both can be valid during cutover). - How to get a key: the workspace owner issues (and revokes) keys self-service at https://app.qrtap.kg/workspace/api — requires a paid plan. Help: https://app.qrtap.kg/support - Missing/invalid key: HTTP 401 {"error":"unauthorized"}. ## REST API v1 — endpoints (base https://app.qrtap.kg) Machine-readable spec: https://app.qrtap.kg/openapi.json (OpenAPI 3.1). 1. POST /api/v1/codes — create a managed code. Body: {"target_url" (required, http/https), "label"?, "external_ref"?, "group"?, "design_template_id"?}. Groups: partner | equipment | event_menu | bag_insert | training. Idempotent by external_ref: re-sending the same ref returns the existing code, never a duplicate. Response 201 includes id, slug, short_url (permanent printed link), png_url, svg_url, created_at, updated_at. 2. POST /api/v1/codes/bulk — create up to 500 codes in one call. Per-item results ({"ok":true,...code} or {"ok":false,"external_ref","error"}); one bad item does not fail the batch; safe to retry (idempotent by external_ref). 3. GET /api/v1/codes?group=&external_ref=&limit=&offset=&include=stats — list newest first, filter by group/external_ref, paginate (limit 1..500, default 100). With include=stats every code carries its all-time non-bot scan count — sync all counters in one call. 4. GET /api/v1/codes/{id} — read one code. 5. PATCH /api/v1/codes/{id} — change destination. Body {"target_url"}. The printed QR and short_url never change; a new destination version becomes active immediately. 6. GET /api/v1/codes/{id}/history — every destination the code has pointed at, newest first: {version, target_url, reason, status (active|superseded), activated_at, created_at}. 7. GET /api/v1/codes/{id}/stats?from=&to= — anonymous aggregates: {total, by_day: [{day,count}], by_device: [{device,count}]}. from inclusive, to exclusive (ISO 8601); omit both for all time. Devices: mobile | tablet | desktop | other | unknown. 8. GET /api/v1/groups/{group}/stats?from=&to= — the same aggregates across every code of a group, plus the number of codes: {group, codes, total, by_day, by_device}. 9. GET /api/v1/codes/{id}/qr?format=png|svg|pdf&size=&caption=&errorCorrection= — render the artifact. png: raster 256..3000 px (default 1024), decode-verified before return; svg: true vector (styled when the code has a design template; optional caption drawn under the code); pdf: single-page vector for print. Response header X-QRTap-QA reports branded | downgraded | plain. Captions with promo wording prohibited by Kyrgyz advertising law (Article 17: discounts, bonuses, gifts, promo codes) are refused with 422 before rendering. 10. POST /api/v1/design-templates — create a reusable branded look: {"name" (required), "module_color"? (#rrggbb), "logo"? (data:image/png|jpeg|webp;base64 URI only — never a URL), "caption"?}. The logo is sanitised and re-encoded server-side. Codes reference a template via design_template_id at creation. A decode-QA gate keeps branded codes scannable (logo is shrunk or dropped if it breaks the read). 11. GET /api/v1/design-templates — list templates (logo bytes never echoed). 12. POST /api/v1/webhooks — register an HTTPS endpoint for real-time scan events. Response includes signing_secret exactly once. GET /api/v1/webhooks — list endpoints (no secrets). DELETE /api/v1/webhooks/{id} — deactivate. 13. GET /api/v1/pages — the organization's public pages (business cards served at https://app.qrtap.kg/a/{slug}), newest version first, WITHOUT the content blob: {id, slug, title, segment, theme, status, published, version, public_url, created_at, updated_at}. 14. POST /api/v1/pages — build and publish a page. Body {"content": {...}} where content holds: name (required), segment (master | vrach | psiholog | otzyvy | …), theme (emerald | ocean | plum | terracotta | graphite), specialty, bio, phone, whatsapp, instagram, twogis, bookingUrl, hours, address, licenseNumber, services[{name, price}] (max 12), reviewLinks{twogis, google, yandex, flamp}, socialLinks{tiktok, telegram, website, youtube}. Content is sanitised server-side: unknown keys dropped, phones normalised to +digits, WhatsApp/Instagram rewritten to canonical links, only http(s) URLs survive. At least one contact (phone | whatsapp | instagram | bookingUrl) is required; otherwise 422 invalid_content. Response 201 carries slug and public_url — create a code with target_url = public_url to get a printable card. Repeating the call publishes a NEW VERSION of the SAME page (never a second page) and never changes the slug, so retries are safe and printed codes keep working. 15. GET /api/v1/pages/{slug} — the page including its current content. PATCH /api/v1/pages/{slug} — replace the content (wholesale — read first if you only mean to change one field) and publish a new version. Slug and public_url unchanged. A slug outside your organization answers 404, never 403. 16. GET /api/v1/reviews?status=pending|published|hidden — guest reviews left on your public pages, newest first (max 200), default pending. A review is invisible to the public until it is published, so pending IS the moderation queue. Only the name and text the guest typed are stored — no IP, no user-agent, no identifier. POST /api/v1/reviews/{id}/moderate — body {"action":"publish"|"hide"}; the only way a review reaches the public page. Returns the review in its new state; 404 for a review of another organization. 17. GET /api/v1/bookings?status=new|confirmed|declined — booking requests from your public pages, newest first (max 200); omit status for all. Fields: {id, service, preferred_at, guest_name, guest_phone, status, created_at, updated_at}. preferred_at is FREE TEXT as the guest typed it ("завтра после 14:00") and is deliberately never parsed. POST /api/v1/bookings/{id}/status — body {"status":"confirmed"|"declined"}. 'new' is not accepted (a decided request is never reopened via the API). QRTap does not notify the guest: the business calls the phone number on the request. 18. GET /api/v1/codes/{id}/smart-rules — the code's current conditional redirect rules. PUT /api/v1/codes/{id}/smart-rules — body {"rules":[…]} replaces the set (max 5 rules; [] or null clears it). Each rule is {"if": , "to": ""}. Conditions: {"kind":"device","in":["ios","android","desktop","other"]}; {"kind":"lang","in":["ru","ky"]} (primary language subtags); {"kind":"hours","tz":"Asia/Bishkek","from":"09:00","to":"18:00","days":[1,2,3,4,5]} (to exclusive; from > to crosses midnight; days ISO 1=Mon..7=Sun; omit for every day). On each scan rules are evaluated in order and the FIRST MATCH WINS; if nothing matches — or the stored set is ever unreadable — the redirect falls back to the code's primary destination, so a bad rule can never break a printed code. Evaluation sees only coarse classified signals; raw IP, user-agent and Accept-Language are never stored. Paid plans only: 402 plan_required otherwise. Tenancy rule for all of the above: everything is scoped to the organization of the API key. An id or slug belonging to another organization answers 404, never 403 — foreign identifiers must stay unenumerable. ## Rate limiting Every /api/v1 endpoint allows 120 requests per minute per API key (fixed one-minute window). Over the limit: HTTP 429 {"error":"rate_limited"} with headers Retry-After (seconds until reset), X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset (epoch seconds). A 429'd request had no effect — wait Retry-After and repeat. Webhook management routes (/api/v1/webhooks*) are exempt. Implementation caveat: the counter lives in the memory of the process that served the request, so with several web instances the effective ceiling is 120 x instances. Treat 120/min as the guaranteed floor and always honour Retry-After instead of your own arithmetic. ## Typical agent flow: onboard a customer end to end 1. POST /api/v1/pages with the customer's details -> take public_url from the reply. 2. POST /api/v1/codes {"target_url": public_url, "label": "..."} -> take png_url/svg_url for print, and short_url as the permanent link. 3. Later: GET /api/v1/reviews?status=pending and POST .../moderate to publish the good ones; GET /api/v1/bookings?status=new and POST .../status to confirm or decline. 4. PATCH /api/v1/pages/{slug} whenever the details change — the printed code is untouched. Actions that change what real customers see (publishing/hiding a review, deciding a booking, retargeting a live code, editing a published page) should be confirmed with the human operator before an agent performs them. ## Scan webhook deliveries On every scan, active endpoints receive: POST X-QRTap-Event: scan X-QRTap-Signature: sha256= Content-Type: application/json {"code_id","external_ref","group","ts","device_type"} Verify: hex HMAC-SHA256 of the raw body with your signing_secret must equal the value after "sha256=". Delivery is at-least-once (make handlers idempotent): acknowledge with any 2xx within 8 seconds; failures retry with exponential backoff (about 2^attempt minutes, capped at 12 hours) up to 8 attempts. ## Error model JSON errors: {"error": "", "detail"?: ...}. Codes: unauthorized (401), invalid_request / invalid_url / invalid_id / unknown_design_template / invalid_image (400), invalid_group (400), plan_required (402), not_found (404), invalid_content / invalid_rules / prohibited_content (422), rate_limited (429), qa_failed (500). ## MCP server (Model Context Protocol) QRTap ships a stdio MCP server for Claude Desktop, Claude Code and other MCP clients (npm package: qrtap-mcp). Env config: QRTAP_API_KEY (required), QRTAP_API_URL (default https://app.qrtap.kg). Tools: - qrtap_create_code(url, title?) — create a managed QR code, returns short_url + artifact URLs - qrtap_list_codes(group?, external_ref?, include_stats?) — list codes - qrtap_change_destination(codeId, url) — retarget without reprinting - qrtap_code_stats(codeId, from?, to?) — anonymous scan aggregates - qrtap_create_short_link(url) — managed short link (same object; permanent short_url) - qrtap_create_page(content) — build and publish the customer's public page; returns public_url - qrtap_update_page(slug, content) — replace page content, slug and printed codes unchanged - qrtap_list_pages() — pages with slug, segment, theme, published state, version, public_url - qrtap_list_reviews(status?) — guest reviews; default pending = the moderation queue - qrtap_moderate_review(reviewId, action) — publish or hide one review - qrtap_list_bookings(status?) — booking requests; 'new' is the inbox needing a decision - qrtap_set_booking_status(bookingId, status) — confirm or decline one request - qrtap_set_smart_rules(codeId, rules) — replace a code's conditional redirect rules Setup snippets (Claude Desktop mcpServers JSON, claude mcp add): https://app.qrtap.kg/developers ## Links - Developer docs (human): https://app.qrtap.kg/developers - OpenAPI 3.1: https://app.qrtap.kg/openapi.json - Short index for LLMs: https://app.qrtap.kg/llms.txt - Terms: https://app.qrtap.kg/oferta · Privacy: https://app.qrtap.kg/privacy - Support / API key requests: https://app.qrtap.kg/support