Heron Tools API Documentation
The Heron Tools API provides a bounded, stateless set of math, 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. Where present, meta.requestId in successful JSON responses and requestId in error responses contain the same value. Record this value when troubleshooting. Math evaluation returns a top-level result without a meta wrapper.
Successful JSON Responses
JSON endpoints use this standard response format, except for the legacy-friendly math result described below:
{
"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 |
429 | Calculation rate limit exceeded |
500 | Internal API or deployment configuration error |
502 | Upstream service failed or calculation resource limit exceeded |
504 | Calculation service response deadline exceeded |
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/math/evaluate | Yes | JSON result |
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 |
Mathematical Expression Evaluation
POST /v1/math/evaluate
Evaluate a single mathematical expression with SymPy. This is an API-only service; there is no browser calculator.
This endpoint accepts the standard HERON_API_KEY or a separate math-only MATH_API_KEY, both sent as Bearer tokens. The math-only key cannot call text, conversion, image, or PDF endpoints. The example below uses that scoped key.
curl 'https://api.heron.tools/v1/math/evaluate' \
-H "Authorization: Bearer $MATH_API_KEY" \
-H 'Content-Type: application/json' \
--data '{"formula":"2**10 + sqrt(16)"}'
{"result":"1028"}
| Field | Type | Default | Meaning |
|---|---|---|---|
formula | string | Required | One expression, at most 2048 characters |
mode | string | numeric | numeric evaluates to an approximation; exact preserves rational and symbolic expressions |
precision | integer | 15 | 1–50 significant digits in numeric mode; not a number of decimal places |
Results are strings to preserve precision and support symbolic and matrix results. Exact integer results remain integer strings. A successful response contains only the top-level result; use the X-Request-Id header for troubleshooting. Errors follow the standard problem-details format above. Inputs and results are not stored or logged, and all evaluation responses use Cache-Control: no-store.
The first request to a new engine instance loads SymPy and may take around 10–15 seconds; later requests are usually much faster. Allow at least 30 seconds in your HTTP client. This is a bounded utility API, not a low-latency or large-scale symbolic computing service.
Supported Expressions
| Input | Mode | Result |
|---|---|---|
0.1 + 0.2 | exact | 3/10 |
1/3 with precision 8 | numeric | 0.33333333 |
sin(pi/6) | exact | 1/2 |
diff(x**2, x) | exact | 2*x |
integrate(sin(x), x) | exact | -cos(x) |
integrate(x, (x, 0, 2)) | exact | 2 |
factor(x**2-1) | exact | (x - 1)*(x + 1) |
expand((x+1)**2) | exact | x**2 + 2*x + 1 |
Matrix([[1,2],[3,4]]).det() | exact | -2 |
- Operators:
+,-,*,/,%,**, parentheses, and unary+/-. Multiplication must be explicit.^is not accepted as power. - Numbers: decimal and scientific notation; decimal input is parsed exactly before optional numeric evaluation.
- Annotations: paired
#...#labels are ignored by default, including Chinese text and multiline notes. For example,#梁1# 2*3 + #梁2# 4*5returns26. Delimiters must be paired, cannot nest, and are recognized only outside quoted strings. Annotations count toward the 2048-character limit and act as whitespace:2#梁1#3is invalid, not23. Nosafeoption is needed. - Symbols: ASCII letters followed by letters or digits, up to 32 characters, without underscores. Constants:
pi,E,I;oomay be used for integration bounds, but infinite or undefined final results are rejected. - Functions:
sin,cos,tan,asin,acos,atan,sinh,cosh,tanh,sqrt,exp,log/ln(optional base),abs/Abs,floor,ceiling,factorial,simplify,factor,expand,cancel, andtogether. - Calculus:
diff(expression, symbol[, order]),integrate(expression, symbol), orintegrate(expression, (symbol, lower, upper)). An unevaluated integral may be returned when SymPy cannot find a result. - Matrices:
Matrix([[...], [...]]),det(Matrix(...)), andMatrix(...).det(). Matrix arithmetic and other methods are outside this first version. - Arbitrary Python, imports, assignments, multiple statements, attribute access other than the documented
det()method, and unknown functions are rejected.
Legacy Steel-Bar Function
sbw(dimeter, factor=6.17, length=1, unit='t') preserves the old implementation, including the dimeter spelling and positional argument order. Numeric arguments must be nonnegative. Its exact formula is:
unit='t': dimeter × dimeter × factor × length / 1000
unit='kg': dimeter × dimeter × factor × length
For example, sbw(10) in exact mode returns 617/1000, and sbw(10, length=2, unit='kg') returns 1234. The original input units and coefficient convention were not documented. These are compatibility calculations: do not assume that the default diameter is in millimeters or that the coefficient is a standard kg/m conversion. Check the units used by your existing caller.
Limits and Migration
The endpoint accepts a 16 KiB JSON body, up to 256 AST nodes, nesting depth 32, 16 distinct symbols, 4×4 matrices, and an 8192-character output. Numeric literals have at most 100 mantissa characters and scientific exponents up to ±1000. Powers require a rational exponent between −1000 and 1000 and are also limited by intermediate numeric size. Factorial inputs are integers from 0 to 1000; derivative order is 1–5. Symbolic intermediates are bounded by operation count. Expressions may hit the engine CPU or memory budget even when they fit the syntax limits.
All calculation callers share an approximate limit of 60 calculation requests per minute per Cloudflare location. A 429 response includes Retry-After: 60. The engine has a 15000 ms CPU budget and the gateway has a twenty-second response deadline. 422 with EXPRESSION_LIMIT means a known expression limit was exceeded; 502 with CALCULATION_FAILED may indicate an engine resource limit or service failure; 504 with CALCULATION_TIMEOUT means the response deadline expired. Do not automatically retry expensive formulas in a loop.
To migrate from the former heron.tools/eval/api/, change the URL and add Bearer authentication. The formula input and result output names remain the same. The old URL is not restored or redirected. Numeric formatting can differ, and automatic simplify is no longer applied to every result: request simplify(...) explicitly if needed. Exact mode is new. Paired #...# annotations work without a query option. Query parameters (including safe=true), unpaired line comments, implicit multiplication, and automatic Chinese-text removal outside annotations are unsupported and return an error instead of silently changing the expression.
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.