{"openapi":"3.1.0","info":{"title":"Pankoblitz API","version":"1.0.0","description":"Multi-tenant landing page builder. Create, edit, and publish landing pages with 8 visual themes, 7 content block types, and subdomain hosting on panko.page."},"servers":[{"url":"https://api.pankoblitz.com"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Get a token via `POST /auth/token` with email + password. Tokens last 30 days."}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string"},"code":{"type":"string"}},"required":["error"]},"Organization":{"type":"object","properties":{"id":{"type":"string","description":"Unique org ID (nanoid)"},"slug":{"type":"string","description":"URL-safe org slug"},"name":{"type":"string"},"subscriptionTier":{"type":"string","enum":["free","developer","pro"]},"stripeCustomerId":{"type":"string","nullable":true},"stripeSubscriptionId":{"type":"string","nullable":true},"subscriptionStatus":{"type":"string"},"subscriptionEndsAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Site":{"type":"object","properties":{"id":{"type":"integer","description":"Auto-increment site ID"},"userId":{"type":"string"},"orgId":{"type":"string"},"slug":{"type":"string","description":"URL slug, unique within org. Lowercase alphanumeric + hyphens."},"subdomain":{"type":"string","nullable":true,"description":"Custom subdomain for panko.page (e.g. 'mysite' → mysite.panko.page)"},"title":{"type":"string"},"description":{"type":"string"},"primaryColor":{"type":"string","description":"Hex color, e.g. '#3b82f6'"},"template":{"type":"string"},"theme":{"type":"string","enum":["clean","sleek","glass","paper","brutalist","mono","neon"],"description":"Visual theme applied to the published landing page"},"content":{"$ref":"#/components/schemas/SiteContent"},"blockOrder":{"type":"array","items":{"type":"string"},"description":"Ordered list of block instance keys, e.g. ['hero:0', 'features:0', 'cta:0']"},"templateId":{"type":"integer","nullable":true},"status":{"type":"string","enum":["draft","active"]},"previewToken":{"type":"string","description":"96-char hex token for draft preview access"},"publishedSnapshot":{"type":"object","nullable":true,"description":"Frozen copy of content at last publish"},"hasUnpublishedChanges":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"SiteContent":{"type":"object","description":"Map of block instance keys to block content. Keys follow the pattern `{blockType}:{instanceNumber}`, e.g. `hero:0`, `features:0`, `cta:0`. Multiple instances of the same type are supported: `hero:0`, `hero:1`.","additionalProperties":{"oneOf":[{"$ref":"#/components/schemas/HeroBlock"},{"$ref":"#/components/schemas/LogoCloudBlock"},{"type":"array","items":{"$ref":"#/components/schemas/Feature"}},{"$ref":"#/components/schemas/StatsBlock"},{"type":"array","items":{"$ref":"#/components/schemas/Testimonial"}},{"$ref":"#/components/schemas/FAQBlock"},{"$ref":"#/components/schemas/CTABlock"}]},"example":{"hero:0":{"headline":"Welcome","subheadline":"Build something amazing","button_text":"Get Started","button_url":"#signup","text_align":"center"},"features:0":[{"title":"Fast","description":"Built for speed","icon":"zap"}],"cta:0":{"heading":"Ready?","subtext":"Join today","button_text":"Sign Up","button_url":"#signup"}}},"HeroBlock":{"type":"object","description":"Main headline and call-to-action section","properties":{"headline":{"type":"string","description":"Main heading text"},"subheadline":{"type":"string","description":"Supporting text below headline"},"button_text":{"type":"string","description":"Primary CTA button label"},"button_url":{"type":"string","description":"Primary CTA button URL"},"secondary_text":{"type":"string","description":"Secondary button label (optional)"},"secondary_url":{"type":"string","description":"Secondary button URL (optional)"},"badge":{"type":"string","description":"Eyebrow badge text above headline (optional)"},"text_align":{"type":"string","enum":["left","center","right"],"default":"center"}}},"LogoCloudBlock":{"type":"object","description":"'Trusted by' section with company logos","properties":{"heading":{"type":"string","description":"Section heading, e.g. 'Trusted by teams at'"},"logos":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"Company/brand name"},"url":{"type":"string","description":"Optional link URL"}},"required":["name"]}}}},"Feature":{"type":"object","description":"A single feature card. Features blocks are arrays of these.","properties":{"title":{"type":"string"},"description":{"type":"string"},"icon":{"type":"string","enum":["zap","shield","chart","globe","code","star","clock","lock"],"description":"Icon identifier. Rendered as SVG."}},"required":["title","description"]},"StatsBlock":{"type":"object","description":"Key metrics and numbers section","properties":{"heading":{"type":"string"},"items":{"type":"array","items":{"type":"object","properties":{"value":{"type":"string","description":"Metric value, e.g. '10K+', '99.9%'"},"label":{"type":"string","description":"Metric label, e.g. 'Active Users'"}},"required":["value","label"]}}}},"Testimonial":{"type":"object","description":"A single testimonial. Testimonials blocks are arrays of these.","properties":{"quote":{"type":"string"},"author":{"type":"string"},"role":{"type":"string","description":"Author's role/title"},"avatar":{"type":"string","description":"Initials or emoji for avatar"}},"required":["quote","author"]},"FAQBlock":{"type":"object","description":"Frequently asked questions section","properties":{"heading":{"type":"string"},"items":{"type":"array","items":{"type":"object","properties":{"question":{"type":"string"},"answer":{"type":"string"}},"required":["question","answer"]}}}},"CTABlock":{"type":"object","description":"Final call-to-action section","properties":{"heading":{"type":"string"},"subtext":{"type":"string"},"button_text":{"type":"string"},"button_url":{"type":"string"}}},"Template":{"type":"object","properties":{"id":{"type":"integer"},"orgId":{"type":"string","nullable":true,"description":"null for system templates"},"name":{"type":"string"},"slug":{"type":"string"},"description":{"type":"string"},"blockOrder":{"type":"array","items":{"type":"string"},"description":"Block types in order, e.g. ['hero', 'features', 'cta']"},"isSystem":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"BillingStatus":{"type":"object","properties":{"tier":{"type":"string","enum":["free","developer","pro"]},"tier_name":{"type":"string"},"status":{"type":"string"},"site_count":{"type":"integer"},"site_limit":{"type":"integer","description":"Numeric site limit for the org; -1 means the system does not enforce a hard numeric cap."},"can_upgrade":{"type":"boolean"},"has_analytics":{"type":"boolean"},"has_custom_templates":{"type":"boolean"},"has_support":{"type":"boolean"},"stripe_customer_id":{"type":"string","nullable":true},"subscription_id":{"type":"string","nullable":true},"subscription_ends_at":{"type":"string","format":"date-time","nullable":true}}}}},"paths":{"/auth/token":{"post":{"tags":["Auth"],"summary":"Get API token","description":"Exchange email + password for a 30-day JWT Bearer token.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email"},"password":{"type":"string","minLength":8}},"required":["email","password"]}}}},"responses":{"200":{"description":"Token issued","content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string"},"expires_in":{"type":"integer","description":"Seconds until expiry"}}}}}},"401":{"description":"Invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/orgs":{"get":{"tags":["Organizations"],"summary":"List organizations","description":"Returns all organizations the authenticated user is a member of.","responses":{"200":{"content":{"application/json":{"schema":{"type":"object","properties":{"orgs":{"type":"array","items":{"$ref":"#/components/schemas/Organization"}}}}}}}}},"post":{"tags":["Organizations"],"summary":"Create organization","description":"Creates a new organization and adds the authenticated user as owner.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"Display name"},"slug":{"type":"string","pattern":"^[a-z0-9]([a-z0-9-]*[a-z0-9])?$","description":"URL slug, lowercase alphanumeric + hyphens"}},"required":["name","slug"]}}}},"responses":{"201":{"content":{"application/json":{"schema":{"type":"object","properties":{"org":{"$ref":"#/components/schemas/Organization"}}}}}},"409":{"description":"Slug taken","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/orgs/{orgId}/sites":{"parameters":[{"name":"orgId","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Sites"],"summary":"List sites","responses":{"200":{"content":{"application/json":{"schema":{"type":"object","properties":{"sites":{"type":"array","items":{"$ref":"#/components/schemas/Site"}},"count":{"type":"integer"}}}}}}}},"post":{"tags":["Sites"],"summary":"Create site","description":"Creates a new landing page. Subject to tier site limits (Free=1, Developer=10, Pro=current plan cap).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"slug":{"type":"string","pattern":"^[a-z0-9]([a-z0-9-]*[a-z0-9])?$","description":"Required. URL slug, unique within org."},"title":{"type":"string","description":"Required. Page title."},"description":{"type":"string","default":""},"primaryColor":{"type":"string","default":"#3b82f6","description":"Hex color"},"template":{"type":"string","default":"minimal"},"theme":{"type":"string","enum":["clean","sleek","glass","paper","brutalist","mono","neon"],"default":"clean"},"content":{"$ref":"#/components/schemas/SiteContent"},"blockOrder":{"type":"array","items":{"type":"string"},"description":"Block instance keys in display order"},"templateId":{"type":"integer","description":"Optional reference to a template"}},"required":["slug","title"]}}}},"responses":{"201":{"content":{"application/json":{"schema":{"type":"object","properties":{"site":{"$ref":"#/components/schemas/Site"}}}}}},"403":{"description":"Site limit reached","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Slug taken","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/orgs/{orgId}/sites/{siteId}":{"parameters":[{"name":"orgId","in":"path","required":true,"schema":{"type":"string"}},{"name":"siteId","in":"path","required":true,"schema":{"type":"integer"}}],"get":{"tags":["Sites"],"summary":"Get site","responses":{"200":{"content":{"application/json":{"schema":{"type":"object","properties":{"site":{"$ref":"#/components/schemas/Site"}}}}}}}},"put":{"tags":["Sites"],"summary":"Update site","description":"Partial update — only provided fields are changed. If the site is published, sets hasUnpublishedChanges=true.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string"},"description":{"type":"string"},"primaryColor":{"type":"string"},"template":{"type":"string"},"theme":{"type":"string","enum":["clean","sleek","glass","paper","brutalist","mono","neon"]},"content":{"$ref":"#/components/schemas/SiteContent"},"blockOrder":{"type":"array","items":{"type":"string"}}}}}}},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object","properties":{"site":{"$ref":"#/components/schemas/Site"}}}}}}}},"delete":{"tags":["Sites"],"summary":"Delete site","responses":{"200":{"content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"boolean"},"id":{"type":"integer"}}}}}}}}},"/api/orgs/{orgId}/sites/{siteId}/publish":{"parameters":[{"name":"orgId","in":"path","required":true,"schema":{"type":"string"}},{"name":"siteId","in":"path","required":true,"schema":{"type":"integer"}}],"post":{"tags":["Sites"],"summary":"Publish site","description":"Sets status to 'active', creates a published snapshot of current content, clears hasUnpublishedChanges.","responses":{"200":{"content":{"application/json":{"schema":{"type":"object","properties":{"site":{"$ref":"#/components/schemas/Site"},"message":{"type":"string"}}}}}}}}},"/api/orgs/{orgId}/sites/{siteId}/unpublish":{"parameters":[{"name":"orgId","in":"path","required":true,"schema":{"type":"string"}},{"name":"siteId","in":"path","required":true,"schema":{"type":"integer"}}],"post":{"tags":["Sites"],"summary":"Unpublish site","description":"Sets status back to 'draft'.","responses":{"200":{"content":{"application/json":{"schema":{"type":"object","properties":{"site":{"$ref":"#/components/schemas/Site"},"message":{"type":"string"}}}}}}}}},"/api/orgs/{orgId}/sites/{siteId}/discard":{"parameters":[{"name":"orgId","in":"path","required":true,"schema":{"type":"string"}},{"name":"siteId","in":"path","required":true,"schema":{"type":"integer"}}],"post":{"tags":["Sites"],"summary":"Discard unpublished changes","description":"Restores content from the published snapshot. Only works if a snapshot exists.","responses":{"200":{"content":{"application/json":{"schema":{"type":"object","properties":{"site":{"$ref":"#/components/schemas/Site"},"message":{"type":"string"}}}}}}}}},"/api/orgs/{orgId}/sites/{siteId}/subdomain":{"parameters":[{"name":"orgId","in":"path","required":true,"schema":{"type":"string"}},{"name":"siteId","in":"path","required":true,"schema":{"type":"integer"}}],"put":{"tags":["Sites"],"summary":"Set subdomain","description":"Sets a custom subdomain on panko.page. Must be globally unique.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"subdomain":{"type":"string","pattern":"^[a-z0-9]([a-z0-9-]*[a-z0-9])?$","maxLength":63,"description":"Subdomain name (without .panko.page)"}},"required":["subdomain"]}}}},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object","properties":{"subdomain":{"type":"string"},"message":{"type":"string"}}}}}},"409":{"description":"Subdomain taken"}}},"delete":{"tags":["Sites"],"summary":"Clear subdomain","responses":{"200":{"content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"}}}}}}}}},"/api/orgs/{orgId}/templates":{"parameters":[{"name":"orgId","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Templates"],"summary":"List templates","description":"Returns system templates + org's custom templates.","responses":{"200":{"content":{"application/json":{"schema":{"type":"object","properties":{"templates":{"type":"array","items":{"$ref":"#/components/schemas/Template"}},"count":{"type":"integer"}}}}}}}},"post":{"tags":["Templates"],"summary":"Create custom template","description":"Requires Developer tier or above.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"slug":{"type":"string"},"description":{"type":"string"},"blockOrder":{"type":"array","items":{"type":"string"},"description":"Block types, e.g. ['hero', 'features', 'cta']"}},"required":["name","slug"]}}}},"responses":{"201":{"content":{"application/json":{"schema":{"type":"object","properties":{"template":{"$ref":"#/components/schemas/Template"}}}}}},"403":{"description":"Tier required (Developer+)"}}}},"/api/orgs/{orgId}/templates/{templateId}":{"parameters":[{"name":"orgId","in":"path","required":true,"schema":{"type":"string"}},{"name":"templateId","in":"path","required":true,"schema":{"type":"integer"}}],"put":{"tags":["Templates"],"summary":"Update template","description":"Cannot edit system templates.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"slug":{"type":"string"},"description":{"type":"string"},"blockOrder":{"type":"array","items":{"type":"string"}}}}}}},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object","properties":{"template":{"$ref":"#/components/schemas/Template"}}}}}}}},"delete":{"tags":["Templates"],"summary":"Delete template","description":"Cannot delete system templates.","responses":{"200":{"content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"boolean"},"id":{"type":"integer"}}}}}}}}},"/api/orgs/{orgId}/billing/status":{"parameters":[{"name":"orgId","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Billing"],"summary":"Get billing status","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BillingStatus"}}}}}}},"/api/orgs/{orgId}/billing/checkout":{"parameters":[{"name":"orgId","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Billing"],"summary":"Create Stripe checkout session","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"tier":{"type":"string","enum":["developer","pro"]}},"required":["tier"]}}}},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object","properties":{"checkout_url":{"type":"string"},"tier":{"type":"string"}}}}}}}}},"/api/orgs/{orgId}/billing/portal":{"parameters":[{"name":"orgId","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Billing"],"summary":"Create Stripe billing portal session","responses":{"200":{"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string"}}}}}}}}},"/api/subdomain/check":{"get":{"tags":["Public"],"summary":"Check subdomain availability","security":[],"parameters":[{"name":"subdomain","in":"query","required":true,"schema":{"type":"string"}},{"name":"site_id","in":"query","schema":{"type":"integer"},"description":"Exclude this site from check"}],"responses":{"200":{"description":"HTML status message"}}}},"/health":{"get":{"tags":["Public"],"summary":"Health check","security":[],"responses":{"200":{"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"timestamp":{"type":"string"}}}}}}}}}}}