Hazydo

API reference · v1

Hazydo HTTP API

Everything the web app does is a REST call. Mint a personal API key, paste it below, and run live calls against your own server with one click — every endpoint on this page is a real button.

No key set — Run buttons send unauthenticated requests.

Mint a key at Settings → API keys in the web app. Saved to your browser only; clear to remove.

Overview

Hazydo exposes a JSON-over-HTTP API rooted at https://hazy.r8.rs/api/v1. Each request authenticates as a user — the same RLS rules, role gates, and project memberships that govern your web app session apply unchanged.

The API is the web app's only backend. There is no "internal" API reserved for the operator UI; every action the web app takes is one of the endpoints below. This means anything you can do by clicking in the browser, you can do from a script.

  • Base URL. https://hazy.r8.rs/api/v1
  • Content type. application/json for request and response bodies.
  • Auth. Authorization: Bearer hzy_u_… (see Authentication).
  • Timestamps. RFC 3339 UTC.
  • IDs. UUIDv4, lowercase.
  • OpenAPI spec. YAML · JSON — every endpoint below is rendered from this file.

Authentication

Every API call carries a personal API key on the Authorization header. Keys are minted from Settings → API keys in the web app and identify you as the user who created them — they have your role, see your projects, and respect your project memberships.

Personal API keys start with hzy_u_ and authenticate as you — sent as Authorization: Bearer hzy_u_….

Creating a key

  1. Sign in to the web app.
  2. Open Settings from the account menu.
  3. Scroll to API keys and click + New API key.
  4. Enter a label (e.g. "Home Assistant" or "CI"), pick an optional expiry, and create.
  5. Copy the key from the one-time reveal dialog. The plaintext is shown exactly once — you cannot recover it from the server. If you lose it, revoke and recreate.

Using a key

curl -H "Authorization: Bearer $HAZYDO_API_KEY" \
     https://hazy.r8.rs/api/v1/users/me

Or paste the key into the box at the top of this page and click Run on any endpoint to fire it directly from the browser. The Run buttons go to the same server you're reading from right now.

Revocation

Revoking a key marks it inactive immediately and the next request using it returns 401 Unauthorized with "api key revoked". The audit trail keeps the record; the key just can't authenticate anymore.

Conventions

Request bodies

Send Content-Type: application/json. Numeric fields use JSON numbers; timestamps are RFC 3339 strings (2026-05-15T08:00:00Z). Boolean flags accept true / false.

Partial updates

PATCH endpoints accept any subset of the resource's mutable fields; omitted fields are unchanged. Sending null explicitly clears a nullable column (e.g. "due_at": null removes a task's due date).

Error responses

Errors have a JSON body of the shape { "code": "area.subkey", "error": "human msg" }. The code field is the stable identifier the SPA's i18n catalogue dispatches against; error is the legacy human-readable message. Codes follow area.subkey shape (auth.invalid_credentials, billing.not_configured, …).

Pagination

v1 returns full collections in a single response. Hazydo projects have at most low-thousands of tasks; if you outgrow that, file a feature request.

Rate limits

Personal API keys are rate-limited per key with a token bucket: 60 requests/second sustained, 120 burst. JWT and cookie authentication aren't limited at the API layer (the web app uses them) — only hzy_u_… traffic counts against the bucket. Successful responses include X-RateLimit-Limit, X-RateLimit-Remaining, and (under deficit) X-RateLimit-Reset. Over the limit is 429 with code: rate_limited and Retry-After (seconds).

Loading endpoints from openapi.json

Status codes

200 OK / 201 Created / 204 No Content
Success. 201 on create endpoints, 204 on delete + status-toggle.
400 Bad Request
Malformed JSON, missing required field, validation failed. Body has error.
401 Unauthorized
Missing, malformed, revoked, or expired credentials. The body distinguishes: "invalid api key", "api key revoked", "api key expired".
402 Payment Required
Subscription lapsed; the account is read-only. Pay or cancel via /billing/checkout.
403 Forbidden
Authenticated but not allowed — viewer trying to edit, non-owner trying to delete a project, unverified account hitting a cost-bearing route (with code: email_unverified), demo account on a no-mutate route (code: demo_account).
404 Not Found
Resource doesn't exist or RLS hides it. The two are not distinguished by design.
409 Conflict
State conflict — duplicate email on signup, per-user API-key cap, project_ready violation, already-decided approvals.
429 Too Many Requests
Per-key rate limit exceeded. Body has code: rate_limited; Retry-After is the minimum wait.
5xx
Server error or subsystem not configured (503 for billing on installs that didn't wire it up). Body has a human-readable description; the underlying error is in the server logs with a correlation id.

Versioning & stability

All public endpoints are mounted under /api/v1. Within v1 we promise additive change only: new fields can appear on response bodies, new endpoints can show up — existing fields won't be removed, renamed, or repurposed without a new version prefix.

When something does need to change incompatibly, it will land under /api/v2 with at least one release of parallel support. There is no v2 today. The web app is also a v1 consumer, so an internal change that would break v1 would break the web app first — incentives are aligned.