Heron Tools API Documentation
The Heron Tools API provides a bounded, stateless set of text, conversion, image, and PDF tools. Production base URL:
https://api.heron.tools
The current version is published under the /v1 path. The machine-readable OpenAPI 3.1 contract is the definitive reference for endpoint parameters and response schemas:
https://api.heron.tools/v1/openapi.json
Quick Start
All endpoints except the health check and OpenAPI contract require a Bearer API Key:
export HERON_API_KEY='your-api-key'
curl 'https://api.heron.tools/v1/text/slug' \
-H "Authorization: Bearer $HERON_API_KEY" \
-H 'Content-Type: application/json' \
--data '{"text":"Hello Heron Tools"}'
Response:
{
"data": {
"slug": "hello-heron-tools",
"inputLength": 17,
"slugLength": 17,
"wordCount": 3
},
"meta": {
"requestId": "5a8d83c3-4d9e-43a7-8d94-3f9356881269"
}
}
Production API Keys are not currently available through a self-service endpoint and must be issued by the Heron Tools API operator. Do not embed long-lived API Keys in public web pages or client packages; publicly accessible browser code cannot keep secrets. Server-side requests are not subject to browser CORS restrictions.
General Conventions
Authentication
Endpoints that require authentication must include:
Authorization: Bearer <HERON_API_KEY>
Missing or invalid credentials return 401 Unauthorized with WWW-Authenticate: Bearer realm="heron-tools-api".
Only these endpoints do not require authentication:
GET /v1/healthGET /v1/openapi.json
Request ID
Every response includes X-Request-Id. The meta.requestId in successful JSON responses and the requestId in error responses contain the same value. Record this value when troubleshooting.
Successful JSON Responses
JSON endpoints use this standard response format:
{
"data": {},
"meta": {
"requestId": "..."
}
}
The JSON request body limit is 16 KiB.
Error Responses
Errors use application/problem+json:
{
"type": "https://api.heron.tools/problems/invalid-input",
"title": "Invalid input",
"status": 422,
"code": "INVALID_INPUT",
"detail": "The request did not match the endpoint contract.",
"requestId": "5a8d83c3-4d9e-43a7-8d94-3f9356881269",
"issues": [
{
"path": "text",
"message": "Too big: expected string to have <=10000 characters"
}
]
}
issues appears only when field-level validation errors exist. Common status codes:
| Status Code | Meaning |
|---|---|
400 | Request body is missing or cannot be read |
401 | API Key is missing or invalid |
404 | Route does not exist |
413 | Request body exceeds the endpoint limit |
415 | Unsupported Content-Type |
422 | Invalid parameter, file, or operation |
500 | Internal API or deployment configuration error |
502 | Upstream exchange-rate service is unavailable |
Clients should branch on the HTTP status code and stable code field rather than parsing the detail text.
File Requests and Privacy
- Image and single-PDF endpoints accept file bytes directly in the request body, not JSON or Base64.
- Single-file requests may include
X-Filenamefor metadata or the download filename; the maximum length is 255 characters. - The image request body limit is 20 MiB.
- The total PDF request body limit, including the complete multipart request for Merge, is 12 MiB.
- Files are used only to generate the current response and are not written to persistent storage.
- Binary download responses include
Cache-Control: no-storeandContent-Disposition: attachment.
Endpoint Overview
| Method | Path | Authentication | Output |
|---|---|---|---|
GET | /v1/health | No | JSON |
GET | /v1/openapi.json | No | OpenAPI JSON |
POST | /v1/text/slug | Yes | JSON |
POST | /v1/conversions/unit | Yes | JSON |
GET | /v1/exchange-rates/latest | Yes | JSON |
POST | /v1/images/info | Yes | JSON |
POST | /v1/images/transform | Yes | Image |
POST | /v1/pdfs/info | Yes | JSON |
POST | /v1/pdfs/merge | Yes | |
POST | /v1/pdfs/extract | Yes | |
POST | /v1/pdfs/delete | Yes | |
POST | /v1/pdfs/rotate | Yes | |
POST | /v1/pdfs/split | Yes | ZIP |
POST | /v1/pdfs/compress | Yes |
System Endpoints
Health Check
GET /v1/health
curl 'https://api.heron.tools/v1/health'
{
"data": {
"status": "ok",
"service": "heron-tools-api",
"version": "v1"
},
"meta": {
"requestId": "..."
}
}
OpenAPI Contract
GET /v1/openapi.json
curl 'https://api.heron.tools/v1/openapi.json' --output heron-tools-openapi.json
You can import this file into client generators, API testing tools, or documentation viewers that support OpenAPI 3.1.
Text Endpoints
Generate a Slug
POST /v1/text/slug
Content-Type: application/json
Request fields:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
text | string | Yes | — | Input text, up to 10,000 characters |
delimiter | string | No | - | Delimiter, up to 8 characters; an empty or alphanumeric value falls back to - |
lowercase | boolean | No | true | Whether to convert the output to lowercase |
dedupeDelimiter | boolean | No | true | Whether to merge consecutive delimiters |
curl 'https://api.heron.tools/v1/text/slug' \
-H "Authorization: Bearer $HERON_API_KEY" \
-H 'Content-Type: application/json' \
--data '{
"text": "Heron Tools: Fast & Private",
"delimiter": "_",
"lowercase": true,
"dedupeDelimiter": true
}'
The data object in a successful response contains slug, inputLength, slugLength, and wordCount.
Conversion Endpoints
Unit Conversion
POST /v1/conversions/unit
Content-Type: application/json
Request fields:
| Field | Type | Required | Description |
|---|---|---|---|
value | number | Yes | Finite numeric value |
from | string | Yes | Source unit ID |
to | string | Yes | Target unit ID; must be in the same category as the source unit |
curl 'https://api.heron.tools/v1/conversions/unit' \
-H "Authorization: Bearer $HERON_API_KEY" \
-H 'Content-Type: application/json' \
--data '{"value":1,"from":"mile","to":"kilometer"}'
The data object in a successful response contains input, output, formattedOutput, summary, from, and to.
Supported unit IDs:
| Category | Unit IDs |
|---|---|
| Length | meter, kilometer, centimeter, millimeter, inch, foot, yard, mile |
| Weight | gram, kilogram, milligram, ounce, pound, stone |
| Temperature | celsius, fahrenheit, kelvin |
| Volume | liter, milliliter, cubic-meter, teaspoon, tablespoon, fluid-ounce, cup, pint, quart, gallon |
Combining units from different categories returns 422 and INCOMPATIBLE_UNITS.
Latest Exchange Rate
GET /v1/exchange-rates/latest?base=USD"e=CNY
base and quote are both required three-letter currency codes and are case-insensitive. Data comes from Frankfurter; the upstream request timeout is 5 seconds.
curl 'https://api.heron.tools/v1/exchange-rates/latest?base=USD"e=CNY' \
-H "Authorization: Bearer $HERON_API_KEY"
The data object in a successful response contains date, base, quote, and rate. This is the latest available exchange rate for the currency pair and is not guaranteed to be a real-time trading quote.
Image Endpoints
Supported input Content-Type values:
image/avifimage/gifimage/heicimage/jpegimage/pngimage/svg+xmlimage/webp
Images are processed by Cloudflare Images within the current request. The input limit is 20 MiB.
Read Image Information
POST /v1/images/info
Content-Type: <actual image type>
curl 'https://api.heron.tools/v1/images/info' \
-H "Authorization: Bearer $HERON_API_KEY" \
-H 'Content-Type: image/jpeg' \
-H 'X-Filename: photo.jpg' \
--data-binary @photo.jpg
The data object in a successful response contains format and, when detectable, fileSize, width, and height.
Transform an Image
POST /v1/images/transform?<parameters>
Content-Type: <actual image type>
Query parameters:
| Parameter | Type/Allowed Values | Default | Description |
|---|---|---|---|
width | integer 1..12000 | — | Output width |
height | integer 1..12000 | — | Output height |
fit | scale-down, contain, pad, squeeze, cover, crop | — | Sizing mode; pad, cover, and crop require both width and height |
gravity | auto, center, entropy, face, left, right, top, bottom | — | Crop focal area |
background | hexadecimal CSS color | — | Fill color, such as %23ffffff; # must be encoded in the URL |
rotate | 0, 90, 180, 270 | — | Clockwise rotation angle |
flip | h, v, hv | — | Horizontal, vertical, or both |
format | avif, jpeg, png, webp | webp | Output format |
quality | integer 1..100 | 85 | Output quality |
curl 'https://api.heron.tools/v1/images/transform?width=1200&format=webp&quality=82' \
-H "Authorization: Bearer $HERON_API_KEY" \
-H 'Content-Type: image/jpeg' \
-H 'X-Filename: photo.jpg' \
--data-binary @photo.jpg \
--output photo-transformed.webp
The output type is determined by format. The response filename appends -transformed to the original base name.
PDF Endpoints
Except for Merge, PDF endpoints use a raw request body:
Content-Type: application/pdf
X-Filename: document.pdf
The total limit per request is 12 MiB. Page numbering starts at 1; ranges use the format 1-3,5,8, cannot be in descending order, and cannot exceed the document’s page count.
Binary PDF responses may include these headers:
| Response Header | Meaning |
|---|---|
X-Page-Count | Number of pages in the output PDF; for Split, the total pages across all output files |
X-File-Count | Number of PDFs generated by Split |
X-Metadata-Cleared | Whether Compress cleared the metadata |
X-Original-Size | Compress input size in bytes |
X-Output-Size | Compress output size in bytes |
X-Savings-Bytes | Bytes saved by Compress; may be negative |
Read PDF Information
POST /v1/pdfs/info
curl 'https://api.heron.tools/v1/pdfs/info' \
-H "Authorization: Bearer $HERON_API_KEY" \
-H 'Content-Type: application/pdf' \
-H 'X-Filename: document.pdf' \
--data-binary @document.pdf
The data object in a successful response contains:
- Always present:
fileName,fileSize,pageCount,encrypted - Optional:
title,author,subject,keywords,creator,producer,creationDate,modificationDate
Date fields are ISO 8601 strings.
Merge PDFs
POST /v1/pdfs/merge
Content-Type: multipart/form-data
Use repeated files fields to merge 2 to 10 PDFs in upload order. The complete multipart request cannot exceed 12 MiB.
curl 'https://api.heron.tools/v1/pdfs/merge' \
-H "Authorization: Bearer $HERON_API_KEY" \
-F 'files=@first.pdf;type=application/pdf' \
-F 'files=@second.pdf;type=application/pdf' \
--output merged.pdf
Extract Pages
POST /v1/pdfs/extract?pages=1-3,5
Creates a new PDF in the order specified by pages.
curl 'https://api.heron.tools/v1/pdfs/extract?pages=1-3,5' \
-H "Authorization: Bearer $HERON_API_KEY" \
-H 'Content-Type: application/pdf' \
-H 'X-Filename: document.pdf' \
--data-binary @document.pdf \
--output document-extracted.pdf
Delete Pages
POST /v1/pdfs/delete?pages=2,4-6
pages specifies the pages to delete. At least one page must remain.
curl 'https://api.heron.tools/v1/pdfs/delete?pages=2,4-6' \
-H "Authorization: Bearer $HERON_API_KEY" \
-H 'Content-Type: application/pdf' \
-H 'X-Filename: document.pdf' \
--data-binary @document.pdf \
--output document-pages-removed.pdf
Rotate Pages
POST /v1/pdfs/rotate?angle=90&pages=1-3,5
| Parameter | Required | Description |
|---|---|---|
angle | Yes | 90, 180, or 270; rotates clockwise from each page’s current angle |
pages | No | Pages to rotate; omit to rotate all pages |
curl 'https://api.heron.tools/v1/pdfs/rotate?angle=90&pages=1-3,5' \
-H "Authorization: Bearer $HERON_API_KEY" \
-H 'Content-Type: application/pdf' \
-H 'X-Filename: document.pdf' \
--data-binary @document.pdf \
--output document-rotated.pdf
Split a PDF
POST /v1/pdfs/split?ranges=1-2;3-5;6
Separate output file groups with semicolons. Within each group, commas can combine page numbers and ranges. For example, 1,3-4;5-8 generates two PDFs. The response is a ZIP file containing one PDF for each group.
Semicolons have special meaning in the shell, so place the URL in quotes:
curl 'https://api.heron.tools/v1/pdfs/split?ranges=1-2;3-5;6' \
-H "Authorization: Bearer $HERON_API_KEY" \
-H 'Content-Type: application/pdf' \
-H 'X-Filename: document.pdf' \
--data-binary @document.pdf \
--output document-split.zip
Losslessly Repack a PDF
POST /v1/pdfs/compress?clearMetadata=false
clearMetadata is optional and accepts true or false; the default is false.
curl 'https://api.heron.tools/v1/pdfs/compress?clearMetadata=true' \
-H "Authorization: Bearer $HERON_API_KEY" \
-H 'Content-Type: application/pdf' \
-H 'X-Filename: document.pdf' \
--data-binary @document.pdf \
--output document-compressed.pdf
This endpoint rewrites the PDF using object streams and can clear document metadata. It does not rasterize pages or reduce image quality; output may not be smaller for PDFs that are already optimized. For rendering, lossy compression of scanned documents, or PDF-to-image conversion, use the browser-based Heron Tools instead of this API.
Local Development
The project requires Node.js 22.x. After installing dependencies, create a local secrets file from the example:
npm install
cp .dev.vars.example .dev.vars
Replace HERON_API_KEY in .dev.vars with a random local key. This file is ignored by Git; do not commit it. Start the Worker:
npm run api:dev
The default local URL is http://localhost:8787:
curl 'http://localhost:8787/v1/text/slug' \
-H 'Authorization: Bearer replace-with-your-local-key' \
-H 'Content-Type: application/json' \
--data '{"text":"Hello Heron Tools"}'
Validate the API:
npm run api:test
npm run api:check
api:check generates Worker types, runs TypeScript checks, checks test types, and completes a Wrangler dry-run build.
Deployment Notes
On the first deployment, the Worker does not yet exist, so Wrangler cannot bind the required secret in advance. Validate first, then complete the initial deployment using the Git-ignored .dev.vars file:
npm run api:check
npx wrangler deploy --config wrangler.api.jsonc --secrets-file .dev.vars
After the Worker has been created, use these commands to set or rotate the production key:
npx wrangler secret put HERON_API_KEY --config wrangler.api.jsonc
npm run api:deploy
The production Worker, static site, and deployment workflows are independent. Deploying the website does not automatically deploy the API.