heron.tools
OpenAPI 3.1

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/health
  • GET /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 CodeMeaning
400Request body is missing or cannot be read
401API Key is missing or invalid
404Route does not exist
413Request body exceeds the endpoint limit
415Unsupported Content-Type
422Invalid parameter, file, or operation
429Calculation rate limit exceeded
500Internal API or deployment configuration error
502Upstream service failed or calculation resource limit exceeded
504Calculation 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-Filename for 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-store and Content-Disposition: attachment.

Endpoint Overview

MethodPathAuthenticationOutput
GET/v1/healthNoJSON
GET/v1/openapi.jsonNoOpenAPI JSON
POST/v1/math/evaluateYesJSON result
POST/v1/text/slugYesJSON
POST/v1/conversions/unitYesJSON
GET/v1/exchange-rates/latestYesJSON
POST/v1/images/infoYesJSON
POST/v1/images/transformYesImage
POST/v1/pdfs/infoYesJSON
POST/v1/pdfs/mergeYesPDF
POST/v1/pdfs/extractYesPDF
POST/v1/pdfs/deleteYesPDF
POST/v1/pdfs/rotateYesPDF
POST/v1/pdfs/splitYesZIP
POST/v1/pdfs/compressYesPDF

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"}
FieldTypeDefaultMeaning
formulastringRequiredOne expression, at most 2048 characters
modestringnumericnumeric evaluates to an approximation; exact preserves rational and symbolic expressions
precisioninteger151–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

InputModeResult
0.1 + 0.2exact3/10
1/3 with precision 8numeric0.33333333
sin(pi/6)exact1/2
diff(x**2, x)exact2*x
integrate(sin(x), x)exact-cos(x)
integrate(x, (x, 0, 2))exact2
factor(x**2-1)exact(x - 1)*(x + 1)
expand((x+1)**2)exactx**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*5 returns 26. 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#3 is invalid, not 23. No safe option is needed.
  • Symbols: ASCII letters followed by letters or digits, up to 32 characters, without underscores. Constants: pi, E, I; oo may 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, and together.
  • Calculus: diff(expression, symbol[, order]), integrate(expression, symbol), or integrate(expression, (symbol, lower, upper)). An unevaluated integral may be returned when SymPy cannot find a result.
  • Matrices: Matrix([[...], [...]]), det(Matrix(...)), and Matrix(...).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:

FieldTypeRequiredDefaultDescription
textstringYes—Input text, up to 10,000 characters
delimiterstringNo-Delimiter, up to 8 characters; an empty or alphanumeric value falls back to -
lowercasebooleanNotrueWhether to convert the output to lowercase
dedupeDelimiterbooleanNotrueWhether 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:

FieldTypeRequiredDescription
valuenumberYesFinite numeric value
fromstringYesSource unit ID
tostringYesTarget 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:

CategoryUnit IDs
Lengthmeter, kilometer, centimeter, millimeter, inch, foot, yard, mile
Weightgram, kilogram, milligram, ounce, pound, stone
Temperaturecelsius, fahrenheit, kelvin
Volumeliter, 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&quote=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&quote=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/avif
  • image/gif
  • image/heic
  • image/jpeg
  • image/png
  • image/svg+xml
  • image/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:

ParameterType/Allowed ValuesDefaultDescription
widthinteger 1..12000—Output width
heightinteger 1..12000—Output height
fitscale-down, contain, pad, squeeze, cover, crop—Sizing mode; pad, cover, and crop require both width and height
gravityauto, center, entropy, face, left, right, top, bottom—Crop focal area
backgroundhexadecimal CSS color—Fill color, such as %23ffffff; # must be encoded in the URL
rotate0, 90, 180, 270—Clockwise rotation angle
fliph, v, hv—Horizontal, vertical, or both
formatavif, jpeg, png, webpwebpOutput format
qualityinteger 1..10085Output 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 HeaderMeaning
X-Page-CountNumber of pages in the output PDF; for Split, the total pages across all output files
X-File-CountNumber of PDFs generated by Split
X-Metadata-ClearedWhether Compress cleared the metadata
X-Original-SizeCompress input size in bytes
X-Output-SizeCompress output size in bytes
X-Savings-BytesBytes 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
ParameterRequiredDescription
angleYes90, 180, or 270; rotates clockwise from each page’s current angle
pagesNoPages 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.