Skip to content

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 reads API-Version from any /api/* response.
  • One source of truth. The header value is always equal to info.version in /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 Deprecation header on every response of that operation from the moment the deprecation is announced.
  • The server will emit an RFC 8594 Sunset header 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 Sunset date.
  • Clients should treat either header as a signal to re-check API-Version and 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, and RateLimit-Reset response 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, a Retry-After header (seconds until the window resets), and an RFC 9457 application/problem+json body with code: "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), and supabaseProjectRef (the leftmost label of the configured Supabase URL, or null when it cannot be parsed).
  • Mismatch: 401 with an RFC 9457 body.
  • Header present but the server has no token configured: 500 with 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 form https://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 example invalid_email, not_found, method_not_allowed, unauthorized, internal_error). Use code for branching.
  • hint — actionable resolution guidance, distinct from detail. When handling an error, act on hint.

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) returns 404 with code: "not_found".
  • Unsupported method: DELETE /api/waitlist (or any documented path called with a method it does not support) returns 405 with code: "method_not_allowed" and a preserved Allow header 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 Accept header, or with Accept: 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 Accept header explicitly excludes both text/html and text/markdown (for example Accept: application/json with no wildcard), the server returns 406 Not Acceptable with a plain-text body.
  • 404 outranks 406. A missing resource always returns 404, even when the Accept header would otherwise trigger 406. A request for /docs/developer/does-not-exist with Accept: application/json returns 404, not 406. The same applies to unknown categories and to generic unknown pages outside the allowlist when Accept: text/markdown is sent — the server returns a Markdown 404 recovery 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, and POST /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.

Was this helpful?