Core concepts
The handful of ideas that make Numeratica different — read these once and the rest of the API is obvious.
result_id ·
Seeds ·
Error model ·
Versioning ·
Idempotency ·
Not advice ·
Rate limits
Authentication & API keys
Every /v1 endpoint requires an API key; health probes
(/readyz) and self-serve signup (/v1/signup)
are public. Get a free key in seconds — no card. Send the key as a
bearer token, or in the X-API-Key header — both are accepted:
# either of these:
-H "Authorization: Bearer nmr_sk_test_xxx"
-H "X-API-Key: nmr_sk_test_xxx"- Only the SHA-256 hash of a key is stored — the plaintext is shown once at creation and can never be recovered, only rotated.
- A new key works on its first request. Revoking a key (deactivating it) takes effect across all servers within about a minute or two (the auth cache lifetime).
- A missing or invalid key returns
401 unauthorized.
Determinism & result_id
Numeratica calculations are pure functions. Every response carries an
engine_version and a result_id, and
the same inputs always produce the same answer and the same id — byte for byte.
The result_id is a content hash:
result_id = "<prefix>_" + sha256( canonical_json({ params, engine }) )[:16 hex]
# e.g. mc_5445a80e48d88d60 (prefix "mc" = Monte Carlo)- It changes only when an input changes or the
engine_versionbumps — nothing else. - Use it to cache responses, dedupe identical requests,
diff two runs, and build an audit trail ("this number
came from
mc_5445a80e48d88d60on enginemontecarlo-1.0.0").
Seeds
Stochastic engines (such as the retirement Monte Carlo) draw random paths. To make a run
exactly reproducible, pass a seed:
- Pass a seed → the same paths every time; the seed is echoed back in the response.
- Omit it → the API generates one and returns it, so the run is still reproducible — capture the seed from the response to replay later.
- The generator is a self-contained
splitmix64, independent of the language runtime's PRNG, so output is fixed across versions and platforms.
Error model
Errors share one envelope — a stable machine-readable code plus a
human message:
{
"error": {
"code": "missing_field",
"message": "field \"current_age\" is required"
}
}| Code | HTTP | When |
|---|---|---|
unauthorized | 401 | Missing or invalid API key. |
invalid_json | 400 | Body isn't valid JSON, or contains an unknown field. |
missing_field | 400 | A required field is absent (the message names it). |
invalid_params | 400 | A value is out of its allowed range or internally inconsistent. |
too_many_simulations | 400 | A simulation count exceeds the engine's cap. |
no_solution | 400 | A root-finder couldn't converge for the given inputs. |
method_not_allowed | 405 | Wrong HTTP method (calc endpoints are POST). |
rate_limited | 429 | Over your per-minute budget — see Rate limits. |
deadline_exceeded | 503 | The calculation hit its 25-second compute deadline. |
internal_error | 500 | An unexpected server error (should be rare; please report). |
Validation is strict and specific: unknown JSON fields are rejected (so typos surface
immediately), and missing_field /
invalid_params name the exact problem. Input caps keep every call
bounded, so deadline_exceeded should never appear in normal use.
Versioning & deprecation
There are two version surfaces:
- API version — the
/v1path prefix. Additive changes (new endpoints, new response fields) ship within/v1and won't break you. Parse JSON tolerantly (ignore unknown response fields). - Engine version — each calculation pins its own
engine_version(e.g.montecarlo-1.0.0,tax-1.0.0). An engine's numeric output is frozen at a given version; if the math changes, the version bumps — and theresult_idfor the same inputs changes with it.
That makes engine changes detectable, not silent: watch
engine_version (or a changed result_id
for known inputs) to know exactly when an engine moved. A formal deprecation-notice policy
lands with the changelog.
Idempotency
Because calculation POSTs are pure and side-effect-free, they are
inherently idempotent: sending the same body twice returns the same result
and the same result_id, and changes nothing on our side. Retry
freely after a network error — no idempotency key required. (Stateful operations like billing,
when they arrive, will use explicit idempotency keys.)
Informational, not financial advice
Every response includes a disclaimer. Numeratica returns
calculation-engine output — it is not financial, tax, or investment advice,
and it does not account for an individual's full circumstances. You are responsible for how
results are presented to end users and for any advice layered on top.
"disclaimer": "Calculation engine output for informational purposes only; not financial advice."Rate limits & quotas
Each API key has a per-minute request budget (a token bucket). Full keys get
200 requests/minute; free keys get
15/minute. Exceed it and the API returns
429 rate_limited with a Retry-After
header (in seconds):
HTTP/1.1 429 Too Many Requests
Retry-After: 1
{ "error": { "code": "rate_limited", "message": "rate limit exceeded; retry after 1s" } }- Handle it by backing off for
Retry-Afterseconds, then retrying. - Free tier — a free key adds a daily quota (250 requests/day,
429 quota_exceeded), caps Monte Carlo at 2,000 simulations per call, and expires 30 days after issue. Need more? Email support@numeratica.com. - Limits apply per key, per server instance.
Next
- API Reference — every endpoint and schema, from the spec.
- Quickstart — your first call in under five minutes.
- Guides — task-oriented, end-to-end recipes with copy-the-source rendered outputs.