---
title: "Danny Keane REST API"
description: "Reference for the Danny Keane REST API on dannykeane.com: every /api/v1 endpoint with its parameters and responses, cursor pagination, errors, rate limits, and batch reads. OpenAPI 3.1, no auth."
canonical: https://dannykeane.com/developers/api
last-updated: 2026-09-27
---

# 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.

- [Danny Keane SDKs and CLI](/developers/sdk)

## 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.

- [openapi.json](/openapi.json)
- [api-catalog](/.well-known/api-catalog)
- [api/llms.txt](/api/llms.txt)
- [auth.md](/auth.md)

## More developer docs

Everything dannykeane.com offers developers and agents is indexed at /developers.

- [Developer docs](/developers)
- [MCP server](/developers/mcp)
- [SDKs and CLI](/developers/sdk)
- [developers/llms.txt](/developers/llms.txt)

## Elsewhere

- [Home](https://dannykeane.com/)
- [About](https://dannykeane.com/about)
- [Contact](https://dannykeane.com/contact)
- [Developers](https://dannykeane.com/developers)
- [Habbo](https://dannykeane.com/habbo)
- [Privacy](https://dannykeane.com/privacy)
- [llms.txt](https://dannykeane.com/llms.txt) -- machine-readable summary
- [llms-full.txt](https://dannykeane.com/llms-full.txt) -- the complete version
