Developer documentation
Steady North Decision API
Ask before you act. Send a subject and what you know about it; get back a decision, the exact rules and versions that produced it, what evidence was missing, and a receipt you can verify later. The engine never answers “compliant” — it answers with requirements you can act on.
Base URL
https://www.steadynorthapp.com/api/v1
One origin for both environments. A key issued as sn_sbx_ evaluates against the same engine and rule library but is marked sandbox on every decision and receipt it produces; sn_live_ keys are production. Nothing about the request changes — the key decides.
Quickstart
Four steps, a few minutes, no SDK.
- 1. Get a key. Dashboard → Settings → API keys. Scope it to what you need; it is shown once and stored as a hash.
- 2. Make a request. Either /evaluate (products) or /age/evaluate (app features).
- 3. Read the decision. Act on requirements and missingEvidence, not on a score.
- 4. Keep the receipt id. It proves later what rules and evidence produced that answer.
Authentication
Authorization: Bearer sn_sbx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
That value is a shape, not a working key — we do not publish an executable one. Keys are organization-scoped: a key can only ever see its own organization’s products, apps and receipts.
CertBridge — product readiness
scope: certbridge:write
Send what you know about a product. Get every applicable requirement, which ones are unsatisfied, and the specific evidence that would satisfy them. Requirements carry the rule id, the exact version, and a link to the authority.
curl -s https://www.steadynorthapp.com/api/v1/evaluate \
-H "Authorization: Bearer sn_sbx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "content-type: application/json" \
-d '{
"module": "CERTBRIDGE",
"subjectLabel": "Wooden Rattle",
"jurisdictions": ["US"],
"context": {
"isChildrensProduct": true,
"category": "TOYS",
"manufactureLocation": "CN",
"importedIntoUS": true,
"subjectToCpscRule": "YES"
},
"evidence": [
{ "key": "field:manufacturer_name", "value": "Acme Toys" }
]
}'Response
{
"decision": "ACTION_REQUIRED",
"reason": "8 of 8 applicable requirements unsatisfied. Driving rule:
SN-CPSC-005 v3 — Determine the applicable rules, then list
every one of them separately on the certificate.",
"requirements": [
{
"ruleId": "SN-CPSC-006",
"version": 2,
"title": "Manufacturer identification at place of manufacture",
"outcome": "UNSATISFIED",
"requiredAction": "Record the manufacturer's name plus full address,
email, and phone (§1110.11(a)(5) detail).",
"severity": "HIGH",
"sourceUrl": "https://www.federalregister.gov/documents/2025/01/08/..."
}
],
"missingEvidence": [
{ "key": "field:manufacturer_address",
"label": "Manufacturer full address and contact",
"ruleId": "SN-CPSC-006" }
],
"coverageGaps": [],
"knowledge": {
"trustedCount": 7,
"withheldCount": 1,
"withheld": [
{ "ruleId": "SN-CPSC-030", "version": 1, "code": "QUARANTINED",
"reason": "Rule SN-CPSC-030 is held at EMERGENCY_REVIEW pending
human review, so it cannot support a clean decision." }
],
"degraded": false
},
"engineVersion": "1.0.0",
"ruleSetVersion": "...",
"evaluatedAt": "2026-08-27T02:47:48.352Z",
"decisionId": "dec_...",
"receiptId": "SN-RCP-3276AD4333F4"
}Any missing legal coverage returns UNKNOWN with each gap listed in coverageGaps. A PLATFORM:* or GLOBAL selector can choose rules, but it cannot substitute for a legal jurisdiction.
A rule with outcome WITHHELD applied to your subject, but we do not yet stand behind it enough to let it clear you — it is under human review, or its source is stale, expired, legally inactive, or its fidelity has not been assessed. Withholding is one-directional: it can never produce a clean answer, and anything adverse the rule found still counts. Do not surface a withheld rule's requiredAction to your users as a task; it is a statement about our confidence, not about their record.
AgeBridge — feature decisions
scope: agebridge:evaluate
Speak in app terms: platform, feature, age signal, jurisdiction. Age signals are ranges, never birth dates — do not send one. The example registers the app inline; apps saved in the dashboard pass appId instead.
curl -s https://www.steadynorthapp.com/api/v1/age/evaluate \
-H "Authorization: Bearer sn_sbx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "content-type: application/json" \
-d '{
"app": { "name": "MyApp", "category": "SOCIAL", "minorsAllowed": true },
"platform": "ANDROID",
"jurisdictions": ["US", "US-TX"],
"feature": {
"key": "direct_messaging",
"capabilities": ["MESSAGING", "SOCIAL"]
},
"ageSignal": "13_15",
"parentApprovalState": "NOT_REQUESTED"
}'Decisions are the enum, not booleans: ALLOW, REQUIRE_PARENT_APPROVAL, BLOCK, UNKNOWN. Treat anything you do not recognise as a block.
Receipts
Answers one question: has this record changed since it was issued? Deliberately keyless — the receipt id is itself the high-entropy reference — and it returns only validity, never the underlying evidence.
curl -s https://www.steadynorthapp.com/api/v1/receipts/verify \
-H "content-type: application/json" \
-d '{ "receiptId": "SN-RCP-3276AD4333F4" }'
# → {
# "receiptId": "SN-RCP-3276AD4333F4",
# "valid": true,
# "checks": [
# { "name": "Payload hash", "ok": true },
# { "name": "Signature", "ok": true },
# { "name": "Receipt id embedded in payload", "ok": true }
# ],
# "issuedAt": "2026-08-27T02:47:48.352Z"
# }scope: receipts:read
The full canonical payload — decision, subject, jurisdictions, every rule and exact version applied, evidence considered, evidence missing, engine version, chain index — plus a fresh verification. Organization-scoped: your key sees only your receipts. Receipts issued by the free public checkers carry no organization and no personal data, and are readable without a key by design.
Webhooks
Register endpoints in Settings → Webhooks. Every delivery carries x-sn-event, x-sn-delivery and x-sn-signature. Failed deliveries retry with exponential backoff (1m → 64m, five attempts).
Verify the signature before trusting a delivery. The header is t=<unix seconds>,v1=<hex>, where the hex is HMAC-SHA256(secret, "<t>.<raw body>"). Sign the raw body — re-serializing the parsed JSON changes the bytes and the signature will not match.
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyWebhook(rawBody: string, header: string, secret: string) {
const parts = Object.fromEntries(
header.split(",").map((kv) => kv.split("=") as [string, string]),
);
const t = parts.t;
const given = parts.v1;
if (!t || !given) return false;
// Reject stale deliveries so a captured request cannot be replayed later.
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const expected = createHmac("sha256", secret)
.update(`${t}.${rawBody}`)
.digest("hex");
const a = Buffer.from(expected, "hex");
const b = Buffer.from(given, "hex");
return a.length === b.length && timingSafeEqual(a, b);
}Events
decision.created · decision.changed · filing.status_changed · recall.detected · parent_approval_required · product.action_required · rule.changed
Errors
| Status | Means | Do |
|---|---|---|
| 400 | Input failed validation | Read detail.fieldErrors — it names each field |
| 401 | Missing or unrecognised key | Check the Authorization header |
| 403 | Key lacks the required scope | The message names the scope; re-issue the key with it |
| 402 | Plan limit reached | Body carries used, limit and upgradeTo |
| 500 | Our fault | Quote x-request-id from the response headers |
Filing SANDBOX
Filing endpoints build and validate a normalized certificate filing against CPSC’s required fields, then submit it to CPSC’s sandbox. Every filing record and response is labeled SANDBOX. Production Product Registry access is user-delegated — it uses your own Registry credentials, not ours — and is not connected yet. Validation results are real regardless of environment.
SDKs PLANNED
There are no downloadable SDKs today. The planned AgeBridge Swift and Kotlin adapters would normalize native platform signals (Apple’s declared age range, Google Play age signals) into the ageSignal vocabulary on-device, then call /age/evaluate. Native signals are never fabricated server-side. Until those exist, call the HTTP API directly — it is the same surface the SDKs would wrap.
Steady North provides compliance workflow software and decision support. It is not a law firm and does not provide legal advice. Every rule links to the authority it was drawn from — read the source.