HTMLPix API Skill
Use the API contracts below when generating code, curl commands, SDK wrappers, or troubleshooting responses.
Base URL and Auth
- API Base URL:
https://api.htmlpix.com - Private endpoints require:
Authorization: Bearer <API_KEY> - API keys are prefixed
hpx_and managed athttps://htmlpix.com/api-keys - Do not call private endpoints from browser client code; mint URLs on the backend.
Authentication Flow
Every private request goes through this pipeline:
- Extract
Authorization: Bearer <key>header - Hash the key with SHA-256, look up by hash in the
apiKeystable - Verify key status is
active(otherwise403 KEY_INACTIVE) - Query the user's subscription status from
quotastable - Subscription must be
active,trialing, orcanceledwith time remaining
Error Codes
| Status | Code | Meaning |
|---|---|---|
401 | MISSING_API_KEY | No Authorization: Bearer header |
401 | INVALID_API_KEY | Key not found |
403 | KEY_INACTIVE | Key revoked or disabled |
402 | NO_SUBSCRIPTION | No quota record found |
402 | SUBSCRIPTION_INACTIVE | Subscription expired or past_due |
429 | QUOTA_EXCEEDED | Monthly mint quota exhausted |
Endpoint Contracts
POST /v1/url (private)
Mint one signed image URL. The server resolves template variables, executes the template JSX, uploads the rendered VNode to Cloudflare R2/KV, and returns a signed URL pointing to the Cloudflare Worker that renders the final image.
Request JSON:
| Field | Type | Required | Constraints |
|---|---|---|---|
templateId | string | yes | max 128 chars |
width | integer | no | 1–4096, defaults to template value or 1200 |
height | integer | no | 1–4096, defaults to template value or 630 |
format | string | no | png, jpeg, or webp — defaults to template value or webp |
quality | integer | no | 0–100, defaults to template value |
devicePixelRatio | number | no | positive number, defaults to template value or 1 |
tv | string | no | max 128 chars, cache-busting version tag — defaults to template updatedAt |
variables | object | no | max 64 keys, key max 64 chars, each value max 4000 chars serialized. Values can be string, number, boolean, null, array, or nested object. |
Response 200:
{ "url": "https://image.htmlpix.com/v1/image?...&sig=...", "expiresAt": 1710345600000 }
Operational limits:
- Body size max: 32 KB
- Rate: 120 mint requests per 60s per API key
- Concurrency: 4 in-flight per user
- URLs expire after ~5 years by default
POST /v1/urls (private)
Mint multiple signed image URLs in one request.
Request JSON:
{ "items": [ /* each item has same shape as POST /v1/url */ ] }
items.lengthmust be 1–25
Response 200:
{ "urls": [{ "templateId": "...", "url": "...", "expiresAt": 1710345600000 }] }
Operational limits:
- Body size max: 256 KB
GET /v1/image (public, signed — served by Cloudflare Worker)
Requests to api.htmlpix.com/v1/image are 301-redirected to the Cloudflare Worker at image.htmlpix.com/v1/image. The Worker renders the image from VNode data stored in R2/KV.
Required query params:
| Param | Description |
|---|---|
templateId | Template identifier |
uid | User ID that minted the URL |
exp | Expiry timestamp (unix ms) |
sig | HMAC signature — invalidated if any param changes |
Optional query params:
| Param | Default | Description |
|---|---|---|
width | 1200 | Image width |
height | 630 | Image height |
format | webp | png, jpeg, or webp |
quality | — | 0–100 |
dpr | — | Device pixel ratio |
tv | — | Cache version tag |
rv | — | Render version |
v_<name> | — | Template variables as v_title=Hello |
Behavior:
403 URL_EXPIRED— URL past expiry403 INVALID_SIGNATURE— params modified after signing- Returns image bytes with immutable caching headers and ETag
- Supports
304 Not ModifiedviaIf-None-Match
Important: treat minted URLs as opaque. If any query value changes, signature validation fails.
GET /v1/templates (private)
List templates visible to the authenticated user.
Query params:
| Param | Default | Values |
|---|---|---|
scope | all | all, mine, starter |
Response 200:
{ "templates": [ /* template objects */ ] }
POST /v1/templates (private)
Create a custom template. Templates are defined by a single code field containing JSX/TSX source code. The server compiles, validates, and extracts variables automatically.
Request JSON:
| Field | Type | Required | Constraints |
|---|---|---|---|
name | string | yes | max 120 chars |
description | string | no | max 2000 chars |
code | string | yes | max 180,000 chars — JSX/TSX template source |
Legacy fields jsx, variables, googleFonts, width, height, format are rejected with error code LEGACY_TEMPLATE_PAYLOAD. Use code only.
Response 201:
{ "templateId": "abc123..." }
Error responses:
400 COMPILE_ERROR— JSX compilation or validation failed400 LEGACY_TEMPLATE_PAYLOAD— sent a deprecated field
GET /v1/templates/:templateId (private)
Fetch one template visible to the caller.
Response 200:
{ "template": { /* template object */ } }
404 TEMPLATE_NOT_FOUNDif not visible to caller
PATCH /v1/templates/:templateId (private)
Update template fields. At least one field required.
Request JSON:
| Field | Type | Required | Constraints |
|---|---|---|---|
name | string | no | max 120 chars |
description | string | no | max 2000 chars |
code | string | no | max 180,000 chars — JSX/TSX template source |
Legacy fields jsx, variables, googleFonts, width, height, format are rejected.
Response 200:
{ "templateId": "abc123..." }
Error responses:
400 EMPTY_UPDATE— no updatable field provided400 COMPILE_ERROR— JSX compilation failed404 TEMPLATE_NOT_FOUND— template doesn't exist or not owned by caller
POST /v1/templates/generate (private)
AI-assisted template generation. Always uses batch format.
Request JSON:
{
"items": [
{ "prompt": "A social card with bold title and gradient background", "width": 1200, "height": 630 }
]
}
| Field | Type | Required | Constraints |
|---|---|---|---|
items | array | yes | 1–5 items |
items[].prompt | string | yes | max 2000 chars |
items[].width | integer | no | 1–4096, default 1200 |
items[].height | integer | no | 1–4096, default 630 |
Response 200:
{
"results": [
{ "ok": true, "result": { /* generated template data */ } },
{ "ok": false, "error": "error message" }
]
}
Each item settles independently — some may succeed while others fail.
Error responses:
429 QUOTA_EXCEEDED— AI generation quota hit402 SUBSCRIPTION_REQUIRED— paid plan needed422 PARSE_FAILED— AI output couldn't be parsed502 AI_NOT_CONFIGURED— AI provider not set up502 GENERATION_FAILED— generic AI failure
Safe Integration Pattern
- Keep API key server-side only.
- Mint with
POST /v1/urlor/v1/urlson your backend. - Store/embed the returned
urldirectly (meta tags, email HTML, social cards, etc.). - Do not re-sign or mutate query params client-side.
- Handle
402,429, and503with retries/fallback messaging.
Minimal Examples
# Mint a single image URL
curl -X POST https://api.htmlpix.com/v1/url \
-H "Authorization: Bearer $HTMLPIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"templateId": "tmpl_123",
"variables": { "title": "Launch Day" },
"width": 1200,
"height": 630,
"format": "png"
}'
# List your templates
curl -X GET "https://api.htmlpix.com/v1/templates?scope=mine" \
-H "Authorization: Bearer $HTMLPIX_API_KEY"
# Create a template from code
curl -X POST https://api.htmlpix.com/v1/templates \
-H "Authorization: Bearer $HTMLPIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "My Social Card",
"code": "export default function Template({ title }) {\n return <div style={{ fontSize: 48 }}>{title}</div>\n}"
}'
# AI-generate a template
curl -X POST https://api.htmlpix.com/v1/templates/generate \
-H "Authorization: Bearer $HTMLPIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"items": [{ "prompt": "Minimalist blog post OG image with title and author" }]
}'
If the user asks for an endpoint not listed above, say it is not present in the current server route table and avoid inventing routes.