{"openapi":"3.1.0","info":{"title":"NowPinned Integrations API","version":"1.0.0","description":"Publish files to permanent NowPinned links from scripts and AI assistants. Authenticate with a dashboard API key or OAuth 2.1 bearer token. Create and publishing operations require an Idempotency-Key header. The MCP server at /api/mcp provides the same capabilities."},"servers":[{"url":"https://manage.nowpinned.com"}],"security":[{"apiKey":[]},{"oauth2":[]}],"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"API key from the dashboard Integrations page (pin_live_…)."},"oauth2":{"type":"oauth2","description":"OAuth 2.1 with PKCE via NowPinned sign-in. MCP clients discover this automatically from /.well-known/oauth-protected-resource.","flows":{"authorizationCode":{"authorizationUrl":"https://zadgqwvmijqybcbfwaan.supabase.co/auth/v1/oauth/authorize","tokenUrl":"https://zadgqwvmijqybcbfwaan.supabase.co/auth/v1/oauth/token","scopes":{"openid":"Confirm your identity","profile":"See your basic profile"}}}}},"schemas":{"ErrorEnvelope":{"type":"object","required":["success","error"],"properties":{"success":{"type":"boolean","const":false},"error":{"type":"string","description":"Human-readable message."},"code":{"type":"string","description":"Stable error code, e.g. UNAUTHORISED, FEATURE_GATED, VALIDATION_FAILED, NOT_FOUND, CONFLICT, PAYLOAD_TOO_LARGE, RATE_LIMITED."}}},"Site":{"type":"object","required":["id","name","slug"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"slug":{"type":"string"}}},"Pin":{"type":"object","required":["id","site_id","display_name","slug","type","public_url","inbound_email_address","active_version_id","folder","show_on_pinboard","pinned_position","updated_at"],"properties":{"id":{"type":"string","format":"uuid"},"site_id":{"type":"string","format":"uuid"},"display_name":{"type":"string"},"slug":{"type":"string"},"type":{"type":["string","null"]},"public_url":{"type":"string","format":"uri","description":"Permanent public viewer URL for this pin. The URL never changes across versions."},"inbound_email_address":{"type":"string"},"active_version_id":{"type":["string","null"],"format":"uuid"},"folder":{"description":"The folder this pin is in, or null.","oneOf":[{"type":"object","required":["id","name"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}}},{"type":"null"}]},"show_on_pinboard":{"type":"boolean","deprecated":true,"description":"Deprecated and always false. The Pinboard feature has been retired and its storage removed, so this field carries no information: reads always return false, and writes that include it are accepted and ignored rather than rejected. It is retained solely so existing clients that require the key keep decoding, and will be removed in a future version."},"pinned_position":{"type":["integer","null"],"description":"0-based dashboard pin-to-top ordering; null when the pin is not pinned to the top of the site list."},"updated_at":{"type":"string","format":"date-time"}}},"Version":{"type":"object","required":["id","pin_id","version_number","file_name","file_url","file_type","file_size_bytes","status","change_label","internal_label","created_at"],"properties":{"id":{"type":"string","format":"uuid"},"pin_id":{"type":"string","format":"uuid"},"version_number":{"type":"integer"},"file_name":{"type":"string"},"file_url":{"type":"string"},"file_type":{"type":"string","enum":["pdf","docx","xlsx","csv","image","html"]},"file_size_bytes":{"type":"integer"},"status":{"type":"string","description":"staged | live | archived | scheduled | pending_approval | rejected"},"change_label":{"type":["string","null"]},"internal_label":{"type":["string","null"],"description":"Private dashboard-only label. Never shown to viewers."},"created_at":{"type":"string","format":"date-time"}}},"VersionHistoryItem":{"type":"object","required":["id","version_number","status","is_live","file_name","file_type","internal_label","created_at"],"properties":{"id":{"type":"string","format":"uuid"},"version_number":{"type":"integer"},"status":{"type":"string","description":"staged | live | archived | scheduled | pending_approval | rejected"},"is_live":{"type":"boolean","description":"True for the version currently served at the public URL."},"file_name":{"type":"string"},"file_type":{"type":"string","enum":["pdf","docx","xlsx","csv","image","html"]},"change_label":{"type":["string","null"]},"internal_label":{"type":["string","null"],"description":"Private dashboard-only label. Never shown to viewers."},"created_at":{"type":"string","format":"date-time"}}},"Folder":{"type":"object","required":["id","name"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}}},"SecureAccess":{"type":"object","required":["pin_id","enabled","access_rule","contact_fields","pin_required","remember_days","source","has_pin_override"],"properties":{"pin_id":{"type":"string","format":"uuid"},"enabled":{"type":"boolean"},"access_rule":{"type":"string","enum":["contact","pin","pin_contact"]},"contact_fields":{"type":"array","items":{"type":"string","enum":["name","email","phone"]}},"pin_required":{"type":"boolean"},"remember_days":{"type":"integer"},"source":{"type":"string","enum":["pin_override","folder","default"]},"has_pin_override":{"type":"boolean"}}},"SiteSummary":{"type":"object","required":["site_id","pin_count","folder_count","latest_change_at"],"properties":{"site_id":{"type":"string","format":"uuid"},"pin_count":{"type":"integer"},"folder_count":{"type":"integer"},"latest_change_at":{"type":["string","null"],"format":"date-time","description":"updated_at of the most recently changed pin, or null when the site has no pins."}},"description":"Cheap change cursor for a site. Poll this and only refetch the full pin list when it changes."},"Snippet":{"type":"object","required":["id","device_id","title","category","type","content","is_starred","created_at","updated_at","usage_count","last_used","schema_version","deleted_at","contract_version"],"properties":{"id":{"type":"string","format":"uuid","description":"Client-generated snippet id."},"device_id":{"type":"string"},"title":{"type":"string"},"category":{"type":"string"},"type":{"type":"string","enum":["text","link","image","video"]},"content":{"type":"string"},"is_starred":{"type":"boolean"},"created_at":{"type":"string","format":"date-time","description":"Client-authoritative."},"updated_at":{"type":"string","format":"date-time","description":"Client-authoritative last-write-wins timestamp."},"usage_count":{"type":"integer","minimum":0},"last_used":{"type":["string","null"],"format":"date-time"},"schema_version":{"type":"integer"},"deleted_at":{"type":["string","null"],"format":"date-time","description":"Soft-delete tombstone; non-null rows are deleted snippets kept for sync."},"contract_version":{"type":"integer"}},"description":"Personal reusable text block owned by the signed-in user (not a site). Synced by the NowPinned native apps with last-write-wins semantics."},"FileContentExcerpts":{"type":"object","properties":{"kind":{"type":"string","enum":["excerpts"]},"version_id":{"type":"string","format":"uuid"},"pin_id":{"type":"string","format":"uuid"},"file_name":{"type":"string"},"file_type":{"type":"string"},"size_bytes":{"type":"integer"},"encoding":{"type":"string","enum":["utf-8"]},"total_lines":{"type":"integer"},"excerpts":{"type":"array","items":{"type":"object","properties":{"start_line":{"type":"integer"},"end_line":{"type":"integer"},"text":{"type":"string","description":"Exact file text — reusable as old_string for editHtml."},"truncated":{"type":"boolean"}}}},"query":{"type":"string"},"total_matches":{"type":"integer"},"returned_matches":{"type":"integer"}}},"QualityReport":{"type":"object","required":["ruleset_version","summary","findings"],"description":"Deterministic quality findings for an HTML document. Advisory — findings never block staging or publishing.","properties":{"ruleset_version":{"type":"string","description":"Ruleset id: 'q-v2' for staging checks, 'review-v2' for the deep review."},"summary":{"type":"object","required":["errors","warnings","notes"],"properties":{"errors":{"type":"integer"},"warnings":{"type":"integer"},"notes":{"type":"integer"}}},"findings":{"type":"array","items":{"type":"object","required":["code","severity","message"],"properties":{"code":{"type":"string"},"severity":{"type":"string","enum":["error","warn","info"]},"message":{"type":"string"},"evidence":{"type":"string"},"fix_hint":{"type":"string"}}}}}},"BoardSummary":{"type":"object","required":["id","name","slug","permanent_link","status","visibility","public_access","pin_count","updated_at"],"description":"A Board: a permanent link to a relationship, holding the pins, links, contacts, checklists and notices that relationship runs on.","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"slug":{"type":"string"},"permanent_link":{"type":"string","description":"The board's permanent public URL. Never changes."},"status":{"type":"string"},"visibility":{"type":"string"},"public_access":{"type":"string"},"pin_count":{"type":"integer"},"updated_at":{"type":"string","format":"date-time"}}},"BoardParty":{"type":"object","required":["id","name","position","is_owner"],"description":"One side of the relationship. is_owner marks the owning site's own side.","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"position":{"type":"integer"},"is_owner":{"type":"boolean"}}},"BoardBlock":{"type":"object","required":["kind","id","zone","position","label"],"description":"One block of the board's stream, flattened across the five kinds and ordered by zone then position. Kind-specific fields appear only on the kind that has them.","properties":{"kind":{"type":"string","enum":["pin","link","text","qr","ack"]},"id":{"type":"string","format":"uuid","description":"The block row id — what remove ops and reorder address."},"zone":{"type":"string","enum":["main","side"]},"position":{"type":"integer"},"label":{"type":"string"},"pin_id":{"type":"string","format":"uuid","description":"pin blocks only."},"url":{"type":"string","description":"link blocks only."},"body":{"type":"string","description":"text blocks only."},"qr_target":{"type":"string","description":"qr blocks only: what the embedded QR points at."},"qr_archived":{"type":"boolean","description":"qr blocks only: archived QRs are hidden on the public page."}}},"BoardChecklist":{"type":"object","required":["id","title","items"],"properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"items":{"type":"array","items":{"type":"object","required":["id","label","done","done_by_name","assigned_party_id"],"properties":{"id":{"type":"string","format":"uuid"},"label":{"type":"string"},"done":{"type":"boolean"},"done_by_name":{"type":"string"},"assigned_party_id":{"type":["string","null"],"description":"The side that owes this item. Null means anyone."}}}}}},"BoardContact":{"type":"object","required":["id","party_id","name","role_title","email","phone","organisation_label"],"properties":{"id":{"type":"string","format":"uuid"},"party_id":{"type":"string","format":"uuid"},"name":{"type":"string"},"role_title":{"type":"string"},"email":{"type":"string"},"phone":{"type":"string"},"organisation_label":{"type":"string"}}},"BoardNotice":{"type":"object","required":["id","title","body","author_name","posted_at"],"properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"body":{"type":"string"},"author_name":{"type":"string"},"posted_at":{"type":"string","format":"date-time"},"expires_at":{"type":["string","null"],"format":"date-time"}}},"BoardDetail":{"allOf":[{"$ref":"#/components/schemas/BoardSummary"},{"type":"object","required":["description","parties","blocks","checklists","contacts","notices","capacity"],"properties":{"description":{"type":"string"},"parties":{"type":"array","items":{"$ref":"#/components/schemas/BoardParty"}},"blocks":{"type":"array","items":{"$ref":"#/components/schemas/BoardBlock"}},"checklists":{"type":"array","items":{"$ref":"#/components/schemas/BoardChecklist"}},"contacts":{"type":"array","items":{"$ref":"#/components/schemas/BoardContact"}},"notices":{"type":"array","description":"Active notices only — torn-off and expired ones are history, not state.","items":{"$ref":"#/components/schemas/BoardNotice"}},"capacity":{"type":"object","description":"Used/cap pairs, so a cap can be seen coming instead of discovered.","properties":{"pins":{"type":"object","required":["used","cap"],"properties":{"used":{"type":"integer"},"cap":{"type":"integer"}}},"links":{"type":"object","required":["used","cap"],"properties":{"used":{"type":"integer"},"cap":{"type":"integer"}}},"parties":{"type":"object","required":["used","cap"],"properties":{"used":{"type":"integer"},"cap":{"type":"integer"}}},"people_per_party":{"type":"object","required":["cap"],"description":"Contacts are capped PER SIDE, so there is no board-wide used count.","properties":{"cap":{"type":"integer"}}},"texts":{"type":"object","required":["used","cap"],"properties":{"used":{"type":"integer"},"cap":{"type":"integer"}}},"checklists":{"type":"object","required":["used","cap"],"properties":{"used":{"type":"integer"},"cap":{"type":"integer"}}},"qr_blocks":{"type":"object","required":["used","cap"],"properties":{"used":{"type":"integer"},"cap":{"type":"integer"}}},"active_notices":{"type":"object","required":["used","cap"],"properties":{"used":{"type":"integer"},"cap":{"type":"integer"}}},"ack_block":{"type":"object","required":["used","cap"],"properties":{"used":{"type":"integer"},"cap":{"type":"integer"}}}}}}}]},"BoardOpReport":{"type":"object","required":["applied","results"],"description":"What a batch did. Ops stop at the first failure: earlier ops are applied and reported ok, the failure carries its own message and code, and later ops are skipped unattempted. Applied ops are NOT rolled back — the skipped list is what to resend.","properties":{"applied":{"type":"integer"},"results":{"type":"array","items":{"type":"object","required":["index","op","status"],"properties":{"index":{"type":"integer"},"op":{"type":"string"},"status":{"type":"string","enum":["ok","failed","skipped"]},"id":{"type":["string","null"],"description":"The created/updated row id, on status 'ok'."},"error":{"type":"string","description":"Failure message, on status 'failed'."},"code":{"type":["string","null"],"description":"Failure code, on status 'failed'."}}}}}},"BoardActivityPage":{"type":"object","required":["entries","next_cursor"],"properties":{"entries":{"type":"array","items":{"type":"object","required":["kind","actor_name","actor_party_name","subject","created_at"],"properties":{"kind":{"type":"string"},"actor_name":{"type":"string"},"actor_party_name":{"type":["string","null"]},"subject":{"type":"string"},"created_at":{"type":"string","format":"date-time"}}}},"next_cursor":{"type":["string","null"],"description":"Pass back as ?cursor= for the next page. Null on the last page."}}},"DesignGuidelineSummary":{"type":"object","required":["id","name","description","revision_number","is_default","updated_at"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":"string"},"revision_number":{"type":"integer"},"is_default":{"type":"boolean","description":"True when this guideline is the site default used for unselected reads."},"updated_at":{"type":"string","format":"date-time"}}},"DesignGuidelineDetail":{"type":"object","required":["id","name","revision_number","is_default","design_md"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":"string"},"revision_number":{"type":"integer"},"is_default":{"type":"boolean"},"updated_at":{"type":"string","format":"date-time"},"design_md":{"type":"string","description":"The guideline serialised as a DESIGN.md document: brand colours/typography in YAML frontmatter, doctrine in the body, and a Do's and Don'ts quality contract. Read it before generating HTML and follow it. Style only — document content comes from the conversation."},"assets":{"type":"array","description":"Brand assets this guideline references (descriptors only, no bytes). Place images with <img data-pinned-asset=\"{id}\">; name font families in CSS. Bytes inline at stage time when design_guideline_id is passed to pinned_stage_html.","items":{"type":"object","required":["id","role","alt_text","usage_guidance","font_family"],"properties":{"id":{"type":"string","format":"uuid"},"role":{"type":"string","enum":["logo","image","font"]},"alt_text":{"type":"string"},"usage_guidance":{"type":"string"},"font_family":{"type":["string","null"],"description":"CSS family name for fonts; null for images."}}}}}},"DocumentTemplate":{"type":"object","required":["id","name","category","purpose","sections","source"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"category":{"type":"string"},"purpose":{"type":"string"},"source":{"type":"string","enum":["managed"]},"recommended_industries":{"type":"array","items":{"type":"string"}},"sections":{"type":"array","items":{"type":"object","required":["id","title","purpose","requirement"],"properties":{"id":{"type":"string"},"title":{"type":"string"},"purpose":{"type":"string"},"requirement":{"type":"string","enum":["required","optional","repeatable"]}}}},"hierarchy_guidance":{"type":"string"},"responsive_guidance":{"type":"string"}}},"DocumentTemplateSummary":{"type":"object","required":["id","name","category","purpose","source"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"category":{"type":"string"},"purpose":{"type":"string"},"source":{"type":"string","enum":["managed","custom"],"description":"'managed' entries come from the code-owned library (slug ids); 'custom' entries are the site's own saved rule files (uuid ids)."},"recommended_industries":{"type":"array","items":{"type":"string"},"description":"Managed templates only."}}},"CustomDocumentTemplate":{"type":"object","required":["id","name","category","purpose","source","body_md"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"category":{"type":"string"},"purpose":{"type":"string"},"source":{"type":"string","enum":["custom"]},"body_md":{"type":"string","description":"The rules markdown the site saved — follow it verbatim."}}}}},"paths":{"/api/v1/openapi.json":{"get":{"security":[],"operationId":"getOpenApiDocument","summary":"Get the public OpenAPI 3.1 contract","responses":{"200":{"description":"OpenAPI document.","content":{"application/json":{"schema":{"type":"object","required":["openapi","info","paths"],"properties":{"openapi":{"type":"string"},"info":{"type":"object","additionalProperties":true},"paths":{"type":"object","additionalProperties":true}}}}}}}}},"/api/v1/readme":{"get":{"security":[],"operationId":"getPinnedReadme","summary":"Get the full NowPinned operating guide for developers and AI assistants","responses":{"200":{"description":"Full operating guide.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"object","required":["readme"],"properties":{"readme":{"type":"string"}}}}}}}}}}},"/api/v1/design-guidelines":{"get":{"operationId":"listDesignGuidelines","summary":"List a site's Design Guidelines (reusable visual systems)","parameters":[{"name":"site_id","in":"query","required":false,"schema":{"type":"string","format":"uuid"},"description":"Optional site. API keys are already site-scoped."}],"responses":{"200":{"description":"Guideline summaries plus the site default pointer.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"object","required":["site_id","guidelines","default_design_guideline_id"],"properties":{"site_id":{"type":"string","format":"uuid"},"guidelines":{"type":"array","items":{"$ref":"#/components/schemas/DesignGuidelineSummary"}},"default_design_guideline_id":{"type":["string","null"],"format":"uuid"}}}}}}}},"400":{"description":"Validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid, expired, or revoked API key/OAuth bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"The principal cannot access this site or the requested feature is not included in its plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested site, pin, version, or folder was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict, including reuse of an idempotency key with changed input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"Request body exceeds the route limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limited. Respect the Retry-After response header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/design-guidelines/{design_guideline_id}":{"get":{"operationId":"getDesignGuideline","summary":"Read one Design Guideline as a DESIGN.md document","parameters":[{"name":"design_guideline_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The brand serialised as DESIGN.md (frontmatter + doctrine + Do's and Don'ts). Apply to appearance only; document content comes from the conversation.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"object","required":["guideline"],"properties":{"guideline":{"$ref":"#/components/schemas/DesignGuidelineDetail"}}}}}}}},"400":{"description":"Validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid, expired, or revoked API key/OAuth bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"The principal cannot access this site or the requested feature is not included in its plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested site, pin, version, or folder was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict, including reuse of an idempotency key with changed input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"Request body exceeds the route limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limited. Respect the Retry-After response header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/document-templates":{"get":{"operationId":"listDocumentTemplates","summary":"List document Templates: the managed library plus the site's custom templates","parameters":[{"name":"site_id","in":"query","required":false,"schema":{"type":"string","format":"uuid"},"description":"Optional site (custom templates are per-site). API keys are already site-scoped."}],"responses":{"200":{"description":"Template summaries.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"object","required":["templates"],"properties":{"templates":{"type":"array","items":{"$ref":"#/components/schemas/DocumentTemplateSummary"}}}}}}}}},"400":{"description":"Validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid, expired, or revoked API key/OAuth bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"The principal cannot access this site or the requested feature is not included in its plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested site, pin, version, or folder was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict, including reuse of an idempotency key with changed input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"Request body exceeds the route limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limited. Respect the Retry-After response header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/document-templates/{template_id}":{"get":{"operationId":"getDocumentTemplate","summary":"Read one document Template with sections and guidance","parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Full template definition. Managed (source 'managed'): sections plus hierarchy and responsive guidance. Custom (source 'custom'): the site's rules markdown as body_md.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"object","required":["template"],"properties":{"template":{"oneOf":[{"$ref":"#/components/schemas/DocumentTemplate"},{"$ref":"#/components/schemas/CustomDocumentTemplate"}]}}}}}}}},"400":{"description":"Validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid, expired, or revoked API key/OAuth bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"The principal cannot access this site or the requested feature is not included in its plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested site, pin, version, or folder was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict, including reuse of an idempotency key with changed input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"Request body exceeds the route limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limited. Respect the Retry-After response header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/auth/otp":{"post":{"security":[],"operationId":"sendNativeOtp","summary":"Send a sign-in code for the NowPinned native apps","description":"Unauthenticated front door for native sign-in. Sends the one-time code server-side (the web login uses Turnstile instead). Provide exactly one of email (may create an account) or E.164 phone (login-only). Rate limited per IP and per identifier. Verify directly against Supabase Auth.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email","description":"May create an account (the signup route)."},"phone":{"type":"string","pattern":"^\\+[1-9]\\d{7,14}$","description":"E.164 phone number, e.g. +61412345678. Login-only."}}}}}},"responses":{"200":{"description":"Code sent.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"object","required":["sent"],"properties":{"sent":{"type":"boolean"}}}}}}}},"400":{"description":"Validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid, expired, or revoked API key/OAuth bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"The principal cannot access this site or the requested feature is not included in its plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested site, pin, version, or folder was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict, including reuse of an idempotency key with changed input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"Request body exceeds the route limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limited. Respect the Retry-After response header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/snippets":{"get":{"operationId":"listSnippets","summary":"Pull the signed-in user’s personal snippets (incremental, tombstones included)","description":"User-scoped: requires an OAuth/user bearer token; site API keys are rejected. updated_since alone is inclusive (>=); pair it with after_id as a keyset cursor for the next page. Ordered by (updated_at, id).","parameters":[{"name":"updated_since","in":"query","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"after_id","in":"query","required":false,"schema":{"type":"string","format":"uuid"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":500,"default":500}}],"responses":{"200":{"description":"Snippets for the user, oldest change first.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"object","required":["snippets","has_more"],"properties":{"snippets":{"type":"array","items":{"$ref":"#/components/schemas/Snippet"}},"has_more":{"type":"boolean"}}}}}}}},"400":{"description":"Validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid, expired, or revoked API key/OAuth bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"The principal cannot access this site or the requested feature is not included in its plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested site, pin, version, or folder was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict, including reuse of an idempotency key with changed input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"Request body exceeds the route limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limited. Respect the Retry-After response header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/snippets/sync":{"post":{"operationId":"syncSnippets","summary":"Push a batch of personal snippets (last-write-wins upsert)","description":"User-scoped: requires an OAuth/user bearer token; site API keys are rejected. Naturally idempotent — replaying a batch is a no-op, so no Idempotency-Key is needed. Stale records lose the LWW guard and are absent from the applied result.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["records"],"properties":{"records":{"type":"array","minItems":1,"maxItems":200,"items":{"$ref":"#/components/schemas/Snippet"}}}}}}},"responses":{"200":{"description":"Records accepted by the LWW guard.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"object","required":["applied"],"properties":{"applied":{"type":"array","items":{"$ref":"#/components/schemas/Snippet"}}}}}}}}},"400":{"description":"Validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid, expired, or revoked API key/OAuth bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"The principal cannot access this site or the requested feature is not included in its plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested site, pin, version, or folder was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict, including reuse of an idempotency key with changed input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"Request body exceeds the route limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limited. Respect the Retry-After response header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/sites":{"get":{"operationId":"listSites","summary":"List the sites this principal can publish to","responses":{"200":{"description":"Sites accessible to the key.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"object","properties":{"sites":{"type":"array","items":{"$ref":"#/components/schemas/Site"}}}}}}}}},"400":{"description":"Validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid, expired, or revoked API key/OAuth bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"The principal cannot access this site or the requested feature is not included in its plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested site, pin, version, or folder was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict, including reuse of an idempotency key with changed input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"Request body exceeds the route limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limited. Respect the Retry-After response header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/pins":{"get":{"operationId":"listPins","summary":"List pins (permanent links) with their live version ids","parameters":[{"name":"site_id","in":"query","required":false,"schema":{"type":"string","format":"uuid"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":200},"description":"Max pins to return. Omit limit and offset for the full list."},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0},"description":"Number of pins to skip."}],"responses":{"200":{"description":"Pins for the site(s).","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"object","properties":{"pins":{"type":"array","items":{"$ref":"#/components/schemas/Pin"}},"total":{"type":"integer","description":"Total pins available before limit/offset were applied."}}}}}}}},"400":{"description":"Validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid, expired, or revoked API key/OAuth bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"The principal cannot access this site or the requested feature is not included in its plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested site, pin, version, or folder was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict, including reuse of an idempotency key with changed input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"Request body exceeds the route limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limited. Respect the Retry-After response header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"post":{"operationId":"createPin","summary":"Create a new pin (a permanent public URL) on a site","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"description":"Unique key (1-120 chars) per logical operation. Replaying the same key with the exact same payload returns the original result without re-executing. Reusing it with changed input returns 409.","schema":{"type":"string","maxLength":120}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["site_id","display_name"],"properties":{"site_id":{"type":"string","format":"uuid"},"display_name":{"type":"string","maxLength":50},"folder_id":{"type":["string","null"],"format":"uuid","description":"Optional folder to place it in."},"visibility":{"type":"string","enum":["private","site","org"],"description":"Who inside the team can see this pin: private (creator only), site (all site members), org (site members plus the organisation hub). Omit to inherit the site's default. Does not affect who can reach the public URL."}}}}}},"responses":{"201":{"description":"Pin created.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"object","properties":{"pin":{"$ref":"#/components/schemas/Pin"},"permanent_link":{"type":"string"}}}}}}}},"400":{"description":"Validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid, expired, or revoked API key/OAuth bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"The principal cannot access this site or the requested feature is not included in its plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested site, pin, version, or folder was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict, including reuse of an idempotency key with changed input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"Request body exceeds the route limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limited. Respect the Retry-After response header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/pins/{pin_id}/archive":{"post":{"operationId":"archivePin","summary":"Archive a pin (soft delete — permanent removal stays in the dashboard)","description":"A PUBLIC change: archived pins stop serving their file — visitors see an archived page — and they drop out of integration pin lists. Distinct from discarding a draft version. Optionally send archived-page overrides in the body; omit the body to keep the saved settings. Reversible from the dashboard.","parameters":[{"name":"pin_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"archived_public_message":{"type":"string","maxLength":1000,"description":"Archived-page message. Omit to keep the saved message or the account-owner-email default."},"archived_public_redirect_label":{"type":["string","null"],"maxLength":120,"description":"Label for the archived-page contact button."},"archived_public_redirect_url":{"type":["string","null"],"maxLength":2000,"description":"Archived-page contact destination: a URL, mailto:, or tel: link."}}}}}},"responses":{"200":{"description":"Pin archived.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"object","properties":{"message":{"type":"string"},"pin_id":{"type":"string","format":"uuid"}}}}}}}},"400":{"description":"Validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid, expired, or revoked API key/OAuth bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"The principal cannot access this site or the requested feature is not included in its plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested site, pin, version, or folder was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict, including reuse of an idempotency key with changed input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"Request body exceeds the route limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limited. Respect the Retry-After response header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/pins/{pin_id}":{"patch":{"operationId":"renamePin","summary":"Rename a pin — the permanent URL does not change","parameters":[{"name":"pin_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["display_name"],"properties":{"display_name":{"type":"string","maxLength":100}}}}}},"responses":{"200":{"description":"Pin renamed.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"object","properties":{"message":{"type":"string"},"pin_id":{"type":"string","format":"uuid"},"display_name":{"type":"string"}}}}}}}},"400":{"description":"Validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid, expired, or revoked API key/OAuth bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"The principal cannot access this site or the requested feature is not included in its plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested site, pin, version, or folder was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict, including reuse of an idempotency key with changed input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"Request body exceeds the route limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limited. Respect the Retry-After response header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/pins/{pin_id}/folder":{"patch":{"operationId":"movePin","summary":"Move a pin into a folder (or null to remove it from its folder)","parameters":[{"name":"pin_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["folder_id"],"properties":{"folder_id":{"type":["string","null"],"format":"uuid"}}}}}},"responses":{"200":{"description":"Pin moved.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"object","properties":{"message":{"type":"string"},"pin_id":{"type":"string","format":"uuid"},"folder_id":{"type":["string","null"],"format":"uuid"}}}}}}}},"400":{"description":"Validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid, expired, or revoked API key/OAuth bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"The principal cannot access this site or the requested feature is not included in its plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested site, pin, version, or folder was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict, including reuse of an idempotency key with changed input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"Request body exceeds the route limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limited. Respect the Retry-After response header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/sites/{site_id}/summary":{"get":{"operationId":"getSiteSummary","summary":"Cheap change cursor for a site","description":"Pin/folder counts plus the newest pin updated_at. Poll this and only refetch the full pin list when the summary changes.","parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Summary for the site.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"object","properties":{"summary":{"$ref":"#/components/schemas/SiteSummary"}}}}}}}},"400":{"description":"Validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid, expired, or revoked API key/OAuth bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"The principal cannot access this site or the requested feature is not included in its plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested site, pin, version, or folder was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict, including reuse of an idempotency key with changed input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"Request body exceeds the route limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limited. Respect the Retry-After response header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/sites/{site_id}/folders":{"get":{"operationId":"listFolders","summary":"List the folders on a site","parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Folders for the site.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"object","properties":{"folders":{"type":"array","items":{"$ref":"#/components/schemas/Folder"}}}}}}}}},"400":{"description":"Validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid, expired, or revoked API key/OAuth bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"The principal cannot access this site or the requested feature is not included in its plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested site, pin, version, or folder was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict, including reuse of an idempotency key with changed input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"Request body exceeds the route limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limited. Respect the Retry-After response header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"post":{"operationId":"createFolder","summary":"Create a folder on a site","parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Idempotency-Key","in":"header","required":true,"description":"Unique key (1-120 chars) per logical operation. Replaying the same key with the exact same payload returns the original result without re-executing. Reusing it with changed input returns 409.","schema":{"type":"string","maxLength":120}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","maxLength":80}}}}}},"responses":{"201":{"description":"Folder created.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"object","properties":{"folder":{"$ref":"#/components/schemas/Folder"}}}}}}}},"400":{"description":"Validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid, expired, or revoked API key/OAuth bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"The principal cannot access this site or the requested feature is not included in its plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested site, pin, version, or folder was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict, including reuse of an idempotency key with changed input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"Request body exceeds the route limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limited. Respect the Retry-After response header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/boards":{"get":{"operationId":"listBoards","summary":"List a site's Boards","description":"A Board is a permanent link to a relationship. Returns names, permanent links, status and pin counts only — read one board for its full composition.","parameters":[{"name":"site_id","in":"query","required":false,"description":"Optional site. Omit when the principal reaches exactly one site.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Boards for the site.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"object","properties":{"site_id":{"type":"string","format":"uuid"},"boards":{"type":"array","items":{"$ref":"#/components/schemas/BoardSummary"}}}}}}}}},"400":{"description":"Validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid, expired, or revoked API key/OAuth bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"The principal cannot access this site or the requested feature is not included in its plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested site, pin, version, or folder was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict, including reuse of an idempotency key with changed input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"Request body exceeds the route limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limited. Respect the Retry-After response header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"post":{"operationId":"createBoard","summary":"Create a Board","description":"Creates a permanent public link to a relationship. The board is born with the site's own side already on it; its id comes back as owner_party_id. Requires owner access.","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"description":"Unique key (1-120 chars) per logical operation. Replaying the same key with the exact same payload returns the original result without re-executing. Reusing it with changed input returns 409.","schema":{"type":"string","maxLength":120}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"site_id":{"type":"string","format":"uuid"},"name":{"type":"string","maxLength":120},"description":{"type":"string","maxLength":1000}}}}}},"responses":{"201":{"description":"Board created.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"object","properties":{"board":{"$ref":"#/components/schemas/BoardSummary"},"owner_party_id":{"type":["string","null"],"format":"uuid"}}}}}}}},"400":{"description":"Validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid, expired, or revoked API key/OAuth bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"The principal cannot access this site or the requested feature is not included in its plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested site, pin, version, or folder was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict, including reuse of an idempotency key with changed input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"Request body exceeds the route limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limited. Respect the Retry-After response header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/boards/{board_id}":{"get":{"operationId":"getBoard","summary":"Read one board's complete composition","description":"Sides, stream blocks in render order, checklists and items with their assignments, contacts by side, active notices, and cap usage. Read this before any board write — every write addresses ids returned here.","parameters":[{"name":"board_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The board.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"object","properties":{"board":{"$ref":"#/components/schemas/BoardDetail"}}}}}}}},"400":{"description":"Validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid, expired, or revoked API key/OAuth bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"The principal cannot access this site or the requested feature is not included in its plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested site, pin, version, or folder was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict, including reuse of an idempotency key with changed input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"Request body exceeds the route limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limited. Respect the Retry-After response header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/boards/{board_id}/content":{"post":{"operationId":"updateBoardContent","summary":"Compose a board's stream with a list of ops","description":"Attach pins, add links, text, QR or the Acknowledge block, remove any of them, or reorder. Imperative, not a diff: nothing is removed unless an op says so. Ops stop at the first failure and applied ops are not rolled back.","parameters":[{"name":"board_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["ops"],"properties":{"ops":{"type":"array","minItems":1,"maxItems":20,"description":"Each op names its verb in `op`, and the rest of its fields follow from that verb. Ids come from GET /api/v1/boards/{board_id}: remove ops take the BLOCK id, detach_pin takes the PIN id.","items":{"oneOf":[{"title":"attach_pin","type":"object","required":["op","pin_id"],"properties":{"op":{"const":"attach_pin"},"pin_id":{"type":"string","format":"uuid","description":"A pin on the board's OWN site. A pin from another site is not found."},"zone":{"type":"string","enum":["main","side"],"description":"Which column the block lands in. Defaults to 'main'."}}},{"title":"detach_pin","type":"object","required":["op","pin_id"],"properties":{"op":{"const":"detach_pin"},"pin_id":{"type":"string","format":"uuid","description":"The PIN id, not the block id."}}},{"title":"add_link","type":"object","required":["op","label","url"],"properties":{"op":{"const":"add_link"},"label":{"type":"string","minLength":1,"maxLength":120},"url":{"type":"string","maxLength":2048,"description":"http(s) only."},"icon":{"type":"string","enum":["link","globe","calendar","map-pin","phone","mail","file-text","video","music","camera","shopping-bag","utensils","book-open","ticket","megaphone","star","users","help-circle","instagram","facebook","x","linkedin","youtube","tiktok","whatsapp","spotify","google"],"description":"Glyph shown beside the link on the public board. Defaults to 'link'. Brand keys are matched to the URL automatically in the dashboard, but must be set explicitly here."},"zone":{"type":"string","enum":["main","side"],"description":"Which column the block lands in. Defaults to 'main'."}}},{"title":"remove_link","type":"object","required":["op","link_id"],"properties":{"op":{"const":"remove_link"},"link_id":{"type":"string","format":"uuid"}}},{"title":"add_text","type":"object","required":["op"],"description":"Needs a heading or a body; both empty is refused.","properties":{"op":{"const":"add_text"},"heading":{"type":"string","maxLength":200},"body":{"type":"string","maxLength":2000},"zone":{"type":"string","enum":["main","side"],"description":"Which column the block lands in. Defaults to 'main'."}}},{"title":"remove_text","type":"object","required":["op","text_id"],"properties":{"op":{"const":"remove_text"},"text_id":{"type":"string","format":"uuid"}}},{"title":"add_qr","type":"object","required":["op","qr_code_id"],"properties":{"op":{"const":"add_qr"},"qr_code_id":{"type":"string","description":"One of the site's own QR codes. Its own short id, not a uuid."},"zone":{"type":"string","enum":["main","side"],"description":"Which column the block lands in. Defaults to 'main'."}}},{"title":"remove_qr","type":"object","required":["op","qr_block_id"],"properties":{"op":{"const":"remove_qr"},"qr_block_id":{"type":"string","format":"uuid","description":"The BLOCK id, not the QR id."}}},{"title":"add_ack","type":"object","required":["op"],"description":"A board holds at most one Acknowledge block.","properties":{"op":{"const":"add_ack"},"prompt":{"type":"string","maxLength":200,"description":"Button wording. Omit for the built-in copy."},"zone":{"type":"string","enum":["main","side"],"description":"Which column the block lands in. Defaults to 'main'."}}},{"title":"remove_ack","type":"object","required":["op"],"description":"No id: a board holds at most one Acknowledge block.","properties":{"op":{"const":"remove_ack"}}},{"title":"reorder","type":"object","required":["op","items"],"description":"Supplied rows only — blocks not named here keep their place.","properties":{"op":{"const":"reorder"},"items":{"type":"array","minItems":1,"items":{"type":"object","required":["kind","id","zone","position"],"properties":{"kind":{"type":"string","enum":["pin","link","text","qr","ack"]},"id":{"type":"string","format":"uuid","description":"The BLOCK id."},"zone":{"type":"string","enum":["main","side"],"description":"Which column the block lands in. Defaults to 'main'."},"position":{"type":"integer","minimum":0}}}}}}],"discriminator":{"propertyName":"op"}}}}}}}},"responses":{"200":{"description":"Per-op report. 200 even when the batch stopped early: the report names exactly what was applied, what failed, and what was skipped.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/BoardOpReport"}}}}}},"400":{"description":"Validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid, expired, or revoked API key/OAuth bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"The principal cannot access this site or the requested feature is not included in its plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested site, pin, version, or folder was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict, including reuse of an idempotency key with changed input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"Request body exceeds the route limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limited. Respect the Retry-After response header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/boards/{board_id}/parties":{"post":{"operationId":"setBoardParties","summary":"Ensure the board’s sides exist, and return the id map","description":"Idempotent by name: a side that already exists is reused, never duplicated. Passing party_id renames that side instead. Sides cannot be deleted here — removing one moves everyone on it.","parameters":[{"name":"board_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["parties"],"properties":{"parties":{"type":"array","minItems":1,"maxItems":4,"items":{"type":"object","required":["name"],"properties":{"name":{"type":"string","maxLength":80},"party_id":{"type":"string","format":"uuid"}}}}}}}}},"responses":{"200":{"description":"Per-op report plus the resolved sides.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"allOf":[{"$ref":"#/components/schemas/BoardOpReport"},{"type":"object","properties":{"parties":{"type":"array","items":{"$ref":"#/components/schemas/BoardParty"}}}}]}}}}}},"400":{"description":"Validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid, expired, or revoked API key/OAuth bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"The principal cannot access this site or the requested feature is not included in its plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested site, pin, version, or folder was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict, including reuse of an idempotency key with changed input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"Request body exceeds the route limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limited. Respect the Retry-After response header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/boards/{board_id}/checklists":{"post":{"operationId":"setBoardChecklist","summary":"Create checklists and items, and tick or untick items","description":"Items may be assigned to a side by party_id so that side can tick them itself. Provide checklists, ticks, or both. Steps stop at the first failure and are reported per index.","parameters":[{"name":"board_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"checklists":{"type":"array","maxItems":4,"items":{"type":"object","required":["title"],"properties":{"checklist_id":{"type":"string","format":"uuid"},"title":{"type":"string","maxLength":160},"items":{"type":"array","maxItems":20,"items":{"type":"object","required":["label"],"properties":{"label":{"type":"string","maxLength":280},"assigned_party_id":{"type":["string","null"],"format":"uuid"}}}}}}},"ticks":{"type":"array","maxItems":20,"items":{"type":"object","required":["checklist_id","item_id","done"],"properties":{"checklist_id":{"type":"string","format":"uuid"},"item_id":{"type":"string","format":"uuid"},"done":{"type":"boolean"}}}}}}}}},"responses":{"200":{"description":"Per-step report.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/BoardOpReport"}}}}}},"400":{"description":"Validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid, expired, or revoked API key/OAuth bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"The principal cannot access this site or the requested feature is not included in its plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested site, pin, version, or folder was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict, including reuse of an idempotency key with changed input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"Request body exceeds the route limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limited. Respect the Retry-After response header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/boards/{board_id}/contacts":{"post":{"operationId":"setBoardContacts","summary":"Add or update the named people on each side","description":"party_id is required on every contact: read the sides first and name one. Pass person_id to update in place. Contacts are capped PER SIDE, and a full side stops the whole call — every later contact is skipped whichever side it was for. Resend from the reported index.","parameters":[{"name":"board_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["contacts"],"properties":{"contacts":{"type":"array","minItems":1,"maxItems":20,"items":{"type":"object","required":["party_id","name"],"properties":{"person_id":{"type":"string","format":"uuid"},"party_id":{"type":"string","format":"uuid"},"name":{"type":"string","maxLength":120},"role_title":{"type":"string","maxLength":120},"email":{"type":"string","maxLength":200},"phone":{"type":"string","maxLength":40},"organisation_label":{"type":"string","maxLength":120}}}}}}}}},"responses":{"200":{"description":"Per-contact report.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/BoardOpReport"}}}}}},"400":{"description":"Validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid, expired, or revoked API key/OAuth bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"The principal cannot access this site or the requested feature is not included in its plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested site, pin, version, or folder was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict, including reuse of an idempotency key with changed input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"Request body exceeds the route limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limited. Respect the Retry-After response header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/boards/{board_id}/notices":{"post":{"operationId":"postBoardNotice","summary":"Post a notice to a board","description":"Posting through the API is silent: there is no field to email the board's subscribers, so an integration never can. Send from the dashboard when that email is wanted.","parameters":[{"name":"board_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title"],"properties":{"title":{"type":"string","maxLength":160},"body":{"type":"string","maxLength":4000},"expires_at":{"type":["string","null"],"format":"date-time"}}}}}},"responses":{"201":{"description":"Notice posted.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"object","properties":{"notice":{"$ref":"#/components/schemas/BoardNotice"}}}}}}}},"400":{"description":"Validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid, expired, or revoked API key/OAuth bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"The principal cannot access this site or the requested feature is not included in its plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested site, pin, version, or folder was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict, including reuse of an idempotency key with changed input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"Request body exceeds the route limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limited. Respect the Retry-After response header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/boards/{board_id}/activity":{"get":{"operationId":"getBoardActivity","summary":"Read a board's activity feed","description":"Who ticked what, who added a contact, which notices went up — newest first, one page at a time. Read-only: entries are derived from actions, never authored.","parameters":[{"name":"board_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"cursor","in":"query","required":false,"description":"The previous page's next_cursor, echoed back verbatim.","schema":{"type":"string","format":"date-time"}}],"responses":{"200":{"description":"One page of the feed.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/BoardActivityPage"}}}}}},"400":{"description":"Validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid, expired, or revoked API key/OAuth bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"The principal cannot access this site or the requested feature is not included in its plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested site, pin, version, or folder was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict, including reuse of an idempotency key with changed input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"Request body exceeds the route limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limited. Respect the Retry-After response header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/pins/{pin_id}/secure-access":{"get":{"operationId":"getSecureAccess","summary":"Read a pin’s secure-access gate (effective policy + where it comes from)","parameters":[{"name":"pin_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The effective secure-access policy.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"object","properties":{"secure_access":{"$ref":"#/components/schemas/SecureAccess"}}}}}}}},"400":{"description":"Validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid, expired, or revoked API key/OAuth bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"The principal cannot access this site or the requested feature is not included in its plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested site, pin, version, or folder was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict, including reuse of an idempotency key with changed input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"Request body exceeds the route limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limited. Respect the Retry-After response header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"patch":{"operationId":"setSecureAccess","summary":"Set a pin’s secure-access gate (Pro plan) — require contact details and/or a PIN","description":"Pass inherit:true to clear the pin override and inherit from its folder/default. Otherwise set enabled + the policy. Enabling requires the Pro plan.","parameters":[{"name":"pin_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"inherit":{"type":"boolean","description":"Clear the pin override and inherit."},"enabled":{"type":"boolean"},"access_rule":{"type":"string","enum":["contact","pin","pin_contact"]},"contact_fields":{"type":"array","items":{"type":"string","enum":["name","email","phone"]}},"pin":{"type":["string","null"],"description":"4-6 digit PIN."},"remember_days":{"type":"integer","minimum":1,"maximum":30}}}}}},"responses":{"200":{"description":"Updated secure-access policy.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"object","properties":{"secure_access":{"$ref":"#/components/schemas/SecureAccess"}}}}}}}},"400":{"description":"Validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid, expired, or revoked API key/OAuth bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"The principal cannot access this site or the requested feature is not included in its plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested site, pin, version, or folder was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict, including reuse of an idempotency key with changed input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"Request body exceeds the route limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limited. Respect the Retry-After response header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/pins/{pin_id}/active-version":{"get":{"operationId":"getActiveVersion","summary":"Get the currently published version for a pin","parameters":[{"name":"pin_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The live version, or null when nothing is published.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"object","properties":{"version":{"oneOf":[{"$ref":"#/components/schemas/Version"},{"type":"null"}]}}}}}}}},"400":{"description":"Validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid, expired, or revoked API key/OAuth bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"The principal cannot access this site or the requested feature is not included in its plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested site, pin, version, or folder was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict, including reuse of an idempotency key with changed input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"Request body exceeds the route limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limited. Respect the Retry-After response header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/versions/{version_id}/content":{"get":{"operationId":"getVersionFileContent","summary":"Fetch a version's raw file content","description":"Redirects (307) to a short-lived signed URL for the original uploaded file. Owner access — bypasses the public viewer gate.","parameters":[{"name":"version_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"307":{"description":"Redirect to a short-lived signed URL for the file content.","headers":{"Location":{"schema":{"type":"string","format":"uri"}}}},"400":{"description":"Validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid, expired, or revoked API key/OAuth bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"The principal cannot access this site or the requested feature is not included in its plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested site, pin, version, or folder was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict, including reuse of an idempotency key with changed input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"Request body exceeds the route limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limited. Respect the Retry-After response header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/versions/{version_id}/content/search":{"get":{"operationId":"searchVersionFileContent","summary":"Read targeted excerpts of a version's content (html/csv)","description":"Returns only matching excerpts with line numbers instead of the whole file. Pass query (case-sensitive substring, with optional context_lines/max_matches) OR start_line+end_line. Excerpt text is exact and reusable as old_string for editHtml.","parameters":[{"name":"version_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"query","in":"query","required":false,"schema":{"type":"string","maxLength":1000},"description":"Case-sensitive literal substring; returns matching excerpts. Provide query OR start_line+end_line."},{"name":"context_lines","in":"query","required":false,"schema":{"type":"integer","minimum":0,"maximum":50},"description":"Context lines around each match (default 3). Requires query."},{"name":"max_matches","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100},"description":"Max matches returned as excerpts (default 20); total_matches always reports the full count."},{"name":"start_line","in":"query","required":false,"schema":{"type":"integer","minimum":1},"description":"1-based inclusive range start. Provide with end_line (max 400 lines)."},{"name":"end_line","in":"query","required":false,"schema":{"type":"integer","minimum":1},"description":"1-based inclusive range end."}],"responses":{"200":{"description":"Matching excerpts.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/FileContentExcerpts"}}}}}},"400":{"description":"Validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid, expired, or revoked API key/OAuth bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"The principal cannot access this site or the requested feature is not included in its plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested site, pin, version, or folder was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict, including reuse of an idempotency key with changed input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"Request body exceeds the route limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limited. Respect the Retry-After response header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/pins/{pin_id}/active-version/content":{"get":{"operationId":"getActiveVersionFileContent","summary":"Fetch the current live version's raw file content for a pin","description":"Redirects (307) to a short-lived signed URL for the pin's live original file. 404 when nothing is published.","parameters":[{"name":"pin_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"307":{"description":"Redirect to a short-lived signed URL for the file content.","headers":{"Location":{"schema":{"type":"string","format":"uri"}}}},"400":{"description":"Validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid, expired, or revoked API key/OAuth bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"The principal cannot access this site or the requested feature is not included in its plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested site, pin, version, or folder was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict, including reuse of an idempotency key with changed input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"Request body exceeds the route limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limited. Respect the Retry-After response header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/pins/{pin_id}/active-version/content/search":{"get":{"operationId":"searchActiveVersionFileContent","summary":"Read targeted excerpts of a pin's live content (html/csv)","description":"Same as searchVersionFileContent, targeting the pin’s current live version. 404 when nothing is published.","parameters":[{"name":"pin_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"query","in":"query","required":false,"schema":{"type":"string","maxLength":1000},"description":"Case-sensitive literal substring; returns matching excerpts. Provide query OR start_line+end_line."},{"name":"context_lines","in":"query","required":false,"schema":{"type":"integer","minimum":0,"maximum":50},"description":"Context lines around each match (default 3). Requires query."},{"name":"max_matches","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100},"description":"Max matches returned as excerpts (default 20); total_matches always reports the full count."},{"name":"start_line","in":"query","required":false,"schema":{"type":"integer","minimum":1},"description":"1-based inclusive range start. Provide with end_line (max 400 lines)."},{"name":"end_line","in":"query","required":false,"schema":{"type":"integer","minimum":1},"description":"1-based inclusive range end."}],"responses":{"200":{"description":"Matching excerpts.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/FileContentExcerpts"}}}}}},"400":{"description":"Validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid, expired, or revoked API key/OAuth bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"The principal cannot access this site or the requested feature is not included in its plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested site, pin, version, or folder was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict, including reuse of an idempotency key with changed input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"Request body exceeds the route limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limited. Respect the Retry-After response header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/uploads/direct":{"post":{"operationId":"uploadFile","summary":"Upload a file in one call (base64 or HTML) and stage it as a new version","description":"Send the file content inline and receive the staged version plus its permanent link. Publish it with publishVersion. Limited to 20 requests/minute per key. Provide exactly one of base64_content or html_content (html_content requires mime_type text/html).","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"description":"Unique key (1-120 chars) per logical operation. Replaying the same key with the exact same payload returns the original result without re-executing. Reusing it with changed input returns 409.","schema":{"type":"string","maxLength":120}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["pin_id","filename","mime_type"],"properties":{"pin_id":{"type":"string","format":"uuid"},"filename":{"type":"string","maxLength":180},"mime_type":{"type":"string","description":"application/pdf, application/vnd.openxmlformats-officedocument.wordprocessingml.document, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, text/csv, image/png, image/jpeg, image/webp, or text/html."},"base64_content":{"type":"string","description":"Base64-encoded file bytes (max 20 MB decoded)."},"html_content":{"type":"string","description":"Raw HTML document (max 20 MB)."},"internal_label":{"type":["string","null"],"description":"Optional private team note (dashboard-only, never shown to viewers)."},"change_label":{"type":"string","description":"Optional public label shown to viewers. Omit to stage with no public label."}}}}}},"responses":{"201":{"description":"Version staged.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"object","properties":{"version":{"$ref":"#/components/schemas/Version"},"permanent_link":{"type":"string","description":"The pin’s public URL (shows the live version until this one is published)."},"staged_preview_url":{"type":"string","description":"Team-gated draft preview of this staged version (not public)."},"quality":{"$ref":"#/components/schemas/QualityReport"}}}}}}}},"400":{"description":"Validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid, expired, or revoked API key/OAuth bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"The principal cannot access this site or the requested feature is not included in its plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested site, pin, version, or folder was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict, including reuse of an idempotency key with changed input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"Request body exceeds the route limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limited (20 requests/minute per key). Respect Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/uploads/edit-html":{"post":{"operationId":"editHtml","summary":"Edit an existing HTML version with exact string replacements and stage the result","description":"Send only the changed strings — not the whole file. Stages the edited result as a NEW version; publish it with publishVersion. Pass exactly one of pin_id (active version) or version_id. Each old_string must match exactly once unless replace_all. 20 requests/minute per key.","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"description":"Unique key (1-120 chars) per logical operation. Replaying the same key with the exact same payload returns the original result without re-executing. Reusing it with changed input returns 409.","schema":{"type":"string","maxLength":120}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["edits"],"properties":{"pin_id":{"type":"string","format":"uuid","description":"Edit this pin’s active (live) version."},"version_id":{"type":"string","format":"uuid","description":"Edit this exact version instead."},"edits":{"type":"array","minItems":1,"maxItems":50,"items":{"type":"object","required":["old_string","new_string"],"properties":{"old_string":{"type":"string","maxLength":5000,"description":"Exact text to replace (whitespace included)."},"new_string":{"type":"string","description":"Replacement text; empty string deletes."},"replace_all":{"type":"boolean","description":"Replace every occurrence instead of requiring a unique match."}}}},"internal_label":{"type":["string","null"],"description":"Optional private team note (dashboard-only, never shown to viewers)."},"change_label":{"type":"string","description":"Optional public label shown to viewers. Omit to stage with no public label."}}}}}},"responses":{"201":{"description":"Edited version staged.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"object","properties":{"version":{"$ref":"#/components/schemas/Version"},"source_version_id":{"type":"string","format":"uuid","description":"The version the edits were applied to."},"edits_applied":{"type":"array","description":"Per-edit replacement counts, in request order.","items":{"type":"object","properties":{"index":{"type":"integer"},"replacements":{"type":"integer"}}}},"staged_preview_url":{"type":"string","description":"Team-gated draft preview of this staged version (not public)."},"quality":{"$ref":"#/components/schemas/QualityReport"}}}}}}}},"400":{"description":"Validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid, expired, or revoked API key/OAuth bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"The principal cannot access this site or the requested feature is not included in its plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested site, pin, version, or folder was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict, including reuse of an idempotency key with changed input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"Request body exceeds the route limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limited (20 requests/minute per key). Respect Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/uploads/prepare":{"post":{"operationId":"prepareUpload","summary":"Get a signed URL for uploading large files directly to storage","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"description":"Unique key (1-120 chars) per logical operation. Replaying the same key with the exact same payload returns the original result without re-executing. Reusing it with changed input returns 409.","schema":{"type":"string","maxLength":120}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["pin_id","mime_type","size"],"properties":{"pin_id":{"type":"string","format":"uuid"},"mime_type":{"type":"string"},"size":{"type":"integer","minimum":1,"description":"File size in bytes (max 20 MB)."}}}}}},"responses":{"200":{"description":"Signed upload target. PUT the file bytes to signed_url, then call completeUpload.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"object","properties":{"signed_url":{"type":"string"},"token":{"type":"string"},"storage_path":{"type":"string"},"upload_id":{"type":"string","format":"uuid"},"file_name":{"type":"string"},"version_number":{"type":"integer"},"file_ext":{"type":"string"}}}}}}}},"400":{"description":"Validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid, expired, or revoked API key/OAuth bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"The principal cannot access this site or the requested feature is not included in its plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested site, pin, version, or folder was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict, including reuse of an idempotency key with changed input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"Request body exceeds the route limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limited. Respect the Retry-After response header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/uploads/complete":{"post":{"operationId":"completeUpload","summary":"Finalise a prepared upload as a staged version","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"description":"Unique key (1-120 chars) per logical operation. Replaying the same key with the exact same payload returns the original result without re-executing. Reusing it with changed input returns 409.","schema":{"type":"string","maxLength":120}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["pin_id","storage_path","upload_id","mime_type","size"],"properties":{"pin_id":{"type":"string","format":"uuid"},"storage_path":{"type":"string"},"upload_id":{"type":"string","format":"uuid"},"mime_type":{"type":"string"},"size":{"type":"integer","minimum":1},"internal_label":{"type":["string","null"],"description":"Optional private team note (dashboard-only, never shown to viewers)."},"change_label":{"type":"string","description":"Optional public label shown to viewers. Omit to stage with no public label."}}}}}},"responses":{"201":{"description":"Version staged.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"object","properties":{"version":{"$ref":"#/components/schemas/Version"},"staged_preview_url":{"type":"string","description":"Team-gated draft preview of this staged version (not public)."}}}}}}}},"400":{"description":"Validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid, expired, or revoked API key/OAuth bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"The principal cannot access this site or the requested feature is not included in its plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested site, pin, version, or folder was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict, including reuse of an idempotency key with changed input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"Request body exceeds the route limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limited. Respect the Retry-After response header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/versions/{version_id}/publish":{"post":{"operationId":"publishVersion","summary":"Publish a staged version — the permanent link updates instantly","parameters":[{"name":"version_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Idempotency-Key","in":"header","required":true,"description":"Unique key (1-120 chars) per logical operation. Replaying the same key with the exact same payload returns the original result without re-executing. Reusing it with changed input returns 409.","schema":{"type":"string","maxLength":120}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"change_label":{"type":"string","description":"Public label shown to viewers. Omit to publish with no public label."},"internal_label":{"type":["string","null"],"description":"Private dashboard-only label. Never shown to viewers."},"announce":{"type":"boolean","description":"Show the change badge and notify subscribers. Requires change_label."},"scheduled_for":{"type":"string","format":"date-time","description":"Optional ISO 8601 timestamp. If set, the version is scheduled to publish then instead of immediately."}}}}}},"responses":{"200":{"description":"Version published (or scheduled).","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"object","required":["message","status","pin_id","permanent_link"],"properties":{"message":{"type":"string"},"status":{"type":"string","description":"live | scheduled | pending_approval"},"pin_id":{"type":"string","format":"uuid"},"permanent_link":{"type":"string","format":"uri","description":"The pin’s permanent public URL."}}}}}}}},"400":{"description":"Validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid, expired, or revoked API key/OAuth bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"The principal cannot access this site or the requested feature is not included in its plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested site, pin, version, or folder was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict, including reuse of an idempotency key with changed input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"Request body exceeds the route limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limited. Respect the Retry-After response header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/versions/{version_id}/review":{"post":{"operationId":"reviewVersion","summary":"Review a stored HTML version against quality rules and the site's Design Guideline","description":"Runs the review-v2 ruleset over the STORED document: generic quality rules plus advisory brand conformance. Reports findings only — it never edits, and findings never block publishing. Optional design_guideline_id in the body; defaults to the site default guideline. HTML versions only.","parameters":[{"name":"version_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"design_guideline_id":{"type":"string","format":"uuid","description":"Review against this Design Guideline. Omit to use the site's default guideline."}}}}}},"responses":{"200":{"description":"Review findings for the stored document.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"object","required":["version_id","pin_id","design_guideline_id","quality"],"properties":{"version_id":{"type":"string","format":"uuid"},"pin_id":{"type":"string","format":"uuid"},"design_guideline_id":{"type":["string","null"],"format":"uuid","description":"The guideline the review ran against, or null when only generic rules ran."},"quality":{"$ref":"#/components/schemas/QualityReport"}}}}}}}},"400":{"description":"Validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid, expired, or revoked API key/OAuth bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"The principal cannot access this site or the requested feature is not included in its plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested site, pin, version, or folder was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict, including reuse of an idempotency key with changed input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"Request body exceeds the route limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limited. Respect the Retry-After response header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/pins/{pin_id}/versions":{"get":{"operationId":"listVersions","summary":"List a pin’s version history (newest first), including archived versions","description":"Use this to find a previous version to restore.","parameters":[{"name":"pin_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"Version history.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"object","properties":{"versions":{"type":"array","items":{"$ref":"#/components/schemas/VersionHistoryItem"}}}}}}}}},"400":{"description":"Validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid, expired, or revoked API key/OAuth bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"The principal cannot access this site or the requested feature is not included in its plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested site, pin, version, or folder was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict, including reuse of an idempotency key with changed input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"Request body exceeds the route limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limited. Respect the Retry-After response header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/versions/{version_id}/restore":{"post":{"operationId":"restoreVersion","summary":"Roll a pin back by restoring an archived version — it becomes live again","description":"The previously-live version is automatically archived. Find the version id with listVersions.","parameters":[{"name":"version_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Version restored and live.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"object","properties":{"message":{"type":"string"},"pin_id":{"type":"string","format":"uuid"}}}}}}}},"400":{"description":"Validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid, expired, or revoked API key/OAuth bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"The principal cannot access this site or the requested feature is not included in its plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested site, pin, version, or folder was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict, including reuse of an idempotency key with changed input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"Request body exceeds the route limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limited. Respect the Retry-After response header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/versions/{version_id}/discard":{"post":{"operationId":"discardVersion","summary":"Discard a staged draft version the user no longer wants","description":"Only staged (unpublished) versions can be discarded — the public file is never affected. The version soft-archives (status archived), so restoreVersion can bring it back. Distinct from archiving a whole pin.","parameters":[{"name":"version_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Draft discarded.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"object","properties":{"message":{"type":"string"},"pin_id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["archived"]}}}}}}}},"400":{"description":"Validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid, expired, or revoked API key/OAuth bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"The principal cannot access this site or the requested feature is not included in its plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested site, pin, version, or folder was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict, including reuse of an idempotency key with changed input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"Request body exceeds the route limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limited. Respect the Retry-After response header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/versions/{version_id}/label":{"patch":{"operationId":"relabelVersion","summary":"Relabel a version — set the public and/or internal label (null clears one)","parameters":[{"name":"version_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","description":"Provide change_label and/or internal_label. Each accepts null to clear it.","properties":{"change_label":{"type":["string","null"],"maxLength":100,"description":"Public label shown to viewers."},"internal_label":{"type":["string","null"],"maxLength":100,"description":"Private dashboard-only label. Never shown to viewers."}}}}}},"responses":{"200":{"description":"Label updated.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"object","properties":{"message":{"type":"string"},"pin_id":{"type":"string","format":"uuid"},"change_label":{"type":["string","null"]},"internal_label":{"type":["string","null"]}}}}}}}},"400":{"description":"Validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid, expired, or revoked API key/OAuth bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"The principal cannot access this site or the requested feature is not included in its plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested site, pin, version, or folder was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict, including reuse of an idempotency key with changed input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"Request body exceeds the route limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limited. Respect the Retry-After response header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}}}}