API v1

Measurement API

Reliable, deterministic unit conversion with decimal-string precision, batch support, and discoverable unit metadata.

154 units · 15 categoriesDecimal-string precisionOpenAPI 3.1 · RESTFree to start

Quick Start

Make your first conversion in under a minute.

  1. Request an API key

    Contact us to get a Bearer API key. Keys use the uu_live_ prefix for production.

    Request API Access →
  2. Store it securely

    Set the key as an environment variable. Never commit it to source control or embed it in client-side code.

    bash
    export UNIFYUNITS_API_KEY="uu_live_your_key_here"
  3. Make your first conversion

    Convert 1 foot to meters using the GET endpoint:

    bash
    curl "https://api.unifyunits.com/v1/convert?value=1&from=ft&to=m" \
      -H "Authorization: Bearer $UNIFYUNITS_API_KEY"
  4. Read the response

    The result is always a decimal string. 1 ft = 0.3048 m exactly.

    json
    {
      "data": {
        "input":  { "value": "1",      "unit": "ft" },
        "result": { "value": "0.3048", "unit": "m"  },
        "category": "length"
      }
    }
  5. Try batch conversion

    Convert multiple measurements in a single request with up to 100 items per call.

    bash
    curl "https://api.unifyunits.com/v1/batch" \
      -X POST \
      -H "Authorization: Bearer $UNIFYUNITS_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"conversions":[
        {"value":"1","from":"ft","to":"m"},
        {"value":"32","from":"F","to":"C"}
      ]}'

Authentication

Conversion endpoints require a Bearer API key. Discovery endpoints (/v1/categories,/v1/units,/v1/health,/openapi.json) are public.

Authorization header

http
Authorization: Bearer uu_live_xxxxxxxxxxxxxxxxxxxxxxxxxx
Key format. Production keys use the uu_live_ prefix and contain 32 cryptographically random bytes. Keys are single-use secrets — treat them like passwords.
Do not embed API keys in public browser or mobile code. Any key distributed in a client bundle can be extracted by users. Use your API key server-side only, or call the public discovery endpoints without authentication.

Endpoint access table

EndpointAuthentication
GET /v1/convert🔒 Bearer
POST /v1/convert🔒 Bearer
POST /v1/batch🔒 Bearer
GET /v1/categories🌐 Public
GET /v1/categories/:category🌐 Public
GET /v1/units🌐 Public
GET /v1/units/:unit🌐 Public
GET /v1/health🌐 Public
GET /openapi.json🌐 Public

Key rotation

Rotate keys without downtime by following these steps:

  1. Request a replacement key
  2. Deploy your service with the new key
  3. Verify traffic is flowing correctly
  4. Revoke the old key

Use separate keys for separate environments. Never reuse a key across production and development.

API Reference

Base URL: https://api.unifyunits.com

All responses use Content-Type: application/json. Values are always decimal strings, not JSON numbers. Every response includes an X-Request-Id header.

GET/v1/convert🔒 Requires API key

Single conversion via query parameters

Converts one measurement value from a source unit to a target unit. All three query parameters are required.

Parameters

NameTypeRequiredDescription
valuestringYesMeasurement value as a decimal string (e.g. "10.5")
fromstringYesSource unit identifier (e.g. "kg")
tostringYesTarget unit identifier (e.g. "lb")

Request

bash
curl "https://api.unifyunits.com/v1/convert?value=10&from=kg&to=lb" \
  -H "Authorization: Bearer $UNIFYUNITS_API_KEY"

Response 200 OK

json
{
  "data": {
    "input": {
      "value": "10",
      "unit": "kg"
    },
    "result": {
      "value": "22.046226218487758",
      "unit": "lb"
    },
    "category": "mass"
  }
}

Possible errors: INVALID_VALUE · UNKNOWN_UNIT · INCOMPATIBLE_UNITS · UNAUTHORIZED · RATE_LIMIT_EXCEEDED

POST/v1/convert🔒 Requires API key

Single conversion via JSON body

Identical conversion behaviour to GET /v1/convert, using a JSON request body instead of query parameters. Both methods share the same conversion engine.

Request

bash
curl "https://api.unifyunits.com/v1/convert" \
  -X POST \
  -H "Authorization: Bearer $UNIFYUNITS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "value": "1",
    "from": "ft",
    "to": "m"
  }'

Response 200 OK

json
{
  "data": {
    "input": {
      "value": "1",
      "unit": "ft"
    },
    "result": {
      "value": "0.3048",
      "unit": "m"
    },
    "category": "length"
  }
}

Possible errors: INVALID_VALUE · UNKNOWN_UNIT · INCOMPATIBLE_UNITS · INVALID_REQUEST · UNAUTHORIZED

POST/v1/batch🔒 Requires API key

Convert up to 100 measurements in one request

Converts multiple independent measurements in a single request. A structurally valid batch returns HTTP 200 even when individual items fail — inspect each item's status field. Each successful item consumes one conversion quota unit.

Request

bash
curl "https://api.unifyunits.com/v1/batch" \
  -X POST \
  -H "Authorization: Bearer $UNIFYUNITS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "conversions": [
      { "value": "1", "from": "ft", "to": "m" },
      { "value": "32", "from": "F", "to": "C" }
    ]
  }'

Response 200 OK

json
{
  "data": [
    {
      "index": 0,
      "status": "success",
      "input": { "value": "1", "unit": "ft" },
      "result": { "value": "0.3048", "unit": "m" },
      "category": "length"
    },
    {
      "index": 1,
      "status": "success",
      "input": { "value": "32", "unit": "F" },
      "result": { "value": "0", "unit": "C" },
      "category": "temperature"
    }
  ],
  "meta": {
    "total": 2,
    "succeeded": 2,
    "failed": 0
  }
}

Possible errors: BATCH_LIMIT_EXCEEDED · INVALID_REQUEST · PAYLOAD_TOO_LARGE · UNAUTHORIZED

GET/v1/categories🌐 Public

List all measurement categories

Returns all supported measurement categories in deterministic order. No authentication required.

Request

bash
curl "https://api.unifyunits.com/v1/categories"

Response 200 OK

json
{
  "data": [
    { "id": "length",      "name": "Length",      "unit_count": 11 },
    { "id": "mass",        "name": "Mass",        "unit_count": 10 },
    { "id": "temperature", "name": "Temperature", "unit_count": 5  },
    { "id": "area",        "name": "Area",        "unit_count": 10 }
  ]
}
GET/v1/categories/:category🌐 Public

Get a single category with its units

Returns a single measurement category and all units it contains. Use the id field from GET /v1/categories as the :category path parameter.

Request

bash
curl "https://api.unifyunits.com/v1/categories/mass"

Response 200 OK

json
{
  "data": {
    "id": "mass",
    "name": "Mass",
    "units": [
      { "id": "kg",  "name": "Kilogram", "symbol": "kg" },
      { "id": "lb",  "name": "Pound",    "symbol": "lb" },
      { "id": "g",   "name": "Gram",     "symbol": "g"  }
    ]
  }
}

Possible errors: UNKNOWN_CATEGORY

GET/v1/units🌐 Public

List all supported units

Returns all supported units across all categories. Filter by category using the optional query parameter. Use canonical IDs returned here in your conversion requests.

Parameters

NameTypeRequiredDescription
categorystringNoFilter by category id (e.g. "mass")

Request

bash
# All units
curl "https://api.unifyunits.com/v1/units"

# Filter by category
curl "https://api.unifyunits.com/v1/units?category=mass"

Response 200 OK

json
{
  "data": [
    { "id": "kg", "name": "Kilogram", "symbol": "kg", "category": "mass" },
    { "id": "lb", "name": "Pound",    "symbol": "lb", "category": "mass" }
  ],
  "meta": { "count": 2 }
}
GET/v1/units/:unit🌐 Public

Get metadata for a single unit

Returns full metadata for one canonical unit identifier or accepted unambiguous alias, including its name, symbol, category, and known aliases.

Request

bash
curl "https://api.unifyunits.com/v1/units/kg"

Response 200 OK

json
{
  "data": {
    "id": "kg",
    "name": "Kilogram",
    "symbol": "kg",
    "category": "mass",
    "aliases": ["kilogram", "kilograms"]
  }
}

Possible errors: UNKNOWN_UNIT

GET/v1/health🌐 Public

Service health check

Lightweight public health endpoint. Does not expose internal state, secrets, or dependency errors. Suitable for basic availability probing.

Request

bash
curl "https://api.unifyunits.com/v1/health"

Response 200 OK

json
{
  "status": "ok",
  "version": "1.0.0",
  "timestamp": "2026-09-24T00:00:00Z"
}
GET/openapi.json🌐 Public

OpenAPI 3.1 specification

Returns the machine-readable OpenAPI 3.1 contract for the API. Use this for client generation, schema validation, and tool integration. The specification is validated against the implementation on every release.

Request

bash
curl "https://api.unifyunits.com/openapi.json"

Response 200 OK

json
{
  "openapi": "3.1.0",
  "info": {
    "title": "UnifyUnits Measurement API",
    "version": "0.1.0"
  },
  "paths": { ... }
}

Code Examples

Complete integration examples for the most common languages. All examples assume you have set UNIFYUNITS_API_KEY in your environment.

Single Conversion

bash
# GET conversion
curl "https://api.unifyunits.com/v1/convert?value=10&from=kg&to=lb" \
  -H "Authorization: Bearer $UNIFYUNITS_API_KEY"

# POST conversion
curl "https://api.unifyunits.com/v1/convert" \
  -X POST \
  -H "Authorization: Bearer $UNIFYUNITS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"value":"10","from":"kg","to":"lb"}'

Batch Conversion

bash
curl "https://api.unifyunits.com/v1/batch" \
  -X POST \
  -H "Authorization: Bearer $UNIFYUNITS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "conversions": [
      {"value":"10","from":"kg","to":"lb"},
      {"value":"100","from":"km","to":"mi"},
      {"value":"32","from":"F","to":"C"}
    ]
  }'

Discovery (no auth needed)

bash
# List all categories
curl "https://api.unifyunits.com/v1/categories"

# Units in a category
curl "https://api.unifyunits.com/v1/units?category=mass"

# Look up a unit
curl "https://api.unifyunits.com/v1/units/kg"

Precision & Numeric Values

Values are always decimal strings, not JSON numbers. Measurement values are represented as strings to preserve decimal intent and avoid binary floating-point loss before the API receives them.

✓ Correct

json
{"value": "0.1", "from": "m", "to": "cm"}

✗ Not valid in v1

json
{"value": 0.1, "from": "m", "to": "cm"}

Accepted numeric formats

FormatExampleAccepted
Integer"10"✓ Yes
Negative"-10"✓ Yes
Decimal"10.5"✓ Yes
Small decimal"0.001"✓ Yes
Scientific"1.25e6"✓ Yes
Negative scientific"1.25e-6"✓ Yes
NaN / Infinity"NaN"✗ No
Comma-separated"1,000"✗ No
Fraction"1/2"✗ No
With unit suffix"10 kg"✗ No

Temperature conversions

Temperature uses affine conversion (offset + scale), not simple multiplication. Results are deterministic for the same input under the same API version.

InputFromToResult
"32"FC"0"
"0"CK"273.15"
"-40"CF"-40"
Unit identifiers are case-sensitive. C (Celsius) and c are distinct. Data units like KB and KiB also differ. Always use the canonical IDs returned by GET /v1/units.

Error Reference

All errors follow the same envelope. Use error.code for programmatic handling — never match on error.message which may change.

json
{
  "error": {
    "code":       "ERROR_CODE",
    "message":    "Human-readable developer message.",
    "details":    {},           // optional, endpoint-specific
    "request_id": "01K..."     // present for server errors
  }
}
json — INCOMPATIBLE_UNITS example
{
  "error": {
    "code":    "INCOMPATIBLE_UNITS",
    "message": "Units 'kg' and 'km' are not compatible.",
    "details": { "from": "kg", "to": "km" }
  }
}
CodeHTTPMeaning
INVALID_REQUEST400Request schema is invalid (missing fields, wrong types, unknown properties)
INVALID_VALUE400Measurement value is not a valid decimal string
UNKNOWN_UNIT400 / 404Unit identifier cannot be resolved (400 for conversions, 404 for resource lookup)
AMBIGUOUS_UNIT400Unit alias matches multiple canonical units — use a specific canonical ID
INCOMPATIBLE_UNITS422Source and target units belong to different categories (e.g. mass → length)
UNKNOWN_CATEGORY404Category identifier does not exist
BATCH_LIMIT_EXCEEDED400Batch contains more than the configured maximum (100) conversions
PAYLOAD_TOO_LARGE413Request body exceeds the maximum allowed size
UNSUPPORTED_MEDIA_TYPE415POST request has an unsupported Content-Type (must be application/json)
UNAUTHORIZED401Authorization header is missing
INVALID_API_KEY401API key is invalid, inactive, or revoked
RATE_LIMIT_EXCEEDED429Burst rate limit exceeded — back off and retry
INTERNAL_ERROR500Unexpected server error — provide the X-Request-Id to support
SERVICE_UNAVAILABLE503A required service dependency is unavailable — retry with exponential backoff
Request ID. Every response includes an X-Request-Id header. Include this ID when contacting support — never send your API key itself.

Rate Limits & Pricing

Rate limits ≠ monthly quotas. Rate limits protect the service from burst traffic and apply per 60-second window. Quotas define your monthly conversion allowance. They are separate controls.
Free
$0
  • 10,000 conversions/month
  • 100 req / 60 s
  • Batch max 100 items
Get started
Most Popular
Developer
$5/mo
  • 250,000 conversions/month
  • 500 req / 60 s
  • Batch max 100 items
Get started
Pro
$19/mo
  • 2,000,000 conversions/month
  • 2,000 req / 60 s
  • Batch max 100 items
Get started
Business
$49/mo
  • 10,000,000 conversions/month
  • 3,000 req / 60 s
  • Batch max 100 items
Get started

Quota accounting

OperationQuota cost
Successful single conversion1 operation
Successful batch item1 operation
Failed conversion (invalid value, unknown unit)0
Server error0
Discovery endpoints (categories, units, health)0
Batch quota. A batch of 50 successful conversions consumes 50 quota operations even though it is 1 HTTP request. Failed items within a valid batch do not consume quota.

Versioning

The API uses three independent version dimensions. These should not be confused with each other.

API Version
/v1

Encoded in the URL path. Breaking contract changes require a new major version.

Software Version
0.1.0

Internal implementation version. Not a compatibility boundary for API consumers.

Dataset Version
X-UnifyUnits-Dataset-Version

Tracks measurement factor updates. A dataset update can occur within v1 when contract-compatible.

Breaking vs non-breaking changes

Breaking (requires new version)

  • Removing or renaming a response field
  • Changing a field's type
  • Changing canonical unit identifiers
  • Changing error code semantics
  • Changing endpoint method or path
  • Precision changes that alter established results

Non-breaking (stays in v1)

  • Adding optional response fields
  • Adding new endpoints
  • Adding new units or categories
  • New optional request parameters with safe defaults
  • Dataset corrections for incorrect factors
Deprecation policy. Endpoints will not be silently removed. Breaking retirements include migration documentation, a communication period, and appropriate sunset headers before removal.

Security Best Practices

Never embed a production API key in public browser or mobile application code. Any secret distributed in a client bundle can be extracted by users and abused at your expense.

Store in environment variables

Use UNIFYUNITS_API_KEY as an environment variable. Never hardcode it in source files or configuration committed to version control.

Never in URLs or query strings

API keys passed in URLs are logged by servers, proxies, and browsers. Always send keys in the Authorization header only.

Separate keys per environment

Use different API keys for development, staging, and production. A compromised development key cannot affect production.

Rotate keys without downtime

Create a new key, deploy it, verify traffic, then revoke the old key. Do not modify a key in place.

CORS & Browser Clients

The API supports GET and POST with the Authorization header from browser origins. However, CORS is not a security boundary — any key visible to a browser can be extracted. Use browser-side API access only for scenarios where you intentionally accept the exposure risk (e.g., a developer playground where the key can be rotated freely).

For unifyunits.com itself, the website uses the shared core library locally and does not call the authenticated API from the browser.

Troubleshooting

Getting 401 Unauthorized

Check each of these before contacting support:

  • Is the Authorization header present on the request?
  • Does the value start with exactly Bearer (with a trailing space)?
  • Was the full key copied — no truncation, no trailing newline?
  • Is the key active and not revoked?
  • Are you pointing at the correct environment?
  • Does the key contain accidental leading/trailing whitespace?
Getting UNKNOWN_UNIT

Discover valid canonical unit IDs before constructing requests:

bash
# Browse all units
curl "https://api.unifyunits.com/v1/units"

# Check if a specific ID exists
curl "https://api.unifyunits.com/v1/units/kg"

Unit identifiers are case-sensitive. C (Celsius) and c are different. Generic ambiguous aliases like gallon are rejected — use gal (US) or imp-gal (Imperial) explicitly.

Getting INCOMPATIBLE_UNITS (422)

You are trying to convert between units from different measurement categories. This is not supported — mass and length cannot be compared.

FromToValid?
kg (mass)lb (mass)✓ Valid
m (length)ft (length)✓ Valid
kg (mass)km (length)✗ Invalid
F (temperature)lb (mass)✗ Invalid
Getting INVALID_VALUE

The most common causes:

  • Sending a JSON number instead of a string: 10 → should be "10"
  • Using a localized decimal comma: "1,5" → should be "1.5"
  • Including the unit in the value: "10kg" → should be "10"
  • Using fraction notation: "1/2" → should be "0.5"
  • Passing "NaN" or "Infinity"
Getting 429 Rate Limited

Your plan's burst rate limit has been exceeded. Use exponential backoff with jitter — do not retry immediately in a tight loop.

typescript
// Check for Retry-After header
const retryAfter = response.headers.get("Retry-After");
const delay = retryAfter ? parseInt(retryAfter, 10) * 1000 : 2000;
await new Promise((r) => setTimeout(r, delay));

Consider using batch requests to reduce total HTTP request count. Do not retry INVALID_VALUE, UNKNOWN_UNIT, or INCOMPATIBLE_UNITS — these are client errors that will not resolve on retry.

Batch returns 200 but some items failed

This is correct behaviour. A structurally valid batch always returns HTTP 200, even when individual items contain conversion errors. Inspect each item's status field:

typescript
for (const item of body.data) {
  if (item.status === "error") {
    console.error(`Item ${item.index}: ${item.error.code} - ${item.error.message}`);
  }
}

Item order in the response exactly matches the request order. Failed items do not consume quota.

Support & Resources

Contacting support

When reporting an issue, please include:

  • The X-Request-Id header value from the failing response
  • The endpoint and HTTP method you called
  • Approximate timestamp (UTC)
  • The error code from error.code
  • Expected vs actual behaviour
Never send your API key to support. We will never ask for your secret key. Provide the X-Request-Id instead — it is all we need to locate the request in our logs.

Quick reference

Base URL

https://api.unifyunits.com

API Version

/v1/...

Content-Type

application/json

Authentication

Bearer uu_live_...

Values

Decimal strings, not numbers

Batch max

100 items/request