{"openapi":"3.0.3","info":{"title":"Simple Signups API","version":"1.0.0","description":"Campaign-scoped subscriber signup collection, management, webhooks and API keys."},"servers":[{"url":"https://simple-signups.com"}],"tags":[{"name":"Public","description":"Unauthenticated / API-key signup endpoints"},{"name":"Campaigns","description":"Dashboard campaign management (session cookie)"},{"name":"Subscribers","description":"Subscriber management (session cookie)"},{"name":"Webhooks","description":"Outbound webhook management (session cookie)"},{"name":"API keys","description":"Scoped bearer credential management (session cookie)"}],"components":{"securitySchemes":{"sessionCookie":{"type":"apiKey","in":"cookie","name":"session","description":"Better Auth session cookie"},"apiKey":{"type":"http","scheme":"bearer","description":"Scoped `ss_...` API key"}},"schemas":{"Error":{"type":"object","properties":{"success":{"type":"boolean","example":false},"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}}}}},"SubscribeInput":{"type":"object","required":["campaignId","email"],"properties":{"campaignId":{"type":"string","description":"Public campaign id (`pub_...`)"},"email":{"type":"string","format":"email"},"firstName":{"type":"string"},"lastName":{"type":"string"},"tags":{"oneOf":[{"type":"array","items":{"type":"string"},"maxItems":20},{"type":"string","description":"Comma/semicolon/pipe-separated slugs"}],"description":"Category slugs (max 20). Re-subscribe merges with existing tags.","example":["newsletter","vip"]},"metadata":{"type":"object","additionalProperties":{"type":"string"}},"redirect":{"type":"string","format":"uri","description":"No-JS success redirect (must be an allowed domain)"}}},"PathSubscribeInput":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email"},"firstName":{"type":"string"},"lastName":{"type":"string"},"tags":{"oneOf":[{"type":"array","items":{"type":"string"},"maxItems":20},{"type":"string"}],"description":"Category slugs (max 20). Re-subscribe merges with existing tags.","example":["newsletter"]},"metadata":{"type":"object","additionalProperties":{"type":"string"}}}},"SuccessResponse":{"type":"object","properties":{"success":{"type":"boolean"},"status":{"type":"string","enum":["pending","confirmed"]}}},"Webhook":{"type":"object","properties":{"id":{"type":"string"},"url":{"type":"string","format":"uri"},"kind":{"type":"string","enum":["standard","slack"]},"events":{"type":"array","items":{"type":"string","enum":["subscriber.created","subscriber.confirmed"]}},"enabled":{"type":"boolean"},"createdAt":{"type":"integer"}}},"CreateWebhookInput":{"type":"object","required":["url","events"],"properties":{"url":{"type":"string","format":"uri","description":"https URL that must pass the SSRF guard"},"events":{"type":"array","minItems":1,"items":{"type":"string","enum":["subscriber.created","subscriber.confirmed"]}},"kind":{"type":"string","enum":["standard","slack"],"default":"standard"},"enabled":{"type":"boolean","default":true}}},"WebhookDelivery":{"type":"object","properties":{"id":{"type":"string"},"webhookId":{"type":"string"},"eventId":{"type":"string"},"eventType":{"type":"string","enum":["subscriber.created","subscriber.confirmed"]},"status":{"type":"string","enum":["pending","success","failed"]},"attempts":{"type":"integer"},"lastStatusCode":{"type":"integer","nullable":true},"lastError":{"type":"string","nullable":true},"nextAttemptAt":{"type":"integer","nullable":true},"createdAt":{"type":"integer"}}},"ApiKey":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"keyPrefix":{"type":"string"},"scopes":{"type":"array","items":{"type":"string","enum":["submit:campaign","read:subscribers","export:campaign","write:subscribers"]}},"createdAt":{"type":"integer"},"lastUsedAt":{"type":"integer","nullable":true},"revokedAt":{"type":"integer","nullable":true}}},"CreateApiKeyInput":{"type":"object","required":["name","scopes"],"properties":{"name":{"type":"string"},"scopes":{"type":"array","minItems":1,"items":{"type":"string","enum":["submit:campaign","read:subscribers","export:campaign","write:subscribers"]}}}}}},"paths":{"/api/subscribe":{"post":{"tags":["Public"],"summary":"Submit a signup (body-style)","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscribeInput"}}},"required":true},"responses":{"200":{"description":"Accepted (confirmed)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"202":{"description":"Accepted (pending confirmation)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Error envelope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Error envelope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Error envelope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/c/{publicCampaignId}":{"post":{"tags":["Public"],"summary":"Submit a signup (path-style); optional API-key auth","security":[{},{"apiKey":["submit:campaign"]}],"parameters":[{"name":"publicCampaignId","in":"path","required":true,"schema":{"type":"string"},"description":"Public campaign id (`pub_...`)"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PathSubscribeInput"}}},"required":true},"responses":{"200":{"description":"Accepted (confirmed)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"202":{"description":"Accepted (pending confirmation)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Error envelope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Error envelope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Error envelope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/c/{publicCampaignId}/subscribers":{"get":{"tags":["Public"],"summary":"List subscribers (API key: read:subscribers)","security":[{"apiKey":["read:subscribers"]}],"parameters":[{"name":"publicCampaignId","in":"path","required":true,"schema":{"type":"string"},"description":"Public campaign id (`pub_...`)"}],"responses":{"200":{"description":"Subscriber page"},"401":{"description":"Error envelope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Error envelope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Error envelope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/c/{publicCampaignId}/subscribers/export":{"get":{"tags":["Public"],"summary":"Export subscribers as CSV/JSON (API key: export:campaign)","security":[{"apiKey":["export:campaign"]}],"parameters":[{"name":"publicCampaignId","in":"path","required":true,"schema":{"type":"string"},"description":"Public campaign id (`pub_...`)"}],"responses":{"200":{"description":"CSV or JSON export"},"401":{"description":"Error envelope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Error envelope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Error envelope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/c/{publicCampaignId}/subscribers/bulk-tags":{"post":{"tags":["Public"],"summary":"Bulk add/remove tags (API key: write:subscribers)","security":[{"apiKey":["write:subscribers"]}],"parameters":[{"name":"publicCampaignId","in":"path","required":true,"schema":{"type":"string"},"description":"Public campaign id (`pub_...`)"}],"responses":{"200":{"description":"Updated count"},"400":{"description":"Error envelope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Error envelope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Error envelope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Error envelope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/c/{publicCampaignId}/subscribers/{subscriberId}":{"patch":{"tags":["Public"],"summary":"Patch subscriber tags (API key: write:subscribers)","security":[{"apiKey":["write:subscribers"]}],"parameters":[{"name":"publicCampaignId","in":"path","required":true,"schema":{"type":"string"},"description":"Public campaign id (`pub_...`)"},{"name":"subscriberId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Updated tags"},"400":{"description":"Error envelope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Error envelope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Error envelope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Error envelope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/subscribe/confirm":{"get":{"tags":["Public"],"summary":"Double opt-in confirmation link; redirects to the SPA","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"302":{"description":"Redirect to /confirm?status=..."}}}},"/api/campaigns":{"get":{"tags":["Campaigns"],"summary":"List campaigns","security":[{"sessionCookie":[]}],"responses":{"200":{"description":"Campaigns"}}},"post":{"tags":["Campaigns"],"summary":"Create a campaign","security":[{"sessionCookie":[]}],"responses":{"201":{"description":"Created campaign"},"400":{"description":"Error envelope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/campaigns/{id}":{"get":{"tags":["Campaigns"],"summary":"Get a campaign","security":[{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Internal campaign id"}],"responses":{"200":{"description":"Campaign"},"404":{"description":"Error envelope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Campaigns"],"summary":"Update a campaign","security":[{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Internal campaign id"}],"responses":{"200":{"description":"Updated campaign"},"400":{"description":"Error envelope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Error envelope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Campaigns"],"summary":"Delete a campaign","security":[{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Internal campaign id"}],"responses":{"200":{"description":"Deleted"},"404":{"description":"Error envelope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/campaigns/{id}/webhooks":{"get":{"tags":["Webhooks"],"summary":"List webhooks","security":[{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Internal campaign id"}],"responses":{"200":{"description":"Webhooks","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Error envelope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Webhooks"],"summary":"Create a webhook (secret shown once)","security":[{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Internal campaign id"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWebhookInput"}}},"required":true},"responses":{"201":{"description":"Created webhook + secret","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Webhook"}}}},"400":{"description":"Error envelope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Error envelope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/campaigns/{id}/webhooks/deliveries":{"get":{"tags":["Webhooks"],"summary":"List recent webhook deliveries","security":[{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Internal campaign id"}],"responses":{"200":{"description":"Deliveries","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookDelivery"}}}},"404":{"description":"Error envelope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/campaigns/{id}/webhooks/{webhookId}":{"patch":{"tags":["Webhooks"],"summary":"Update a webhook (enable/events)","security":[{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Internal campaign id"},{"name":"webhookId","in":"path","required":true,"schema":{"type":"string"},"description":"Webhook id"}],"responses":{"200":{"description":"Updated webhook","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Webhook"}}}},"400":{"description":"Error envelope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Error envelope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Webhooks"],"summary":"Delete a webhook","security":[{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Internal campaign id"},{"name":"webhookId","in":"path","required":true,"schema":{"type":"string"},"description":"Webhook id"}],"responses":{"200":{"description":"Deleted"},"404":{"description":"Error envelope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/subscribers/{id}":{"delete":{"tags":["Subscribers"],"summary":"Delete a subscriber","security":[{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Subscriber id"}],"responses":{"200":{"description":"Deleted"},"404":{"description":"Error envelope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/subscribers/{id}/resend-confirmation":{"post":{"tags":["Subscribers"],"summary":"Resend a pending double opt-in email (cooldown enforced)","security":[{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Subscriber id"}],"responses":{"200":{"description":"Resent"},"400":{"description":"Error envelope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Error envelope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Error envelope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/api-keys":{"get":{"tags":["API keys"],"summary":"List API keys","security":[{"sessionCookie":[]}],"responses":{"200":{"description":"API keys"}}},"post":{"tags":["API keys"],"summary":"Create an API key (token shown once)","security":[{"sessionCookie":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateApiKeyInput"}}},"required":true},"responses":{"201":{"description":"Created key + token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKey"}}}},"400":{"description":"Error envelope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/api-keys/{id}":{"delete":{"tags":["API keys"],"summary":"Revoke an API key","security":[{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"API key id"}],"responses":{"200":{"description":"Revoked"},"404":{"description":"Error envelope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}