{"openapi":"3.1.0","info":{"title":"Tuelia Agent API","version":"1.0.0","description":"Installation and consent diagnostics. Results are technical observations, not a legal certification."},"servers":[{"url":"/"}],"paths":{"/api/agent/manifest":{"get":{"operationId":"manifest","security":[],"responses":{"200":{"description":"Discovery","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/api/agent/bootstrap":{"post":{"operationId":"bootstrap","summary":"Create or reuse this user’s banner for a canonical site URL","security":[{"apiKey":[]}],"description":"Requires write scope and an active plan/trial. Strips query and fragment, normalizes trailing slash. Same-user concurrent retries serialize in PostgreSQL. Existing settings are preserved. Script publication failure can be retried with the same URL.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BootstrapInput"}}}},"responses":{"200":{"description":"Existing banner","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BootstrapResult"}}}},"201":{"description":"Created banner","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BootstrapResult"}}}},"400":{"description":"Request rejected or unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Request rejected or unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Request rejected or unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Request rejected or unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Request rejected or unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Request rejected or unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Request rejected or unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Request rejected or unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/banners/{id}/install-code":{"get":{"operationId":"install_code","summary":"Read snippets for supported stacks","security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-f0-9]{32}$"}}],"responses":{"200":{"description":"Result","content":{"application/json":{"schema":{"type":"object","properties":{"scriptUrl":{"type":"string"},"html":{"type":"string"},"nextjs":{"type":"string"},"nextjsPages":{"type":"string"},"react":{"type":"string"},"wordpress":{"type":"string"}}}}}},"400":{"description":"Request rejected or unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Request rejected or unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Request rejected or unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Request rejected or unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Request rejected or unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Request rejected or unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Request rejected or unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Request rejected or unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/banners/{id}/verify":{"post":{"operationId":"verify","summary":"Verify the banner runtime, including SPA rendering","security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-f0-9]{32}$"}}],"responses":{"200":{"description":"Result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Verification"}}}},"400":{"description":"Request rejected or unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Request rejected or unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Request rejected or unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Request rejected or unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Request rejected or unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Request rejected or unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Request rejected or unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Request rejected or unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/scanner/{id}/scan":{"post":{"operationId":"scan","summary":"Start an asynchronous scan","security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-f0-9]{32}$"}}],"responses":{"202":{"description":"Scan started or already busy; inspect status and poll history","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScanStart"}}}},"400":{"description":"Request rejected or unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Request rejected or unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Request rejected or unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Request rejected or unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Request rejected or unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Request rejected or unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Request rejected or unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Request rejected or unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/scanner/{id}/history":{"get":{"operationId":"scan_history","summary":"Read the latest ten scans, newest first","security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-f0-9]{32}$"}}],"responses":{"200":{"description":"Result","content":{"application/json":{"schema":{"type":"array","maxItems":10,"items":{"$ref":"#/components/schemas/Scan"}}}}},"400":{"description":"Request rejected or unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Request rejected or unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Request rejected or unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Request rejected or unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Request rejected or unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Request rejected or unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Request rejected or unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Request rejected or unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/banners/{id}/compliance":{"get":{"operationId":"compliance","summary":"Read current report and next actions","security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-f0-9]{32}$"}}],"responses":{"200":{"description":"Result","content":{"application/json":{"schema":{"type":"object","properties":{"score":{"type":"number"},"issues":{"type":"array","items":{"type":"object"}},"summary":{"type":"object"}}}}}},"400":{"description":"Request rejected or unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Request rejected or unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Request rejected or unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Request rejected or unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Request rejected or unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Request rejected or unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Request rejected or unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Request rejected or unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"Tuelia ck_ API key. Mutating operations require write scope."}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string"},"code":{"type":"string"}}},"BootstrapInput":{"type":"object","additionalProperties":false,"required":["url"],"properties":{"url":{"type":"string","format":"uri","maxLength":2048},"name":{"type":"string","minLength":1,"maxLength":100},"language":{"type":"string","default":"en","description":"Supported language code from GET /api/languages"},"stack":{"type":"string","enum":["html","wordpress","nextjs","nextjs-pages","react","nuxt","shopify","gtm","angular"],"default":"html"}}},"BootstrapResult":{"type":"object","required":["bannerId","created","url","scriptTag","installCodeUrl","verifyUrl","scanUrl","complianceUrl","policyUrls","nextSteps"],"properties":{"plan":{"type":"object","properties":{"tier":{"type":"string","enum":["free","pro","agency","legacy","blocked"]},"status":{"type":"string"},"siteLimit":{"type":["integer","null"],"minimum":0},"canWrite":{"type":"boolean"},"trialEndsAt":{"type":["string","null"],"format":"date-time"},"features":{"type":"object","properties":{"removeBranding":{"type":"boolean"}}}}},"bannerId":{"type":"string","pattern":"^[a-f0-9]{32}$"},"created":{"type":"boolean"},"url":{"type":"string"},"stack":{"type":"string","enum":["html","wordpress","nextjs","nextjs-pages","react","nuxt","shopify","gtm","angular"]},"scriptTag":{"type":"string"},"installCodeUrl":{"type":"string"},"verifyUrl":{"type":"string"},"scanUrl":{"type":"string"},"scanHistoryUrl":{"type":"string"},"complianceUrl":{"type":"string"},"policyUrls":{"type":"object","properties":{"privacy":{"type":["string","null"]},"cookies":{"type":["string","null"]}}},"nextSteps":{"type":"array","items":{"type":"string"}}}},"Verification":{"type":"object","required":["installed","status","checkedAt"],"properties":{"installed":{"type":"boolean"},"status":{"type":"string","enum":["installed","not_found","unreachable"]},"url":{"type":"string"},"checkedAt":{"type":"string","format":"date-time"},"evidence":{"type":"object"}}},"ScanStart":{"type":"object","properties":{"status":{"type":"string"},"scan":{"anyOf":[{"$ref":"#/components/schemas/Scan"},{"type":"null"}]}}},"Scan":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string"},"scannedAt":{"type":"string","format":"date-time"},"pagesScanned":{"type":"integer"},"cookiesFound":{"type":"integer"},"preConsentAuditStatus":{"type":"string"}}}}}}