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
Endpoint
Authentication
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:
Request a replacement key
Deploy your service with the new key
Verify traffic is flowing correctly
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
Name
Type
Required
Description
value
string
Yes
Measurement value as a decimal string (e.g. "10.5")
Identical conversion behaviour to GET /v1/convert, using a JSON request body instead of query parameters. Both methods share the same conversion engine.
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.
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
Name
Type
Required
Description
category
string
No
Filter 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"
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.
# 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"
async function convert(value: string, from: string, to: string) {
const url = new URL("https://api.unifyunits.com/v1/convert");
url.searchParams.set("value", value);
url.searchParams.set("from", from);
url.searchParams.set("to", to);
const res = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.UNIFYUNITS_API_KEY}` },
});
const body = await res.json();
if (!res.ok) {
// Use error.code for programmatic handling, not error.message
switch (body.error.code) {
case "UNKNOWN_UNIT":
throw new Error(`Unknown unit. Check GET /v1/units for valid IDs.`);
case "INCOMPATIBLE_UNITS":
throw new Error(`Cannot convert between ${from} and ${to}. Different categories.`);
case "RATE_LIMIT_EXCEEDED":
// Implement exponential backoff here
throw new Error("Rate limited. Retry after backoff.");
default:
throw new Error(`API error: ${body.error.code}`);
}
}
return body.data.result.value;
}
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
Format
Example
Accepted
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.
Input
From
To
Result
"32"
F
C
"0"
"0"
C
K
"273.15"
"-40"
C
F
"-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.
{
"error": {
"code": "INCOMPATIBLE_UNITS",
"message": "Units 'kg' and 'km' are not compatible.",
"details": { "from": "kg", "to": "km" }
}
}
Code
HTTP
Meaning
INVALID_REQUEST
400
Request schema is invalid (missing fields, wrong types, unknown properties)
INVALID_VALUE
400
Measurement value is not a valid decimal string
UNKNOWN_UNIT
400 / 404
Unit identifier cannot be resolved (400 for conversions, 404 for resource lookup)
AMBIGUOUS_UNIT
400
Unit alias matches multiple canonical units — use a specific canonical ID
INCOMPATIBLE_UNITS
422
Source and target units belong to different categories (e.g. mass → length)
UNKNOWN_CATEGORY
404
Category identifier does not exist
BATCH_LIMIT_EXCEEDED
400
Batch contains more than the configured maximum (100) conversions
PAYLOAD_TOO_LARGE
413
Request body exceeds the maximum allowed size
UNSUPPORTED_MEDIA_TYPE
415
POST request has an unsupported Content-Type (must be application/json)
UNAUTHORIZED
401
Authorization header is missing
INVALID_API_KEY
401
API key is invalid, inactive, or revoked
RATE_LIMIT_EXCEEDED
429
Burst rate limit exceeded — back off and retry
INTERNAL_ERROR
500
Unexpected server error — provide the X-Request-Id to support
SERVICE_UNAVAILABLE
503
A 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.
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.
From
To
Valid?
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.
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.
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.