Output contract · enforced by a build gate
We will not change these fields.
And that sentence has a lock on it.
What breaks an integration is not a missing feature — it is a field that quietly changed shape and woke someone up at 3am. Everybody says they will not do that, so we made it a build gate instead: if any published identifier disappears or is renamed, our own release fails.
31
22
90d
Ask twice, get the same answer
At the same moment and against the same lists, the same address returns the same decision, reason_code and evidence links. Decisions come from lists and deterministic rules, not model scoring, so asking again does not change them.
An answer changes only when the lists update, the on-chain picture changes (transaction count, address age, links to risky addresses), or the rules are revised. Every response carries evaluated_at (when this decision was computed) and evidence_as_of (how current each list was) — in a post-incident review, those two fields explain why a payment was decided the way it was.
Safe to branch on
These four are language-independent and structurally stable. New values may appear at any time — treat an unrecognised value as unhandled, never as an error.
signal_keysverdictreason_coderetryNever branch on
signals · note · advice
Human prose. It gets translated, reworded, and rewritten in copy passes. Branch on it and the next copy edit breaks you.
the exact risk number
The 0–100 scale stays, but what a given address scores moves as the data improves. Rank with it; do not hard-code thresholds without expecting drift.
array ordering
The order of signals and evidence is not guaranteed.
What we must do to change one
- The replacement ships alongside the old one; both are returned.
- The old one is marked deprecated in /api/contract with a removal date at least 90 days out.
- Only then is it removed.
Until step 2 has run its course the gate refuses the deletion — not as a policy, but as a line in the build script.
Registered signal_keys
Grouped by prefix, and the prefixes are stable too: list.* list hits, taint.* fund taint, age.* address age and activity, src.* data-source degradation, scope.* coverage boundaries, endpoint.* the endpoint layer.
age.activeage.active.tronage.brandnewage.brandnew.tronage.new7dage.new7d.tronage.receiveonlyendpoint.deep_subdomainendpoint.delistedendpoint.free_hostingendpoint.ip_literalendpoint.non_publicendpoint.parent_listedendpoint.phishing_listedendpoint.punycodefreeze.unknownfrozen.issuerfrozen.usdtintel.profilelabel.tronscanlist.delistedlist.oceanaltrelated.onehop.outscope.btcscope.solsrc.degradedsrc.onchainsrc.tron.degradedsrc.tronscantaint.onehoptaint.usdtRegistered reason_codes
behavioral_anomalyclearelevated_risk_payeeidempotency_key_invalididempotency_key_reusedidempotency_request_in_progressintent_information_requiredintent_injection_markerinvalid_inputkey_ip_deniedkey_scope_deniedkya_proof_missingkya_unattributedkyc_requiredmandate_intent_mismatchmandate_revokedover_daily_limitover_per_tx_limitpayee_not_allowlistedreplay_detectedsanctioned_or_high_risk_payeescreening_unavailableMachine-readable
GET https://oceanalt.com/api/contract
The same content, with semantics and the deprecation list. Your CI can diff against it directly.

