{"openapi":"3.1.0","info":{"title":"Notifications API","version":"0.1.0"},"paths":{"/api/v1/health":{"get":{"description":"Deployment health check with database reachability.","responses":{"200":{"description":"OK"},"503":{"description":"HTTP 503"}},"security":[]}},"/api/v1/push/vapid-public-key":{"get":{"description":"Returns the public VAPID key used by browser push subscribe.","responses":{"200":{"description":"OK"}},"security":[{"bearerAuth":[]}]}},"/api/v1/preferences":{"get":{"description":"Lists effective preferences for a normalized principal.","responses":{"200":{"description":"OK"}},"security":[{"bearerAuth":[]}]},"patch":{"description":"Updates preferences for a normalized principal. Optional initializeOnly:true creates missing preference rows without overwriting saved choices.","responses":{"200":{"description":"OK"}},"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"initializeOnly":{"type":"boolean"},"principal":{"type":"object","properties":{"sourceApp":{"type":"string","enum":["openlabs","buildspace","bio-token","bios"]},"principalType":{"type":"string","enum":["orgActor","user","profile","wallet"]},"principalId":{"type":"string","minLength":1}},"required":["sourceApp","principalType","principalId"],"additionalProperties":false},"preferences":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","enum":["bios.binder.completed","bios.binder.failed","bios.binder.design_completed","bios.binder.ready_for_md","bios.deep_research.update_ready","bios.deep_research.failed","bios.deep_research.completed","openlabs.post.created","openlabs.comment.created","openlabs.review.received","buildspace.application.approved","buildspace.application.rejected","buildspace.application.needs_action","buildspace.launch.scheduled","buildspace.launch.live","bio-token.launch.event","bio-token.ignition.started","bio-token.ignition.ending_soon","bio-token.tge","bio-token.ignition.funding_reached","bio-token.project.added","bio-token.token.price_increase"]},"channel":{"type":"string","enum":["email","push"]},"enabled":{"type":"boolean"}},"required":["type","channel","enabled"],"additionalProperties":false}}},"required":["principal","preferences"],"additionalProperties":false}}}}}},"/api/v1/push-subscriptions":{"get":{"description":"Lists active push subscription IDs and endpoints for a normalized principal, without encryption keys.","responses":{"200":{"description":"OK"},"400":{"description":"HTTP 400"},"403":{"description":"HTTP 403"}},"security":[{"bearerAuth":[]}]},"post":{"description":"Saves or refreshes a browser push subscription.","responses":{"201":{"description":"HTTP 201"}},"security":[{"bearerAuth":[]}]},"delete":{"description":"Revokes a browser push subscription.","responses":{"200":{"description":"OK"}},"security":[{"bearerAuth":[]}]}},"/api/v1/dispatch":{"post":{"description":"Queues a push notification broadcast for explicit normalized recipients. Optional idempotencyKey (1–256 characters, trimmed) is scoped to the registered event type and its source app. Retrying with the same payload and recipient set returns the original queued acknowledgement; reusing the key for different content returns 409. Omitting the key creates a new broadcast on every request. Email remains on the legacy notification path.","responses":{"202":{"description":"HTTP 202"},"400":{"description":"HTTP 400"},"403":{"description":"HTTP 403"},"409":{"description":"HTTP 409"}},"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"idempotencyKey":{"type":"string","minLength":1,"maxLength":256},"type":{"type":"string","enum":["bios.binder.completed","bios.binder.failed","bios.binder.design_completed","bios.binder.ready_for_md","bios.deep_research.update_ready","bios.deep_research.failed","bios.deep_research.completed","openlabs.post.created","openlabs.comment.created","openlabs.review.received","buildspace.application.approved","buildspace.application.rejected","buildspace.application.needs_action","buildspace.launch.scheduled","buildspace.launch.live","bio-token.launch.event","bio-token.ignition.started","bio-token.ignition.ending_soon","bio-token.tge","bio-token.ignition.funding_reached","bio-token.project.added","bio-token.token.price_increase"]},"recipients":{"minItems":1,"type":"array","items":{"type":"object","properties":{"sourceApp":{"type":"string","enum":["openlabs","buildspace","bio-token","bios"]},"principalType":{"type":"string","enum":["orgActor","user","profile","wallet"]},"principalId":{"type":"string","minLength":1}},"required":["sourceApp","principalType","principalId"],"additionalProperties":false}},"payload":{}},"required":["type","recipients","payload"],"additionalProperties":false}}}}}},"/api/v1/test-notification":{"post":{"description":"Sends a dev/staging test notification.","responses":{"202":{"description":"HTTP 202"}},"security":[{"bearerAuth":[]}]}}},"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"opaque","scheme":"bearer","type":"http"}}}}