2DTiler API is a Cloudflare Workers service that syncs pixel art color palettes from Lospec into Cloudflare D1 and serves them through a small read-only HTTP API.
The current API is intentionally small: a health check plus a palette listing endpoint with pagination, search, and tag filtering.
- Daily Lospec palette sync via a scheduled Cloudflare Worker job
- Read-only palette API backed by Cloudflare D1
- Pagination, search, and tag filtering for palette queries
- CORS allowlist for trusted browser clients
- Per-IP rate limiting on the palette listing endpoint
- TypeScript, Hono, Wrangler, and Vitest-based development workflow
- TypeScript
- Hono
- Cloudflare Workers
- Cloudflare D1
- Wrangler
- Vitest
- Production:
https://api.2dtiler.com - Local development:
http://127.0.0.1:8787orhttp://localhost:8787
| Method | Path | Description |
|---|---|---|
GET |
/ |
Health check endpoint |
GET |
/lospec_palettes |
List synced Lospec palettes from D1 |
Simple health check endpoint.
Example:
curl https://api.2dtiler.com/Response:
{}Returns a paginated list of palettes stored in the local D1 cache. The count
field in the response is the total number of matching palettes before
pagination is applied.
| Name | Type | Required | Description |
|---|---|---|---|
page |
integer | No | Zero-based page index. Defaults to 0. Each page returns up to 100 palettes. |
search |
string | No | Case-insensitive title search. Leading and trailing whitespace is ignored. |
tags |
string | No | Case-insensitive exact tag match. Leading and trailing whitespace is ignored. |
curl "https://api.2dtiler.com/lospec_palettes?page=0&search=sunset&tags=warm"{
"count": 1,
"items": [
{
"id": "sunset-1",
"title": "Sunset",
"slug": "sunset",
"description": "Warm palette",
"tags": ["warm", "sky"],
"user": "alice",
"colors": ["#ff6600", "#220044"],
"examples": [
{
"image": "https://cdn.lospec.com/pixel-art/sunset.png",
"description": "Preview"
}
],
"published_at": "2026-04-01T00:00:00.000Z"
}
]
}- Results are ordered by
published_atdescending, theniddescending. tagsandcolorsare returned as parsed JSON arrays when present.examples[].imagevalues are normalized to full Lospec CDN URLs.- The endpoint is rate limited to
10requests per minute per IP.
| Status | When it happens | Example body |
|---|---|---|
400 |
page is not a non-negative integer |
{ "error": "Invalid page parameter. Expected a non-negative integer with 100 results per page." } |
403 |
Browser request comes from a disallowed origin | { "error": "Forbidden origin" } |
429 |
Rate limit is exceeded | { "error": "Rate limit exceeded. Try again in a minute." } |
- Node.js and npm
- A Cloudflare account if you want to run against configured resources or deploy changes
- Access to the Cloudflare resources referenced in
wrangler.toml
This repository does not currently include D1 schema or migration files, so local and deployed environments are expected to have the required database objects and bindings available already.
npm installnpm run devWrangler starts the Worker locally and serves the API on its default development port, usually 8787.
| Command | Purpose |
|---|---|
npm run dev |
Start the local Worker with Wrangler |
npm run test |
Run the Vitest suite with coverage |
npm run lint |
Run ESLint on src/ |
npm run typecheck |
Run TypeScript without emitting output |
npm run deploy |
Deploy the Worker with Wrangler |
The test configuration enforces coverage thresholds of 80.01% for lines, functions, branches, and statements.
| Name | Required | Purpose |
|---|---|---|
DB |
Yes | Cloudflare D1 database used to store and query palettes |
RATE_LIMITER |
Yes | Cloudflare rate-limiter binding used by GET /lospec_palettes |
INTERNAL_API_KEY |
Situational | Trusted internal key used to bypass the browser-origin allowlist when sent as X-Internal-Api-Key |
Allowed browser origins:
https://2dtiler.comhttps://app.2dtiler.comhttp://localhost:4321http://127.0.0.1:4321http://localhost:5173http://127.0.0.1:5173http://localhost:8787http://127.0.0.1:8787
Requests without an Origin header are allowed. OPTIONS preflight requests are supported for allowed origins.
The Worker is configured to handle requests for api.2dtiler.com/*.
Palette data is fetched from Lospec using https://lospec.com/palette-list/load.
The scheduled sync job:
- Runs daily at
14:00 UTC - Fetches Lospec pages in newest-first order
- Inserts unseen palettes into D1
- Stops when Lospec returns no more results or when a page includes already-known palette IDs
- Uses a
15minute timeout for the scheduled run
src/
index.ts Cloudflare Worker entrypoint
app.ts Hono app used for HTTP route composition
app/
controllers/ Request handlers
middleware/ Cross-cutting HTTP behavior such as CORS
models/ Data normalization and response mapping
routes/ Route registration
services/ Lospec and D1 data access
jobs/
sync-palettes.ts Scheduled Lospec sync job
tests/
*.test.ts App, service, sync, and worker tests
npm run deployDeployment assumes the Cloudflare zone, D1 database, rate-limiter binding, and any required secrets are already configured for this project.
This project is licensed under the MIT License. See LICENSE for details.