Danny Keane REST API
Public HTTP endpoints on dannykeane.com, Danny Keane's site, versioned under /api/v1 and described in full by the OpenAPI 3.1 spec at https://dannykeane.com/openapi.json. No authentication: no account, no API key. This page is generated from that spec, so the two can't disagree.
Endpoints
10 operations, all relative to https://dannykeane.com. Request and response bodies are JSON except the avatar proxy, which returns a PNG.
GET /api/v1/stats -- Live visitor count
Active users on the site right now, from Google Analytics 4. Cached for 60 seconds. Operation ID: getStats.
Responses: 200 Current active users · 429 Rate limited; Retry-After says when to retry · 500 Internal error.
GET /api/v1/github-contributions -- GitHub contribution calendar
Danny Keane's GitHub contribution graph: 52 weeks of daily counts plus aggregate stats. Operation ID: getGithubContributions.
Responses: 200 Contribution weeks and aggregate stats · 500 Server not configured or internal error · 502 Upstream GitHub API failure.
GET /api/v1/npc-chat -- NPC chat health check
Liveness probe for the NPC chat service. Operation ID: npcChatHealth.
Responses: 200 Service status.
POST /api/v1/npc-chat -- Chat with a Habbo room NPC
Dialogue endpoint for the NPCs in the Habbo prototype at /habbo. Takes a visitor message and an NPC id, returns the NPC's reply as chat-bubble messages plus any triggered action. Operation ID: npcChat.
JSON body: message (required, string, up to 500 characters), npcId (required, string), context (object).
Responses: 200 The NPC's reply · 400 Missing, empty, or over-long message · 429 Rate limited; Retry-After says when to retry · 500 Internal error · 503 Service not configured.
POST /api/v1/track -- First-party analytics event
Accepts two allowlisted event names and nothing else; see /privacy for exactly what is stored. Rate limited. Safe to retry with the same Idempotency-Key: the repeat is acknowledged without recording a second event. Operation ID: trackEvent.
Parameters: Idempotency-Key (header, string, up to 255 characters): Client-generated key (a UUID works) that makes retries safe. A repeat within 24 hours returns 200 with Idempotent-Replayed: true and records nothing.
JSON body: event (required, one of hire_opened, command_run), command (string, up to 40 characters), ref (string, up to 80 characters), referrer (string, up to 300 characters), deepLinked (boolean).
Responses: 200 Event accepted, or an Idempotency-Key replay acknowledged · 400 Invalid body, unknown event, or malformed Idempotency-Key.
POST /api/v1/batch -- Run several read-only operations in one request
Executes up to 10 argument-free GET operations, named by operationId (getStats, getGithubContributions, npcChatHealth), and returns each result with its own status -- one failed item never fails the batch. Optional async mode for clients that don't want to hold a connection open: send Prefer: respond-async for a 202 and a job to poll at getJob instead. Jobs are stateless -- the id carries the request, the batch runs when you poll, nothing is stored -- and ids expire an hour after creation. In async mode an unknown operationId fails the whole request with a 400. Operation ID: runBatch.
Parameters: Prefer (header, one of respond-async): RFC 7240. respond-async returns 202 Accepted with a job to poll instead of the results. Omit for the synchronous 200.
JSON body: requests (required, array of 1-10 items; each item: id (string), operationId (required, one of getStats, getGithubContributions, npcChatHealth)).
Responses: 200 Per-operation results, in request order · 202 Prefer: respond-async only. Job accepted; poll Location for the result. · 400 Invalid body, unknown operationId, or too many operations (in async mode, also a request too large for a job id).
GET /api/v1/jobs/{id} -- Poll an async batch job
Returns the result of a batch submitted with Prefer: respond-async. Jobs are stateless: the id carries the request, the batch runs when you poll, and nothing is stored, so every poll runs it afresh and returns status succeeded with the same body a synchronous runBatch would. Ids expire an hour after creation (410). Operation ID: getJob.
Parameters: id (path, required, string, up to 2048 characters): The id from the 202 body; the Location header carries the full URL.
Responses: 200 The finished job, with the batch response as result · 404 Not a job id this API issued · 410 Job id expired; resubmit the batch · 429 Rate limited; Retry-After says when to retry.
GET /api/v1/work -- Roles, paginated
Every role from the /work tables -- company, role, years, and a slug for get_work_history's detail -- one cursor-paginated page at a time. Operation ID: listWork.
Parameters: limit (query, integer 1-20, default 10): Page size. · cursor (query, string): The next_cursor from the previous page. Omit for the first page.
Responses: 200 One page of roles · 400 limit out of range or cursor not issued by this endpoint · 429 Rate limited; Retry-After says when to retry.
GET /api/v1/habbo-avatar -- Habbo avatar image proxy
Proxies Habbo's avatar imaging service for the /habbo prototype, with validated parameters. Returns a PNG. Operation ID: getHabboAvatar.
Parameters: figure (query, required, string) · direction (query, required, integer 0-7) · head_direction (query, integer 0-7) · size (query, one of n, l) · gesture (query, one of nrm, sml, sad, srp, spk, eyb) · frame (query, integer) · action (query, string)
Responses: 200 Avatar image · 400 Missing or invalid parameters · 502 Upstream imager failure.
GET /api/v1/manifest -- Web app manifest
The PWA manifest, themed by the optional `theme` parameter. Operation ID: getManifest.
Parameters: theme (query, one of dark, light)
Responses: 200 Web app manifest JSON.
Pagination, versioning, and rate limits
Pagination
list endpoints (/api/v1/work) are cursor-paginated. Pass limit for the page size; each page returns next_cursor, null on the last page -- send it back as cursor for the next one. Cursors are opaque tokens.
Async jobs
send Prefer: respond-async to POST /api/v1/batch for a 202 whose Location (and body status_url) is /api/v1/jobs/{id}; GET it for the result. Jobs are stateless -- the id carries the request, the work runs when you poll, nothing is stored -- and ids expire an hour after creation.
Versioning
the stable surface is /api/v1/. Breaking changes ship as a new version prefix; a deprecated endpoint answers with Deprecation and Sunset headers (RFC 9745 / RFC 8594) at least six months before removal.
Deprecation policy
every /api response carries `Link: <https://dannykeane.com/developers>; rel="sunset"` (RFC 8594 section 6), pointing at this policy before any Sunset date is set. Nothing is deprecated today.
Rate limits
every response carries advisory RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset, and RateLimit-Policy headers (60 requests per 60-second window per endpoint); a 429 carries Retry-After.
Errors
every 4xx/5xx body is the ErrorResponse object -- a machine-readable `code`, a human-readable `error`, and an optional `hint`.
Error codes
Every 4xx and 5xx body is an ErrorResponse, and its code is one of: invalid_body, unknown_event, missing_message, empty_message, message_too_long, missing_parameters, invalid_parameter, rate_limited, not_configured, upstream_error, method_not_allowed, not_found, internal_error, job_expired. Match on code, not on the message.
Clients
The official TypeScript and Python SDKs cover every data operation above, with typed errors, cursor iteration, and 429 retries. Both install the dannykeane CLI.
Machine-readable
The spec itself, the RFC 9727 API catalog that lists it, an API-scoped llms.txt, and auth.md for agents that check for one.
More developer docs
Everything dannykeane.com offers developers and agents is indexed at /developers.