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/jsonfor 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
- Sign in to the web app.
- Open Settings from the account menu.
- Scroll to API keys and click + New API key.
- Enter a label (e.g. "Home Assistant" or "CI"), pick an optional expiry, and create.
- 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.
201on create endpoints,204on 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-Afteris the minimum wait. 5xx- Server error or subsystem not configured (
503for 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.