Pulsyn API and Developer Resources
Developer guide to the Pulsyn public API, OpenAPI specification, versioning, rate limiting, error format, and machine-readable content negotiation.
Last updated: August 30, 2026
The Pulsyn public API is the HTTP surface of pulsyn.tech. It covers the Rune 1 waitlist, Stripe-backed checkout and reservation, a deployment health check, and Open Graph image generation. It does not expose health data, ring telemetry, or account management. The canonical machine-readable description is the OpenAPI 3.1 document at https://pulsyn.tech/openapi.json.
Use /openapi.json as the source of truth for paths, methods, request shapes, and response schemas. This guide summarizes the contract in prose and calls out behavior that is easy to misread from the schema alone.
OpenAPI specification
- URL:
https://pulsyn.tech/openapi.json - Version: OpenAPI 3.1.0
- Info title: Pulsyn Public API
Every path, method, and status code in the document was derived from the actual handlers under src/routes/api/** and the centralized error contract. If a handler changes, the document is re-derived from the handler.
API versioning
The Pulsyn API is versioned by response header, not by URL path. Every /api/* response — successes, errors, and the provider/machine-only routes alike — carries an API-Version response header.
- Read the header, not a path prefix. There are no
/v1/or/v2/URL namespaces and none are planned. A client that wants to know which contract it is talking to readsAPI-Versionfrom any/api/*response. - One source of truth. The header value is always equal to
info.versionin/openapi.json; both are generated from the same package version, so they cannot disagree. - What version changes mean. The version tracks the site release that last changed the public API surface. Additive changes (new optional fields, new endpoints) may ship without a major version bump; a breaking change to a documented request or response shape will bump the version and be announced in this guide and in the OpenAPI document before it lands.
Deprecation and sunset policy (RFC 8594)
No operation is currently deprecated, so no Deprecation or Sunset response headers are emitted today.
When an operation is deprecated in the future:
- The server will emit a
Deprecationheader on every response of that operation from the moment the deprecation is announced. - The server will emit an RFC 8594
Sunsetheader carrying the planned removal date as an HTTP-date, on every response of that operation. - This guide and the OpenAPI document will describe the migration window and the successor operation before the
Sunsetdate. - Clients should treat either header as a signal to re-check
API-Versionand this document.
Rate limiting
Every non-exempt /api/* request is rate limited per client IP at 60 requests per one-minute fixed window, per server process.
- Advertised equals enforced. The
RateLimit-Limit,RateLimit-Remaining, andRateLimit-Resetresponse headers on every non-exempt/api/*response come from the same limiter decision that enforces the threshold — the advertised numbers can never drift from the enforced ones. - 429 behavior. The request that exceeds the window is rejected with
429 Too Many Requests, aRetry-Afterheader (seconds until the window resets), and an RFC 9457application/problem+jsonbody withcode: "rate_limited". - Per-process scope. Counters live in one process's memory. There is no shared external store, so a deployment running multiple instances enforces up to N × 60 requests per window for one client across N instances. This is a deliberate, documented tradeoff, not a bug.
- Exemptions. Three route families are exempt: they are never counted, never limited, and never receive
RateLimit-*headers (a header would advertise a ceiling that is not enforced there):/api/webhooks/*— Stripe retries deliveries with its own backoff; a 429 here would silently drop payment events./api/cron/*— the scheduler is a trusted, secret-authenticated caller, not a burst-abuse surface./api/unsubscribe— RFC 8058 one-click unsubscribe must always return 200; a 429 could leave a recipient stuck receiving mail they opted out of.
- Exempt routes still carry
API-Version. The version header is a property of the API surface, not of the rate limiter.
Public endpoints
These are the only endpoints a third-party caller should invoke directly.
GET /api/health
Public and unauthenticated by default. Returns a minimal JSON body:
{ "status": "ok", "timestamp": "2026-05-31T12:00:00.000Z" }Send the x-health-check-token header to request the detailed mode. When the header is present the server compares it against a server-side configured token:
- Match: returns the detailed body with
status,timestamp,uptime(process uptime in seconds), andsupabaseProjectRef(the leftmost label of the configured Supabase URL, ornullwhen it cannot be parsed). - Mismatch:
401with an RFC 9457 body. - Header present but the server has no token configured:
500with an RFC 9457 body. The server does not silently fall back to the public check.
Only the header name x-health-check-token is part of the public contract. No token value is ever exposed in documentation or in a successful response body.
GET /api/og
Image endpoint. Returns image/png (1200x630) on success, never JSON. Query parameters title, subtitle, type, and badge control the card text and variant; each is sanitized and truncated server-side. On internal generation failure the endpoint returns a plain-text 500 body, not a Problem Details object. Unsupported methods return 405 with an Allow: GET header.
POST /api/waitlist
Public state-changing endpoint. Adds an email to the Rune 1 waitlist. The handler validates email and optional name, source, and referredBy fields, persists the entry, and best-effort syncs the contact to the email audience. Re-submitting an email already on the list succeeds with alreadyOnList: true rather than erroring. Validation failures return 400 with an RFC 9457 body.
POST /api/checkout/stripe
Public state-changing endpoint. Validates the full order payload (email, shippingAddress, variantId, ringSize, optional billingAddress, paymentMethod, and addOns), persists a pending order, creates a Stripe Checkout session, and returns { redirectUrl }. Order status is updated later by Stripe webhooks, not by this endpoint.
POST /api/checkout/stripe/reserve
Public state-changing endpoint. Validates email and optional name, persists a pending reservation order for the fixed reservation amount, creates a Stripe Checkout session, and returns { redirectUrl }. Like the full-order endpoint, status transitions happen via webhooks.
Provider and machine-only routes
The following routes exist but are not part of the public API. Do not call them directly. They are documented here only so integrators do not mistake them for public capabilities.
POST /api/webhooks/stripe— Stripe webhook receiver. Signature-verified and provider-acknowledged. Callable only by Stripe.POST /api/cron/waitlist-followup— Scheduler-only dispatch. Callable only by the internal scheduler.POST /api/unsubscribe— One-click unsubscribe invoked via a signed token embedded in outbound email headers, not a general API operation.
These three are intentionally excluded from the OpenAPI document and from the centralized public-API error normalization. Their contracts are provider or machine-to-machine, not caller-to-Pulsyn.
Error format
Every documented JSON error is an RFC 9457 Problem Details object with media type application/problem+json:
{
"type": "https://pulsyn.tech/problems/invalid_email",
"title": "Invalid email address",
"status": 400,
"detail": "Provide a valid, correctly formatted email address in the `email` field.",
"code": "invalid_email",
"hint": "Provide a valid, correctly formatted email address in the `email` field.",
"instance": "https://pulsyn.tech/problems/invalid_email#request-id"
}type— stable absolute URI of the formhttps://pulsyn.tech/problems/<code>.title— short human-readable summary of the problem type.status— HTTP status code. Always mirrors the actual response status. The HTTP status is authoritative.detail— human-readable detail for this specific occurrence.instance— optional URI identifying this specific occurrence. Present only when the server provides it.code— stable snake_case identifier from a small fixed registry (for exampleinvalid_email,not_found,method_not_allowed,unauthorized,internal_error). Usecodefor branching.hint— actionable resolution guidance, distinct fromdetail. When handling an error, act onhint.
The code and hint members are Pulsyn extensions alongside the RFC 9457 core. status and code always agree; if they ever appear to disagree, trust the HTTP status.
Unknown paths and unsupported methods
A single centralized handler normalizes two cases for every in-scope /api/* path:
- Unknown path:
GET /api/does-not-exist(or any method to an undocumented/api/*path) returns404withcode: "not_found". - Unsupported method:
DELETE /api/waitlist(or any documented path called with a method it does not support) returns405withcode: "method_not_allowed"and a preservedAllowheader listing the methods that path actually supports.
Both shapes are application/problem+json and are produced centrally, not per route, so an unlisted path or method reliably produces one of these two shapes rather than an ad-hoc error. The three provider/machine-only routes above are excluded from this normalization.
Webhook semantics
Publicly documentable webhook behavior is limited to what a caller can observe without secrets or internal detail:
- Stripe webhooks are signature-verified. The server validates the provider signature before processing.
- Webhook handlers are provider-acknowledged. A successful receipt returns a JSON acknowledgement; failures are logged server-side and the provider may retry according to its own delivery policy.
- No secret, credential, or operational implementation detail is part of the public contract.
Do not attempt to synthesize or replay provider callbacks. They are not a public integration surface.
Content negotiation for docs and marketing pages
The site offers text/markdown as an alternative representation for a small allowlist of routes. Request it with Accept: text/markdown:
//docs/docs/[category]/docs/[category]/[slug]
For example, GET /docs/developer/pulsyn-api-and-developer-resources with Accept: text/markdown returns the same article as Markdown. The same URL without that header, or with a browser default Accept, returns HTML. Absent or malformed Accept defaults to HTML.
Rules that matter for integrators and crawlers:
- HTML is the default. A request with no
Acceptheader, or withAccept: text/html,Accept: */*, or any header where HTML is acceptable, returns HTML. - 406 for an existing allowlisted resource when neither representation is acceptable. If the resource exists but the
Acceptheader explicitly excludes bothtext/htmlandtext/markdown(for exampleAccept: application/jsonwith no wildcard), the server returns406 Not Acceptablewith a plain-text body. - 404 outranks 406. A missing resource always returns
404, even when theAcceptheader would otherwise trigger406. A request for/docs/developer/does-not-existwithAccept: application/jsonreturns404, not406. The same applies to unknown categories and to generic unknown pages outside the allowlist whenAccept: text/markdownis sent — the server returns a Markdown404recovery body that points to/sitemap.xml,/llms.txt,/docs, and/openapi.json.
Routes outside the allowlist — including /api/*, /checkout, /_app/*, static assets, and the already machine-readable endpoints (/robots.txt, /sitemap.xml, /llms.txt, /llms-full.txt, /blog/rss.xml, /docs/search-index.json, /openapi.json) — do not participate in this negotiation.
What Pulsyn does not offer
- No MCP server. Pulsyn does not currently offer an MCP server.
- No SDK. There is no Pulsyn SDK to install. Integrate directly against the HTTP endpoints and the OpenAPI document.
- No public write API beyond checkout and waitlist. The only public state-changing operations are
POST /api/waitlist,POST /api/checkout/stripe, andPOST /api/checkout/stripe/reserve. There is no public API for writing health data, managing accounts, or mutating orders outside the checkout flow.
This list reflects the current surface. Do not infer additional capabilities from the presence of provider or machine-only routes.
