{"openapi":"3.1.0","info":{"title":"NotificationStream API","version":"1.0.0","description":"REST API for [NotificationStream](https://notificationstream.com): in-app notification feeds for your web and mobile apps.\n\n## Authentication\n\n| Caller | Credential |\n| --- | --- |\n| Your backend | `Authorization: Bearer sk_…` (secret key, create it in the dashboard). Never expose it to browsers or apps. |\n| SDKs (browser / React Native) | Every `/v1/client/*` request sends the project's **publishable key** (`X-Publishable-Key: pk_…`) and a **user token** (`Authorization: Bearer <jwt>`): an ES256 JWT your backend signs with one of the project's signing keys, with `sub` = your user id and `exp` at most 7 days ahead. The API stores only the public keys. |\n\n## Errors\n\nEvery error has the shape `{ \"error\": { \"code\", \"message\", \"details\"? } }`. Codes: `bad_request` (400), `unauthorized` (401), `payment_required` (402), `forbidden` (403), `not_found` (404), `conflict` (409), `rate_limited` (429), `internal_error` (500).\n\n## Pagination\n\nList endpoints return `{ items, nextCursor }`. Pass `nextCursor` back as `cursor` until it is `null`.\n\n## Timestamps\n\nAll timestamps are epoch milliseconds (UTC).","contact":{"name":"NotificationStream","url":"https://notificationstream.com"}},"servers":[{"url":"https://api.notificationstream.com","description":"Production"}],"tags":[{"name":"Client","description":"Used by the SDKs from browsers and mobile apps. CORS-enabled; credentials go in the `X-Publishable-Key` and `Authorization` headers, and preflights are cached for 24 hours. You normally don't call these yourself."},{"name":"Notifications","description":"Send, schedule, edit and cancel notifications from your backend."},{"name":"Project users","description":"End users of your app, keyed by your own user id. Profiles, attributes and tags power targeting and templates."},{"name":"Segments","description":"Saved rule-based audiences over project user fields, attributes and tags."},{"name":"Project","description":"The project a secret key belongs to."}],"components":{"securitySchemes":{"secretKey":{"type":"http","scheme":"bearer","bearerFormat":"sk_…","description":"A project secret key (`sk_…`) from the dashboard. Server-side only."},"publishableKey":{"type":"apiKey","in":"header","name":"X-Publishable-Key","description":"The project's publishable key (`pk_…`). Safe to embed in client apps."},"userToken":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"A user token: an ES256 JWT your backend signs with one of the project's signing keys. Claims: `sub` (your user id) and `exp` (at most 7 days ahead). Set `kid` to the signing key id (`sig_…`) to skip trying each key."}},"schemas":{"ClientFeedResponse":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/FeedItem"}},"nextCursor":{"type":["string","null"],"description":"Pass as `cursor` to fetch the next page; `null` on the last page. A page can hold fewer than `limit` items while this is set, when filters skip most notifications."},"unreadCount":{"type":"integer","description":"Unread, non-archived notifications among the newest 100 (at most 100).","example":3},"unseenCount":{"type":"integer","description":"Notifications among the newest 100 published since the user last opened the feed (badge count, at most 100).","example":1},"config":{"$ref":"#/components/schemas/ClientConfig"}},"required":["items","nextCursor","unreadCount","unseenCount","config"],"additionalProperties":false,"description":"A feed page plus the project's dashboard configuration."},"FeedItem":{"type":"object","properties":{"id":{"type":"string","example":"ntf_4mQp9sX2vL0aZr8kTbYc"},"title":{"type":"string","description":"Localized and personalized.","example":"Hi Ada, your invoice is ready"},"body":{"type":["string","null"],"example":"Invoice #1042 for $29.00 is available."},"actionLabel":{"type":["string","null"],"example":"View invoice"},"actionUrl":{"type":["string","null"],"example":"/billing/invoices/1042"},"imageUrl":{"type":["string","null"]},"category":{"type":["string","null"],"example":"billing"},"data":{"type":["object","null"],"additionalProperties":{}},"locale":{"type":"string","description":"Locale actually used to render the content.","example":"en"},"publishedAt":{"type":"integer","description":"When the notification became visible (epoch ms).","example":1792540800000},"expiresAt":{"type":["integer","null"],"description":"When it disappears from feeds (epoch ms).","example":null},"readAt":{"type":["integer","null"],"description":"Epoch ms, `null` while unread.","example":null},"seenAt":{"type":["integer","null"],"description":"Epoch ms, `null` while unseen (counts towards the badge).","example":null},"archivedAt":{"type":["integer","null"],"description":"Epoch ms, `null` unless archived.","example":null}},"required":["id","title","body","actionLabel","actionUrl","imageUrl","category","data","locale","publishedAt","expiresAt","readAt","seenAt","archivedAt"],"additionalProperties":false,"description":"A notification as delivered to an end user."},"ClientConfig":{"type":"object","properties":{"projectName":{"type":"string"},"defaultLocale":{"type":"string"},"theme":{"$ref":"#/components/schemas/ThemeConfig"}},"required":["projectName","defaultLocale","theme"],"additionalProperties":false,"description":"Remote configuration managed from the dashboard."},"ThemeConfig":{"type":"object","properties":{"colorScheme":{"type":"string","enum":["light","dark","system"]},"primaryColor":{"type":"string","maxLength":64},"radius":{"type":"number","minimum":0,"maximum":32},"fontFamily":{"type":"string","maxLength":200},"light":{"type":"object","properties":{"background":{"type":"string","maxLength":64},"foreground":{"type":"string","maxLength":64},"muted":{"type":"string","maxLength":64},"mutedForeground":{"type":"string","maxLength":64},"border":{"type":"string","maxLength":64},"unreadBackground":{"type":"string","maxLength":64},"badgeBackground":{"type":"string","maxLength":64},"badgeForeground":{"type":"string","maxLength":64}},"additionalProperties":false},"dark":{"type":"object","properties":{"background":{"type":"string","maxLength":64},"foreground":{"type":"string","maxLength":64},"muted":{"type":"string","maxLength":64},"mutedForeground":{"type":"string","maxLength":64},"border":{"type":"string","maxLength":64},"unreadBackground":{"type":"string","maxLength":64},"badgeBackground":{"type":"string","maxLength":64},"badgeForeground":{"type":"string","maxLength":64}},"additionalProperties":false}},"additionalProperties":false,"description":"Visual customization managed in the dashboard. SDKs merge built-in defaults → this theme → the theme passed in code.","example":{"colorScheme":"system","primaryColor":"#6d28d9","radius":12}},"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["bad_request","unauthorized","forbidden","not_found","conflict","payment_required","rate_limited","internal_error"],"description":"Stable, machine-readable error code."},"message":{"type":"string","description":"Human-readable explanation."},"details":{"description":"Extra context. For `bad_request` validation errors: an array of `{ path, message, code }`."}},"required":["code","message"],"additionalProperties":false}},"required":["error"],"additionalProperties":false,"example":{"error":{"code":"bad_request","message":"Invalid request","details":[{"path":"contents","message":"At least one locale is required","code":"custom"}]}}},"ClientActionResponse":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"unreadCount":{"type":"integer","description":"Unread, non-archived notifications among the newest 100 (at most 100).","example":3},"unseenCount":{"type":"integer","description":"Notifications among the newest 100 published since the user last opened the feed (badge count, at most 100).","example":1}},"required":["ok","unreadCount","unseenCount"],"additionalProperties":false,"description":"Updated counts after applying the action."},"ClientActionRequest":{"type":"object","properties":{"action":{"type":"string","enum":["read","unread","archive","unarchive","click","read_all","seen_all"],"description":"`read`/`unread`/`archive`/`unarchive`/`click` apply to `ids`. `read_all` marks everything read; `seen_all` clears the badge (call it when the feed is opened)."},"ids":{"type":"array","items":{"type":"string","minLength":1,"maxLength":64},"maxItems":100,"description":"Notification ids. Required for per-notification actions, ignored otherwise.","example":["ntf_4mQp9sX2vL0aZr8kTbYc"]}},"required":["action"]},"ClientProfileResponse":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]}},"required":["ok"],"additionalProperties":false},"ClientUserProfile":{"type":"object","properties":{"email":{"type":["string","null"],"maxLength":320,"format":"email"},"name":{"type":["string","null"],"maxLength":200},"avatarUrl":{"type":["string","null"],"maxLength":2048},"locale":{"type":["string","null"],"minLength":2,"maxLength":35},"timezone":{"type":["string","null"],"maxLength":64},"attributes":{"type":"object","additionalProperties":{"anyOf":[{"type":"string","maxLength":1024},{"type":"number"},{"type":"boolean"},{"type":"null"},{"type":"array","items":{"type":"string","maxLength":256},"maxItems":100}]},"description":"Merged into existing attributes; `null` deletes a key."}},"description":"Profile fields the SDK may sync for the user. Tags can only be set from your backend.","example":{"name":"Ada Lovelace","email":"ada@example.com","locale":"en-GB","attributes":{"plan":"pro"}}},"Notification":{"type":"object","properties":{"id":{"type":"string","example":"ntf_4mQp9sX2vL0aZr8kTbYc"},"status":{"type":"string","enum":["draft","scheduled","published","expired","canceled"],"description":"`scheduled` = published with a future `publishAt`; `expired` = past `expiresAt`."},"audience":{"$ref":"#/components/schemas/StoredAudience"},"contents":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/NotificationContent"}},"defaultLocale":{"type":"string","example":"en"},"category":{"type":["string","null"]},"actionUrl":{"type":["string","null"]},"imageUrl":{"type":["string","null"]},"data":{"type":["object","null"],"additionalProperties":{}},"publishAt":{"type":["integer","null"],"description":"Epoch ms when it becomes visible.","example":null},"expiresAt":{"type":["integer","null"],"description":"Epoch ms when it disappears from feeds.","example":null},"recipientCount":{"type":["integer","null"],"description":"For `users` audiences."},"createdBy":{"type":["string","null"],"description":"Dashboard user id, `null` for API-created."},"createdAt":{"type":"integer","description":"Epoch ms.","example":1792540800000},"updatedAt":{"type":"integer","description":"Epoch ms.","example":1792540800000}},"required":["id","status","audience","contents","defaultLocale","category","actionUrl","imageUrl","data","publishAt","expiresAt","recipientCount","createdBy","createdAt","updatedAt"],"additionalProperties":false},"StoredAudience":{"anyOf":[{"type":"object","properties":{"type":{"type":"string","enum":["all"]}},"required":["type"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["users"]},"count":{"type":"integer","description":"Number of targeted users."}},"required":["type","count"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["tags"]},"tags":{"type":"array","items":{"type":"string"}},"match":{"type":"string","enum":["any","all"]}},"required":["type","tags","match"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["segment"]},"segmentId":{"type":"string"}},"required":["type","segmentId"],"additionalProperties":false}],"description":"The audience as stored. For `users` audiences only the count is returned."},"NotificationContent":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":200,"description":"Supports `{{user.name}}`-style templates."},"body":{"type":"string","maxLength":2000},"actionLabel":{"type":"string","maxLength":60}},"required":["title"],"additionalProperties":false},"CreateNotification":{"type":"object","properties":{"audience":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["all"]}},"required":["type"]},{"type":"object","properties":{"type":{"type":"string","enum":["users"]},"userIds":{"type":"array","items":{"type":"string","minLength":1,"maxLength":255,"description":"Your application's stable user id."},"minItems":1,"maxItems":1000}},"required":["type","userIds"]},{"type":"object","properties":{"type":{"type":"string","enum":["tags"]},"tags":{"type":"array","items":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[a-zA-Z0-9_:.-]+$"},"minItems":1,"maxItems":20},"match":{"type":"string","enum":["any","all"],"default":"any"}},"required":["type","tags"]},{"type":"object","properties":{"type":{"type":"string","enum":["segment"]},"segmentId":{"type":"string","minLength":1}},"required":["type","segmentId"]}],"description":"Who receives the notification."},"contents":{"type":"object","additionalProperties":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":200,"description":"Supports `{{user.name}}`-style templates."},"body":{"type":"string","maxLength":2000},"actionLabel":{"type":"string","maxLength":60}},"required":["title"]},"description":"Content keyed by locale, e.g. `{ en: {...}, de: {...} }`."},"defaultLocale":{"type":"string","minLength":2,"maxLength":35,"description":"Defaults to the project default locale."},"category":{"type":"string","minLength":1,"maxLength":64},"actionUrl":{"type":"string","maxLength":2048},"imageUrl":{"type":"string","maxLength":2048},"data":{"type":"object","additionalProperties":{},"description":"Arbitrary JSON delivered to SDKs and available to templates as `{{data.x}}`."},"status":{"type":"string","enum":["draft","published"],"default":"published"},"publishAt":{"type":"integer","exclusiveMinimum":0,"description":"Epoch milliseconds. Omit to publish immediately; a future value schedules it."},"expiresAt":{"type":"integer","exclusiveMinimum":0,"description":"Epoch milliseconds."},"idempotencyKey":{"type":"string","minLength":1,"maxLength":255}},"required":["audience","contents"],"example":{"audience":{"type":"tags","tags":["beta"],"match":"any"},"contents":{"en":{"title":"Hi {{user.name | there}}, dark mode is here","body":"Try it in **Settings**.","actionLabel":"Open settings"},"de":{"title":"Hallo {{user.name | du}}, der Dark Mode ist da","actionLabel":"Einstellungen öffnen"}},"category":"product","actionUrl":"/settings/appearance"}},"NotificationList":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Notification"}},"nextCursor":{"type":["string","null"],"description":"Pass as `cursor` to fetch the next page; `null` on the last page."}},"required":["items","nextCursor"],"additionalProperties":false},"NotificationWithStats":{"type":"object","properties":{"id":{"type":"string","example":"ntf_4mQp9sX2vL0aZr8kTbYc"},"status":{"type":"string","enum":["draft","scheduled","published","expired","canceled"],"description":"`scheduled` = published with a future `publishAt`; `expired` = past `expiresAt`."},"audience":{"$ref":"#/components/schemas/StoredAudience"},"contents":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/NotificationContent"}},"defaultLocale":{"type":"string","example":"en"},"category":{"type":["string","null"]},"actionUrl":{"type":["string","null"]},"imageUrl":{"type":["string","null"]},"data":{"type":["object","null"],"additionalProperties":{}},"publishAt":{"type":["integer","null"],"description":"Epoch ms when it becomes visible.","example":null},"expiresAt":{"type":["integer","null"],"description":"Epoch ms when it disappears from feeds.","example":null},"recipientCount":{"type":["integer","null"],"description":"For `users` audiences."},"createdBy":{"type":["string","null"],"description":"Dashboard user id, `null` for API-created."},"createdAt":{"type":"integer","description":"Epoch ms.","example":1792540800000},"updatedAt":{"type":"integer","description":"Epoch ms.","example":1792540800000},"stats":{"$ref":"#/components/schemas/NotificationStats"}},"required":["id","status","audience","contents","defaultLocale","category","actionUrl","imageUrl","data","publishAt","expiresAt","recipientCount","createdBy","createdAt","updatedAt","stats"],"additionalProperties":false},"NotificationStats":{"type":"object","properties":{"read":{"type":"integer","description":"Users that opened/read it individually.","example":120},"clicked":{"type":"integer","example":48},"archived":{"type":"integer","example":5}},"required":["read","clicked","archived"],"additionalProperties":false,"description":"Engagement from per-user state. Bulk \"mark all as read\" is not counted per notification."},"UpdateNotification":{"type":"object","properties":{"audience":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["all"]}},"required":["type"]},{"type":"object","properties":{"type":{"type":"string","enum":["users"]},"userIds":{"type":"array","items":{"type":"string","minLength":1,"maxLength":255,"description":"Your application's stable user id."},"minItems":1,"maxItems":1000}},"required":["type","userIds"]},{"type":"object","properties":{"type":{"type":"string","enum":["tags"]},"tags":{"type":"array","items":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[a-zA-Z0-9_:.-]+$"},"minItems":1,"maxItems":20},"match":{"type":"string","enum":["any","all"],"default":"any"}},"required":["type","tags"]},{"type":"object","properties":{"type":{"type":"string","enum":["segment"]},"segmentId":{"type":"string","minLength":1}},"required":["type","segmentId"]}],"description":"Who receives the notification."},"contents":{"type":"object","additionalProperties":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":200,"description":"Supports `{{user.name}}`-style templates."},"body":{"type":"string","maxLength":2000},"actionLabel":{"type":"string","maxLength":60}},"required":["title"]},"description":"Content keyed by locale, e.g. `{ en: {...}, de: {...} }`."},"defaultLocale":{"type":"string","minLength":2,"maxLength":35,"description":"Defaults to the project default locale."},"category":{"type":["string","null"],"minLength":1,"maxLength":64},"actionUrl":{"type":["string","null"],"maxLength":2048},"imageUrl":{"type":["string","null"],"maxLength":2048},"data":{"type":["object","null"],"additionalProperties":{}},"status":{"type":"string","enum":["draft","published"]},"publishAt":{"type":"integer","exclusiveMinimum":0,"description":"Epoch milliseconds. Omit to publish immediately; a future value schedules it."},"expiresAt":{"type":["integer","null"],"exclusiveMinimum":0}},"description":"Partial update. Only drafts and scheduled notifications can be edited.","example":{"publishAt":1792627200000}},"ProjectUser":{"type":"object","properties":{"id":{"type":"string","example":"sub_Vb3kq0Zr7XoP1sLm2NcA"},"userId":{"type":"string","description":"Your application's user id.","example":"user_123"},"email":{"type":["string","null"]},"name":{"type":["string","null"]},"avatarUrl":{"type":["string","null"]},"locale":{"type":["string","null"]},"timezone":{"type":["string","null"]},"attributes":{"type":"object","additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"null"},{"type":"array","items":{"type":"string"}}]}},"tags":{"type":"array","items":{"type":"string"}},"lastSeenAt":{"type":["integer","null"],"description":"Last SDK session (epoch ms, ~24h resolution).","example":null},"createdAt":{"type":"integer","description":"Epoch ms.","example":1792540800000},"updatedAt":{"type":"integer","description":"Epoch ms.","example":1792540800000}},"required":["id","userId","email","name","avatarUrl","locale","timezone","attributes","tags","lastSeenAt","createdAt","updatedAt"],"additionalProperties":false},"UpsertProjectUser":{"type":"object","properties":{"email":{"type":["string","null"],"maxLength":320,"format":"email"},"name":{"type":["string","null"],"maxLength":200},"avatarUrl":{"type":["string","null"],"maxLength":2048},"locale":{"type":["string","null"],"minLength":2,"maxLength":35},"timezone":{"type":["string","null"],"maxLength":64},"attributes":{"type":"object","additionalProperties":{"anyOf":[{"type":"string","maxLength":1024},{"type":"number"},{"type":"boolean"},{"type":"null"},{"type":"array","items":{"type":"string","maxLength":256},"maxItems":100}]},"description":"Merged into existing attributes; `null` deletes a key."},"tags":{"type":"array","items":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[a-zA-Z0-9_:.-]+$"},"maxItems":100,"description":"Replaces the project user's tags."}},"description":"Omitted fields are left unchanged. `attributes` are merged (a `null` value deletes a key); `tags` replace.","example":{"email":"ada@example.com","name":"Ada Lovelace","locale":"en-GB","attributes":{"plan":"pro","seats":5},"tags":["beta","admin"]}},"ProjectUserList":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/ProjectUser"}},"nextCursor":{"type":["string","null"],"description":"Pass as `cursor` to fetch the next page; `null` on the last page."}},"required":["items","nextCursor"],"additionalProperties":false},"UpdateTags":{"type":"object","properties":{"add":{"type":"array","items":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[a-zA-Z0-9_:.-]+$"},"maxItems":100},"remove":{"type":"array","items":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[a-zA-Z0-9_:.-]+$"},"maxItems":100}},"example":{"add":["beta"],"remove":["trial"]}},"FeedResponse":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/FeedItem"}},"nextCursor":{"type":["string","null"],"description":"Pass as `cursor` to fetch the next page; `null` on the last page. A page can hold fewer than `limit` items while this is set, when filters skip most notifications."},"unreadCount":{"type":"integer","description":"Unread, non-archived notifications among the newest 100 (at most 100).","example":3},"unseenCount":{"type":"integer","description":"Notifications among the newest 100 published since the user last opened the feed (badge count, at most 100).","example":1}},"required":["items","nextCursor","unreadCount","unseenCount"],"additionalProperties":false},"SegmentList":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Segment"}}},"required":["items"],"additionalProperties":false},"Segment":{"type":"object","properties":{"id":{"type":"string","example":"seg_9QwErTy0uIoP1aSdFgHj"},"name":{"type":"string"},"description":{"type":["string","null"]},"conditions":{"$ref":"#/components/schemas/ConditionGroup"},"createdAt":{"type":"integer","description":"Epoch ms.","example":1792540800000},"updatedAt":{"type":"integer","description":"Epoch ms.","example":1792540800000}},"required":["id","name","description","conditions","createdAt","updatedAt"],"additionalProperties":false},"ConditionGroup":{"type":"object","properties":{"combinator":{"type":"string","enum":["and","or"]},"conditions":{"type":"array","items":{"anyOf":[{"type":"object","properties":{"field":{"type":"string","pattern":"^(tags|email|name|locale|timezone|createdAt|attributes\\.[a-zA-Z0-9_.-]{1,64})$"},"operator":{"type":"string","enum":["eq","neq","in","not_in","contains","not_contains","starts_with","ends_with","gt","gte","lt","lte","exists","not_exists"]},"value":{"anyOf":[{"type":"string","maxLength":1024},{"type":"number"},{"type":"boolean"},{"type":"null"},{"type":"array","items":{"type":"string","maxLength":256},"maxItems":100}]}},"required":["field","operator"]},{"$ref":"#/components/schemas/ConditionGroup"}]},"minItems":1,"maxItems":50}},"required":["combinator","conditions"],"description":"Boolean tree of conditions (max depth 3, max 50 conditions).","example":{"combinator":"and","conditions":[{"field":"attributes.plan","operator":"eq","value":"pro"},{"field":"tags","operator":"contains","value":"beta"}]}},"SegmentPreview":{"type":"object","properties":{"sampled":{"type":"integer","description":"Recently active project users evaluated (max 1000).","example":1000},"matched":{"type":"integer","example":132},"examples":{"type":"array","items":{"type":"object","properties":{"userId":{"type":"string"},"email":{"type":["string","null"]},"name":{"type":["string","null"]}},"required":["userId","email","name"],"additionalProperties":false},"description":"Up to 5 matching project users."}},"required":["sampled","matched","examples"],"additionalProperties":false,"description":"An estimate over a sample, not an exact count."},"SegmentPreviewRequest":{"type":"object","properties":{"conditions":{"$ref":"#/components/schemas/ConditionGroup"}},"required":["conditions"]},"SegmentInput":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100},"description":{"type":["string","null"],"maxLength":500},"conditions":{"$ref":"#/components/schemas/ConditionGroup"}},"required":["name","conditions"],"example":{"name":"Pro users in beta","description":"Pro plan and opted into the beta","conditions":{"combinator":"and","conditions":[{"field":"attributes.plan","operator":"eq","value":"pro"},{"field":"tags","operator":"contains","value":"beta"}]}}},"SegmentUpdate":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100},"description":{"type":["string","null"],"maxLength":500},"conditions":{"$ref":"#/components/schemas/ConditionGroup"}},"example":{"name":"Pro users (beta)"}},"Project":{"type":"object","properties":{"id":{"type":"string","example":"prj_2xYc8vB0nM1aQs3dFg4h"},"name":{"type":"string","example":"Acme (production)"},"defaultLocale":{"type":"string","example":"en"},"publishableKey":{"type":"string","description":"Safe to embed in client apps.","example":"pk_8sYc2v0Ab1…"}},"required":["id","name","defaultLocale","publishableKey"],"additionalProperties":false}},"parameters":{}},"paths":{"/v1/client/feed":{"get":{"tags":["Client"],"operationId":"getClientFeed","summary":"Get the user's feed","description":"Returns the user's notifications, newest first, already localized and personalized, plus unread/unseen counts and the project's dashboard configuration (`config`). The user is the token's `sub`; their first request creates the project user.\n\nIf the project restricts **allowed origins**, browser requests from other origins get `403 forbidden`.\n\nResponses carry a weak `ETag` and `Cache-Control: private, no-cache`: the browser revalidates automatically and gets `304 Not Modified` when nothing changed.\n\nWhen the organization has used up its plan's monthly requests, each user's feed refreshes every 10 minutes: other polls get `429 rate_limited` with a `Retry-After` header (delay in seconds until that user's next refresh, exposed to browsers through `Access-Control-Expose-Headers`). Keep showing the feed you have and retry after that delay.\n\nAfter a realtime `changed` message, send its `at` as `X-Changed-At`.\n\nWhen the organization has no active, trialing or past_due subscription, this returns `402 payment_required`. Stop polling and show an empty feed; don't retry.","security":[{"publishableKey":[],"userToken":[]}],"parameters":[{"schema":{"type":"integer","minimum":1,"maximum":50,"default":20,"description":"Page size, 1–50.","example":20},"required":false,"description":"Page size, 1–50.","name":"limit","in":"query"},{"schema":{"type":"string","maxLength":200,"description":"Opaque cursor from a previous `nextCursor`."},"required":false,"description":"Opaque cursor from a previous `nextCursor`.","name":"cursor","in":"query"},{"schema":{"type":"string","enum":["all","unread","archived"],"default":"all","description":"`all` (non-archived), `unread` (non-archived, unread) or `archived`."},"required":false,"description":"`all` (non-archived), `unread` (non-archived, unread) or `archived`.","name":"status","in":"query"},{"schema":{"type":"string","maxLength":64,"description":"Only items with this category."},"required":false,"description":"Only items with this category.","name":"category","in":"query"},{"schema":{"type":"string","minLength":2,"maxLength":35,"description":"Preferred locale. Each candidate (this value, then the project user's locale) is matched exactly, then by language (`de-AT` → `de`), then by a regional sibling (`pt` → `pt-BR`); otherwise the notification's default locale is used.","example":"de-AT"},"required":false,"description":"Preferred locale. Each candidate (this value, then the project user's locale) is matched exactly, then by language (`de-AT` → `de`), then by a regional sibling (`pt` → `pt-BR`); otherwise the notification's default locale is used.","name":"locale","in":"query"},{"schema":{"type":"string","pattern":"^\\d+$","description":"The `at` of the realtime `changed` message that triggered this request (epoch ms). Project data cached before that time is reloaded.","example":"1767225600000"},"required":false,"description":"The `at` of the realtime `changed` message that triggered this request (epoch ms). Project data cached before that time is reloaded.","name":"x-changed-at","in":"header"}],"responses":{"200":{"description":"The feed page.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientFeedResponse"}}}},"304":{"description":"Not modified (the `If-None-Match` ETag still matches)."},"400":{"description":"Invalid request. `error.details` lists the validation issues.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unknown publishable key, or a missing, invalid or expired user token (`details.reason` says which check failed). Get a fresh token from your backend and retry once.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The organization has no active, trialing or past_due subscription. Server write endpoints and all client (SDK) endpoints are blocked; server reads keep working.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The request is not allowed, e.g. the browser origin is not allowed or the plan doesn't include the feature.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"The organization used up its plan's monthly requests, so each user's feed refreshes every 10 minutes. Retry after the `Retry-After` delay.","headers":{"Retry-After":{"description":"Seconds to wait before retrying (RFC 9110 delay-seconds).","schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error (`internal_error`). Idempotent requests can be retried.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/client/actions":{"post":{"tags":["Client"],"operationId":"postClientAction","summary":"Mark notifications read, unread, archived or clicked","description":"Applies an interaction for the user and returns the updated counts. Only notifications currently visible to the user are affected. `read_all` and `seen_all` are a single write regardless of feed size.\n\nWhen the organization has no active, trialing or past_due subscription, this returns `402 payment_required`. Stop polling and show an empty feed; don't retry.","security":[{"publishableKey":[],"userToken":[]}],"requestBody":{"required":true,"content":{"application/json":{"example":{"action":"read","ids":["ntf_4mQp9sX2vL0aZr8kTbYc"]},"schema":{"$ref":"#/components/schemas/ClientActionRequest"}}}},"responses":{"200":{"description":"Updated counts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientActionResponse"}}}},"400":{"description":"Invalid request. `error.details` lists the validation issues.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unknown publishable key, or a missing, invalid or expired user token (`details.reason` says which check failed). Get a fresh token from your backend and retry once.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The organization has no active, trialing or past_due subscription. Server write endpoints and all client (SDK) endpoints are blocked; server reads keep working.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The request is not allowed, e.g. the browser origin is not allowed or the plan doesn't include the feature.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error (`internal_error`). Idempotent requests can be retried.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/client/profile":{"post":{"tags":["Client"],"operationId":"updateClientProfile","summary":"Sync the user's profile","description":"Creates or updates the project user from profile fields known to the frontend. Attributes are merged (a `null` value deletes the key); tags can only be set from your backend. Writes are skipped when nothing changed. The SDKs call this only when the profile they were given differs from the last one they synced.\n\nWhen the organization has no active, trialing or past_due subscription, this returns `402 payment_required`. Stop polling and show an empty feed; don't retry.","security":[{"publishableKey":[],"userToken":[]}],"requestBody":{"required":true,"content":{"application/json":{"example":{"name":"Ada Lovelace","email":"ada@example.com","locale":"en-GB"},"schema":{"$ref":"#/components/schemas/ClientUserProfile"}}}},"responses":{"200":{"description":"Profile synced.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientProfileResponse"}}}},"400":{"description":"Invalid request. `error.details` lists the validation issues.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unknown publishable key, or a missing, invalid or expired user token (`details.reason` says which check failed). Get a fresh token from your backend and retry once.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The organization has no active, trialing or past_due subscription. Server write endpoints and all client (SDK) endpoints are blocked; server reads keep working.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The request is not allowed, e.g. the browser origin is not allowed or the plan doesn't include the feature.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error (`internal_error`). Idempotent requests can be retried.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/client/realtime":{"get":{"tags":["Client"],"operationId":"connectClientRealtime","summary":"Receive feed changes over a WebSocket","description":"Opens a WebSocket that says when the user's feed changed, so the app refetches it (`GET /v1/client/feed`) instead of polling. Available on the Growth and Scale plans and on custom plans. The connection counts as one request; messages don't, the refetches they trigger do.\n\n**Credentials.** Browsers can't set headers on a WebSocket, so the publishable key and user token are subprotocols: `Sec-WebSocket-Protocol: notificationstream.v1, pk.<publishable key>, bearer.<user token>`. The server selects `notificationstream.v1`. The checks are the same as on the other client endpoints.\n\n**Messages.** The server sends JSON text frames: `{\"type\":\"changed\",\"at\":<epoch ms>,\"spread\":<ms>}`. Fetch the feed at a random point within `spread` ms (it spreads a broadcast's refetches), with `X-Changed-At: <at>`. Nothing is replayed: fetch once after every (re)connect. Changes are signaled for notifications sent, edited, canceled or deleted (scheduled ones when they publish), the user's actions on any device, project user and segment updates.\n\n**Keepalive.** Send the text frame `ping` every 60 seconds; the server answers `pong`. Any other frame closes the connection with `1003`.\n\n**Refused connections** complete the upgrade and close right away with a code, because browsers hide the HTTP status of a failed upgrade:\n\n- `4401`: unknown publishable key, or a missing, invalid or expired user token (also sent when the token expires while connected). Get a fresh token and reconnect.\n- `4402`: no active subscription. Don't reconnect.\n- `4403`: origin not allowed, or the plan doesn't include realtime. Don't reconnect.\n- `4429`: monthly request limit reached; the reason is `rate_limited:<seconds>`. Reconnect after that delay.\n\nOther closes (deploys, network changes) are normal: reconnect with backoff.","security":[],"parameters":[{"schema":{"type":"string","enum":["websocket"]},"required":true,"name":"upgrade","in":"header"},{"schema":{"type":"string","description":"`notificationstream.v1, pk.<publishable key>, bearer.<user token>`. The server selects `notificationstream.v1`.","example":"notificationstream.v1, pk_live_4mQp9sX2vL0aZr8k, bearer.eyJhbGciOiJFUzI1NiJ9…"},"required":true,"description":"`notificationstream.v1, pk.<publishable key>, bearer.<user token>`. The server selects `notificationstream.v1`.","name":"sec-websocket-protocol","in":"header"}],"responses":{"101":{"description":"Switching protocols: the WebSocket is open (or closes right away with a code above)."},"400":{"description":"Invalid request. `error.details` lists the validation issues.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error (`internal_error`). Idempotent requests can be retried.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/notifications":{"post":{"tags":["Notifications"],"operationId":"createNotification","summary":"Send a notification","description":"Creates and (by default) publishes a notification.\n\n- **Audiences**: `all`, `tags` (`match: any | all`) and `segment` are a single row and are matched against each user when their feed loads, so users created later are included. `users` targets up to 1000 of your user ids; users that never opened the SDK yet are created as placeholders and see it on first session.\n- **Localization**: `contents` is keyed by locale. Users get the best match (`de-AT` → `de` → `defaultLocale`).\n- **Personalization**: `{{user.name | there}}`, `{{user.email}}`, `{{user.id}}`, `{{user.attributes.plan}}` and `{{data.key}}` are rendered per user.\n- **Scheduling**: a future `publishAt` schedules it; `expiresAt` removes it from feeds. Use `status: \"draft\"` to save without publishing.\n\n**Plans**: `tags` and `segment` audiences, `contents` in more than one locale, `{{…}}` templates and a `publishAt` more than a minute ahead need the Growth plan or higher (`403 forbidden` on Starter).\n\nPass an `Idempotency-Key` header (or `idempotencyKey`) to make retries safe. New broadcasts can take up to ~30 seconds to reach every region (feeds are cached per project).","security":[{"secretKey":[]}],"parameters":[{"schema":{"type":"string","minLength":1,"maxLength":255,"description":"Retries with the same key return the originally created notification instead of sending twice. Same as the `idempotencyKey` body field.","example":"invoice-1042-ready"},"required":false,"description":"Retries with the same key return the originally created notification instead of sending twice. Same as the `idempotencyKey` body field.","name":"idempotency-key","in":"header"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateNotification"}}}},"responses":{"201":{"description":"The created notification (or the original one for an idempotent retry).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Notification"}}}},"400":{"description":"Invalid request. `error.details` lists the validation issues.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The organization has no active, trialing or past_due subscription. Server write endpoints and all client (SDK) endpoints are blocked; server reads keep working.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The request is not allowed, e.g. the browser origin is not allowed or the plan doesn't include the feature.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error (`internal_error`). Idempotent requests can be retried.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"tags":["Notifications"],"operationId":"listNotifications","summary":"List notifications","description":"Newest first. Paginate with `cursor`/`nextCursor`.","security":[{"secretKey":[]}],"parameters":[{"schema":{"type":"integer","minimum":1,"maximum":100,"description":"1–100, default 50.","example":50},"required":false,"description":"1–100, default 50.","name":"limit","in":"query"},{"schema":{"type":"string","maxLength":200,"description":"Opaque cursor from a previous response's `nextCursor`."},"required":false,"description":"Opaque cursor from a previous response's `nextCursor`.","name":"cursor","in":"query"},{"schema":{"type":"string","enum":["draft","scheduled","published","expired","canceled"]},"required":false,"name":"status","in":"query"}],"responses":{"200":{"description":"A page of notifications.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationList"}}}},"400":{"description":"Invalid request. `error.details` lists the validation issues.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error (`internal_error`). Idempotent requests can be retried.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/notifications/{id}":{"get":{"tags":["Notifications"],"operationId":"getNotification","summary":"Get a notification","description":"Returns the notification with engagement stats.","security":[{"secretKey":[]}],"parameters":[{"schema":{"type":"string","minLength":1,"maxLength":64,"example":"ntf_4mQp9sX2vL0aZr8kTbYc"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"The notification and its stats.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationWithStats"}}}},"400":{"description":"Invalid request. `error.details` lists the validation issues.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource does not exist in this project.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error (`internal_error`). Idempotent requests can be retried.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Notifications"],"operationId":"updateNotification","summary":"Update a draft or scheduled notification","description":"Published, expired and canceled notifications are immutable (`409 conflict`). Set `status: \"published\"` to publish a draft. The fields you change follow the same plan rules as creating a notification.","security":[{"secretKey":[]}],"parameters":[{"schema":{"type":"string","minLength":1,"maxLength":64,"example":"ntf_4mQp9sX2vL0aZr8kTbYc"},"required":true,"name":"id","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateNotification"}}}},"responses":{"200":{"description":"The updated notification.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Notification"}}}},"400":{"description":"Invalid request. `error.details` lists the validation issues.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The organization has no active, trialing or past_due subscription. Server write endpoints and all client (SDK) endpoints are blocked; server reads keep working.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The request is not allowed, e.g. the browser origin is not allowed or the plan doesn't include the feature.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource does not exist in this project.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The resource is in a state that does not allow this change.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error (`internal_error`). Idempotent requests can be retried.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Notifications"],"operationId":"deleteNotification","summary":"Delete a notification","description":"Permanently deletes the notification and all per-user state.","security":[{"secretKey":[]}],"parameters":[{"schema":{"type":"string","minLength":1,"maxLength":64,"example":"ntf_4mQp9sX2vL0aZr8kTbYc"},"required":true,"name":"id","in":"path"}],"responses":{"204":{"description":"Deleted."},"400":{"description":"Invalid request. `error.details` lists the validation issues.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The organization has no active, trialing or past_due subscription. Server write endpoints and all client (SDK) endpoints are blocked; server reads keep working.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource does not exist in this project.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error (`internal_error`). Idempotent requests can be retried.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/notifications/{id}/cancel":{"post":{"tags":["Notifications"],"operationId":"cancelNotification","summary":"Cancel a notification","description":"Removes it from every feed (including scheduled ones). It stays in your history with status `canceled`.","security":[{"secretKey":[]}],"parameters":[{"schema":{"type":"string","minLength":1,"maxLength":64,"example":"ntf_4mQp9sX2vL0aZr8kTbYc"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"The canceled notification.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Notification"}}}},"400":{"description":"Invalid request. `error.details` lists the validation issues.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The organization has no active, trialing or past_due subscription. Server write endpoints and all client (SDK) endpoints are blocked; server reads keep working.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource does not exist in this project.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error (`internal_error`). Idempotent requests can be retried.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/project-users/{userId}":{"put":{"tags":["Project users"],"operationId":"upsertProjectUser","summary":"Create or update a project user","description":"Identifies an end user by your own user id. Omitted fields are unchanged, `attributes` are merged (`null` deletes a key) and `tags` replace the current tags. Tags can only be set from your backend, never by the SDK.","security":[{"secretKey":[]}],"parameters":[{"schema":{"type":"string","minLength":1,"maxLength":255,"description":"Your application's user id.","example":"user_123"},"required":true,"description":"Your application's user id.","name":"userId","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpsertProjectUser"}}}},"responses":{"200":{"description":"The project user.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectUser"}}}},"400":{"description":"Invalid request. `error.details` lists the validation issues.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The organization has no active, trialing or past_due subscription. Server write endpoints and all client (SDK) endpoints are blocked; server reads keep working.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error (`internal_error`). Idempotent requests can be retried.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"tags":["Project users"],"operationId":"getProjectUser","summary":"Get a project user","security":[{"secretKey":[]}],"parameters":[{"schema":{"type":"string","minLength":1,"maxLength":255,"description":"Your application's user id.","example":"user_123"},"required":true,"description":"Your application's user id.","name":"userId","in":"path"}],"responses":{"200":{"description":"The project user.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectUser"}}}},"400":{"description":"Invalid request. `error.details` lists the validation issues.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource does not exist in this project.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error (`internal_error`). Idempotent requests can be retried.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Project users"],"operationId":"deleteProjectUser","summary":"Delete a project user","description":"Deletes the project user and their notification state (e.g. for GDPR erasure requests).","security":[{"secretKey":[]}],"parameters":[{"schema":{"type":"string","minLength":1,"maxLength":255,"description":"Your application's user id.","example":"user_123"},"required":true,"description":"Your application's user id.","name":"userId","in":"path"}],"responses":{"204":{"description":"Deleted."},"400":{"description":"Invalid request. `error.details` lists the validation issues.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The organization has no active, trialing or past_due subscription. Server write endpoints and all client (SDK) endpoints are blocked; server reads keep working.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource does not exist in this project.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error (`internal_error`). Idempotent requests can be retried.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/project-users":{"get":{"tags":["Project users"],"operationId":"listProjectUsers","summary":"List project users","description":"Newest first. Filter by `search` (user id, email, name) or `tag`.","security":[{"secretKey":[]}],"parameters":[{"schema":{"type":"integer","minimum":1,"maximum":100,"description":"1–100, default 50."},"required":false,"description":"1–100, default 50.","name":"limit","in":"query"},{"schema":{"type":"string","maxLength":200,"description":"Opaque cursor from a previous response's `nextCursor`."},"required":false,"description":"Opaque cursor from a previous response's `nextCursor`.","name":"cursor","in":"query"},{"schema":{"type":"string","maxLength":200,"description":"Substring match on user id, email or name."},"required":false,"description":"Substring match on user id, email or name.","name":"search","in":"query"},{"schema":{"type":"string","maxLength":64,"description":"Only project users with this tag."},"required":false,"description":"Only project users with this tag.","name":"tag","in":"query"}],"responses":{"200":{"description":"A page of project users.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectUserList"}}}},"400":{"description":"Invalid request. `error.details` lists the validation issues.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error (`internal_error`). Idempotent requests can be retried.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/project-users/{userId}/tags":{"post":{"tags":["Project users"],"operationId":"updateProjectUserTags","summary":"Add or remove tags","description":"Adds and removes tags without replacing the whole list.","security":[{"secretKey":[]}],"parameters":[{"schema":{"type":"string","minLength":1,"maxLength":255,"description":"Your application's user id.","example":"user_123"},"required":true,"description":"Your application's user id.","name":"userId","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTags"}}}},"responses":{"200":{"description":"The updated project user.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectUser"}}}},"400":{"description":"Invalid request. `error.details` lists the validation issues.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The organization has no active, trialing or past_due subscription. Server write endpoints and all client (SDK) endpoints are blocked; server reads keep working.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource does not exist in this project.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error (`internal_error`). Idempotent requests can be retried.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/project-users/{userId}/feed":{"get":{"tags":["Project users"],"operationId":"getProjectUserFeed","summary":"Get a project user's feed","description":"The same feed the SDK shows, rendered for this user. Useful for server-side rendering and digest emails. Supports `ETag`/`If-None-Match`.","security":[{"secretKey":[]}],"parameters":[{"schema":{"type":"string","minLength":1,"maxLength":255,"description":"Your application's user id.","example":"user_123"},"required":true,"description":"Your application's user id.","name":"userId","in":"path"},{"schema":{"type":"integer","minimum":1,"maximum":50,"default":20,"description":"Page size, 1–50.","example":20},"required":false,"description":"Page size, 1–50.","name":"limit","in":"query"},{"schema":{"type":"string","maxLength":200,"description":"Opaque cursor from a previous `nextCursor`."},"required":false,"description":"Opaque cursor from a previous `nextCursor`.","name":"cursor","in":"query"},{"schema":{"type":"string","enum":["all","unread","archived"],"default":"all","description":"`all` (non-archived), `unread` (non-archived, unread) or `archived`."},"required":false,"description":"`all` (non-archived), `unread` (non-archived, unread) or `archived`.","name":"status","in":"query"},{"schema":{"type":"string","maxLength":64,"description":"Only items with this category."},"required":false,"description":"Only items with this category.","name":"category","in":"query"},{"schema":{"type":"string","minLength":2,"maxLength":35,"description":"Preferred locale. Each candidate (this value, then the project user's locale) is matched exactly, then by language (`de-AT` → `de`), then by a regional sibling (`pt` → `pt-BR`); otherwise the notification's default locale is used.","example":"de-AT"},"required":false,"description":"Preferred locale. Each candidate (this value, then the project user's locale) is matched exactly, then by language (`de-AT` → `de`), then by a regional sibling (`pt` → `pt-BR`); otherwise the notification's default locale is used.","name":"locale","in":"query"}],"responses":{"200":{"description":"The feed page.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeedResponse"}}}},"304":{"description":"Not modified."},"400":{"description":"Invalid request. `error.details` lists the validation issues.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource does not exist in this project.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error (`internal_error`). Idempotent requests can be retried.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/segments":{"get":{"tags":["Segments"],"operationId":"listSegments","summary":"List segments","security":[{"secretKey":[]}],"responses":{"200":{"description":"All segments of the project, by name.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SegmentList"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error (`internal_error`). Idempotent requests can be retried.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Segments"],"operationId":"createSegment","summary":"Create a segment","description":"A saved, rule-based audience. Use it with `audience: { type: \"segment\", segmentId }`; membership is evaluated when each user loads their feed. Requires the Growth plan or higher. Conditions target `tags`, `email`, `name`, `locale`, `timezone`, `createdAt` or custom `attributes.<key>` with operators `eq`, `neq`, `in`, `not_in`, `contains`, `not_contains`, `starts_with`, `ends_with`, `gt`, `gte`, `lt`, `lte`, `exists`, `not_exists`. String comparisons are case-insensitive. Groups nest up to 3 levels with `and`/`or`.","security":[{"secretKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SegmentInput"}}}},"responses":{"201":{"description":"The created segment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Segment"}}}},"400":{"description":"Invalid request. `error.details` lists the validation issues.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The organization has no active, trialing or past_due subscription. Server write endpoints and all client (SDK) endpoints are blocked; server reads keep working.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The request is not allowed, e.g. the browser origin is not allowed or the plan doesn't include the feature.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error (`internal_error`). Idempotent requests can be retried.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/segments/preview":{"post":{"tags":["Segments"],"operationId":"previewSegment","summary":"Preview segment conditions","description":"Estimates who matches a condition tree by evaluating it over up to 1000 recently active project users. Read-only. Conditions target `tags`, `email`, `name`, `locale`, `timezone`, `createdAt` or custom `attributes.<key>` with operators `eq`, `neq`, `in`, `not_in`, `contains`, `not_contains`, `starts_with`, `ends_with`, `gt`, `gte`, `lt`, `lte`, `exists`, `not_exists`. String comparisons are case-insensitive. Groups nest up to 3 levels with `and`/`or`.","security":[{"secretKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SegmentPreviewRequest"}}}},"responses":{"200":{"description":"Sample-based estimate.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SegmentPreview"}}}},"400":{"description":"Invalid request. `error.details` lists the validation issues.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error (`internal_error`). Idempotent requests can be retried.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/segments/{id}":{"get":{"tags":["Segments"],"operationId":"getSegment","summary":"Get a segment","security":[{"secretKey":[]}],"parameters":[{"schema":{"type":"string","minLength":1,"maxLength":64,"example":"seg_9QwErTy0uIoP1aSdFgHj"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"The segment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Segment"}}}},"400":{"description":"Invalid request. `error.details` lists the validation issues.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource does not exist in this project.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error (`internal_error`). Idempotent requests can be retried.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Segments"],"operationId":"updateSegment","summary":"Update a segment","description":"Changes apply to every notification targeting this segment, including already published ones. Requires the Growth plan or higher.","security":[{"secretKey":[]}],"parameters":[{"schema":{"type":"string","minLength":1,"maxLength":64,"example":"seg_9QwErTy0uIoP1aSdFgHj"},"required":true,"name":"id","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SegmentUpdate"}}}},"responses":{"200":{"description":"The updated segment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Segment"}}}},"400":{"description":"Invalid request. `error.details` lists the validation issues.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The organization has no active, trialing or past_due subscription. Server write endpoints and all client (SDK) endpoints are blocked; server reads keep working.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The request is not allowed, e.g. the browser origin is not allowed or the plan doesn't include the feature.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource does not exist in this project.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error (`internal_error`). Idempotent requests can be retried.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Segments"],"operationId":"deleteSegment","summary":"Delete a segment","description":"Notifications that targeted it stop matching anyone.","security":[{"secretKey":[]}],"parameters":[{"schema":{"type":"string","minLength":1,"maxLength":64,"example":"seg_9QwErTy0uIoP1aSdFgHj"},"required":true,"name":"id","in":"path"}],"responses":{"204":{"description":"Deleted."},"400":{"description":"Invalid request. `error.details` lists the validation issues.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The organization has no active, trialing or past_due subscription. Server write endpoints and all client (SDK) endpoints are blocked; server reads keep working.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource does not exist in this project.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error (`internal_error`). Idempotent requests can be retried.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/project":{"get":{"tags":["Project"],"operationId":"getProject","summary":"Get the current project","description":"The project the secret key belongs to. Handy to verify a key and fetch the publishable key.","security":[{"secretKey":[]}],"responses":{"200":{"description":"The project.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Project"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error (`internal_error`). Idempotent requests can be retried.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"webhooks":{}}