{"openapi":"3.1.0","info":{"title":"GigaQR API","version":"1.0.0","summary":"Create, update, and track QR codes programmatically.","description":"REST API for programmatic QR code creation, management, and scan\nanalytics. Authenticated with a **secret API key** that starts with\n`sk_live_`. Create one under **Dashboard → Developer** (Pro plan or\nhigher).\n\nRate limits scale with your plan. Rate-limit state is returned on\nevery response in `x-ratelimit-limit` and `x-ratelimit-remaining`\nheaders. A 429 response means you've hit the per-minute cap — wait\nand retry.\n\nBase URL: `https://gigaqr.com/api/v1`","contact":{"name":"GigaQR support","url":"https://gigaqr.com/contact"},"license":{"name":"Commercial","url":"https://gigaqr.com/terms"}},"servers":[{"url":"https://gigaqr.com/api/v1","description":"Production"}],"tags":[{"name":"QR codes","description":"Create, read, update, and delete QR codes."},{"name":"Scans","description":"Read scan events and export analytics."},{"name":"Bulk","description":"Create many QR codes in a single job from CSV or JSON."},{"name":"Folders","description":"Organize QR codes into folders."}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"sk_live_...","description":"Pass your secret API key as `Authorization: Bearer sk_live_...`. Create one in Dashboard → Developer."}},"schemas":{"QrType":{"type":"string","description":"Supported QR destination types. `url` is available on every plan.","enum":["url","applink","vcard","sms","whatsapp","email","location","telegram","gigapage"]},"QrStatus":{"type":"string","enum":["active","paused"]},"Shape":{"type":"string","enum":["square","circle"]},"ColorConfig":{"type":"object","description":"Optional styling for the rendered QR. Not all fields apply on every plan.","additionalProperties":true,"properties":{"dotsColor":{"type":"string","description":"CSS color for QR modules."},"backgroundColor":{"type":"string"},"cornerColor":{"type":"string"}}},"QrDataUrl":{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","const":"url"},"url":{"type":"string","description":"Destination URL. Bare hostnames (e.g. `example.com`) are accepted — we prepend `https://`."}}},"QrCreateInput":{"type":"object","required":["qrType","data"],"properties":{"name":{"type":"string","maxLength":200,"description":"Human label shown in the dashboard."},"qrType":{"$ref":"#/components/schemas/QrType"},"data":{"description":"Type-specific payload. Discriminated on `data.type`, which must match `qrType`. See `QrDataUrl` for the URL shape.","oneOf":[{"$ref":"#/components/schemas/QrDataUrl"}]},"folderId":{"type":"string","format":"uuid","nullable":true},"tags":{"type":"array","items":{"type":"string"}},"templateId":{"type":"string","format":"uuid","description":"Apply a saved design template. Create templates in Dashboard → Developer → Templates."},"colorConfig":{"$ref":"#/components/schemas/ColorConfig"},"shape":{"$ref":"#/components/schemas/Shape"},"advanced":{"type":"object","additionalProperties":true,"description":"Advanced rendering options (reserved)."}}},"QrUpdateInput":{"type":"object","description":"All fields optional. Only provided fields are updated.","properties":{"name":{"type":"string","maxLength":200,"nullable":true},"data":{"$ref":"#/components/schemas/QrDataUrl"},"folderId":{"type":"string","format":"uuid","nullable":true},"tags":{"type":"array","items":{"type":"string"}},"status":{"$ref":"#/components/schemas/QrStatus"},"colorConfig":{"$ref":"#/components/schemas/ColorConfig"},"shape":{"$ref":"#/components/schemas/Shape"},"templateId":{"type":"string","format":"uuid"}}},"Qr":{"type":"object","required":["id","hash","qrType","status","scanUrl"],"properties":{"id":{"type":"string","format":"uuid"},"hash":{"type":"string","description":"Short hash used in the scan URL."},"qrType":{"$ref":"#/components/schemas/QrType"},"name":{"type":"string","nullable":true},"folderId":{"type":"string","format":"uuid","nullable":true},"tags":{"type":"array","items":{"type":"string"}},"status":{"$ref":"#/components/schemas/QrStatus"},"scanUrl":{"type":"string","format":"uri","description":"Short URL printed/encoded into the QR."},"createdAt":{"type":"string","format":"date-time","nullable":true},"updatedAt":{"type":"string","format":"date-time","nullable":true}}},"QrList":{"type":"object","required":["items"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Qr"}},"nextCursor":{"type":"string","nullable":true,"description":"Pass as `cursor` query param to fetch the next page. `null` means end of list."}}},"ScanEvent":{"type":"object","properties":{"id":{"type":"string"},"qrId":{"type":"string","format":"uuid"},"scannedAt":{"type":"string","format":"date-time"},"country":{"type":"string","nullable":true},"city":{"type":"string","nullable":true},"device":{"type":"string","nullable":true},"os":{"type":"string","nullable":true},"userAgent":{"type":"string","nullable":true}}},"ScanListResponse":{"type":"object","properties":{"stats":{"type":"object","properties":{"total":{"type":"integer"},"uniqueVisitors":{"type":"integer"}}},"events":{"type":"array","items":{"$ref":"#/components/schemas/ScanEvent"}}}},"BulkJob":{"type":"object","required":["jobId","status","totalItems"],"properties":{"jobId":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["queued","processing","completed","failed"]},"totalItems":{"type":"integer"},"successCount":{"type":"integer"},"failureCount":{"type":"integer"},"createdAt":{"type":"string","format":"date-time"},"completedAt":{"type":"string","format":"date-time","nullable":true}}},"BulkItem":{"type":"object","properties":{"rowIndex":{"type":"integer"},"status":{"type":"string","enum":["pending","succeeded","failed"]},"createdQrId":{"type":"string","format":"uuid","nullable":true},"createdQrHash":{"type":"string","nullable":true},"error":{"type":"string","nullable":true}}},"Folder":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"createdAt":{"type":"string","format":"date-time"}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable description of the problem."},"code":{"type":"string","description":"Machine-readable error category.","enum":["invalid_input","plan_limit","rate_limited","unauthorized","forbidden","not_found","internal"]}}}},"responses":{"Unauthorized":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Rate limit exceeded. See `x-ratelimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"BadRequest":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"PlanLimit":{"description":"Plan limit reached. Upgrade to continue.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":{"QrIdPath":{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"QR code ID."}}},"security":[{"bearerAuth":[]}],"paths":{"/qr":{"get":{"tags":["QR codes"],"summary":"List QR codes","description":"Paginated cursor-based list. Filter by folder, tag, or type.","parameters":[{"name":"folderId","in":"query","schema":{"type":"string"}},{"name":"tag","in":"query","schema":{"type":"string"}},{"name":"qrType","in":"query","schema":{"$ref":"#/components/schemas/QrType"}},{"name":"cursor","in":"query","schema":{"type":"string"},"description":"Opaque cursor from a prior response."},{"name":"limit","in":"query","schema":{"type":"integer","default":25,"maximum":100}}],"responses":{"200":{"description":"Paginated list of QRs owned by the key's workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QrList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"tags":["QR codes"],"summary":"Create a QR code","description":"Creates a new QR. The `data.url` field accepts bare hostnames (e.g. `example.com`) — we normalize them to `https://example.com` before storing.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QrCreateInput"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Qr"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanLimit"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/qr/{id}":{"parameters":[{"$ref":"#/components/parameters/QrIdPath"}],"get":{"tags":["QR codes"],"summary":"Retrieve a QR code","responses":{"200":{"description":"QR","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Qr"}}}},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["QR codes"],"summary":"Update a QR code","description":"Any field that can be set on create can be updated here, including the destination. Existing printed/shared QRs keep working; scans are re-routed immediately.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QrUpdateInput"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Qr"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"}}},"delete":{"tags":["QR codes"],"summary":"Soft-delete a QR code","description":"The QR stops resolving and is hidden from the dashboard. Scan history is preserved.","responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/qr/{id}/scans":{"parameters":[{"$ref":"#/components/parameters/QrIdPath"}],"get":{"tags":["Scans"],"summary":"List scan events and stats for a QR","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","default":50,"maximum":500}}],"responses":{"200":{"description":"Stats + recent events (newest first).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScanListResponse"}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/scans/export":{"get":{"tags":["Scans"],"summary":"Export scan events as CSV or NDJSON","description":"Streams scan events for data-warehouse or BI import. Filter by QR, date range, and output format.","parameters":[{"name":"qrId","in":"query","schema":{"type":"string"},"description":"Limit to a single QR."},{"name":"from","in":"query","schema":{"type":"string","format":"date"}},{"name":"to","in":"query","schema":{"type":"string","format":"date"}},{"name":"format","in":"query","schema":{"type":"string","enum":["csv","ndjson"],"default":"csv"}}],"responses":{"200":{"description":"Stream of scan events.","content":{"text/csv":{"schema":{"type":"string"}},"application/x-ndjson":{"schema":{"type":"string"}}}}}}},"/qr/bulk":{"post":{"tags":["Bulk"],"summary":"Create a bulk job from CSV or JSON","description":"Accepts either `text/csv` (see Dashboard → Bulk for the column spec) or `application/json` with an `items` array of `QrCreateInput` objects. Returns a job id — poll `/qr/bulk/{jobId}` until `status === \"completed\"`, then download the per-row results via `/qr/bulk/{jobId}/download`.","requestBody":{"required":true,"content":{"text/csv":{"schema":{"type":"string","example":"name,url\nHome,https://example.com\nAbout,https://example.com/about"}},"application/json":{"schema":{"type":"object","required":["items"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/QrCreateInput"},"maxItems":10000}}}}}},"responses":{"202":{"description":"Job accepted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkJob"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/qr/bulk/{jobId}":{"parameters":[{"name":"jobId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["Bulk"],"summary":"Get bulk job status","responses":{"200":{"description":"Job state with progress counters.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/BulkJob"},{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/BulkItem"}}}}]}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/qr/bulk/{jobId}/download":{"parameters":[{"name":"jobId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["Bulk"],"summary":"Download bulk job results as CSV","description":"Returns one row per input row with the created `qr_id`, the public `scan_url`, and any per-row error. Safe to call before the job completes — returns partial results.","responses":{"200":{"description":"CSV attachment","content":{"text/csv":{"schema":{"type":"string"},"example":"row,status,qr_id,scan_url,error\n0,succeeded,a1b2…,https://gigaqr.com/scan/abc,\n"}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/folders":{"get":{"tags":["Folders"],"summary":"List folders","responses":{"200":{"description":"Folders in the caller's workspace.","content":{"application/json":{"schema":{"type":"object","properties":{"folders":{"type":"array","items":{"$ref":"#/components/schemas/Folder"}}}}}}}}},"post":{"tags":["Folders"],"summary":"Create a folder","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string"}}}}}},"responses":{"201":{"description":"Folder created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Folder"}}}}}}}}}