ANAX STANDARD X-Layer Canal Support

API

This page documents the endpoints an ANAX Standard subscriber calls directly. It describes the API as deployed. Where a capability is not implemented, this page says so rather than omitting it.

Base URLs

ServiceBase URLAuthentication
X-Layerprovided at on-boardingAPI key
Canalprovided at on-boardingAPI key; two endpoints are public

Authentication

Every X-Layer endpoint except GET /health and GET / requires an API key. Send it in either header:

X-API-Key: sk-canal-...
Authorization: Bearer sk-canal-...

Keys are issued against a Canal subscription. X-Layer is an add-on: a key must already be an active Canal key before X-Layer can be activated on it.

Authentication and entitlement responses

The responses below are X-Layer's ({"status": "<word>"}). Canal returns {"ok": false, "reason": "…"}; a named Canal fault also carries an error word beside reason — see the Canal section.

StatusstatusMeaning
401UNAUTHORIZEDNo key supplied, or the key is unknown or inactive. These are deliberately indistinguishable.
403SUSPENDEDThe key exists but is suspended.
403SUBSCRIPTION_EXPIREDThe licence period has ended. The response carries expired_at.
403UPGRADE_REQUIREDThe key is valid but lacks the entitlement for this endpoint. The response carries current_tier and upgrade_url.
403FORBIDDENOwner scope: the caller may only read its own records.

Key lookups are cached for five minutes. A newly-activated entitlement can therefore take up to five minutes to take effect on a key that was used immediately before activation.

Health

EndpointAuthReturns
GET /healthnone{"status":"OK"} — liveness only. The engine roster is deliberately not exposed here.
GET /noneService name, version and endpoint index.

Sentinel-X — behavioural drift

EndpointEntitlementNotes
POST /sentinel/baselineX-LayerCreates a behavioural baseline for a client_id / system_id / model_id triple.
POST /sentinel/evaluateX-LayerEvaluates one decision against the baseline. Returns 403 when the result is BLOCKED.
GET /sentinel/drift/:sourceX-LayerWindow-based drift for one system: 7-day against 30-day clearance, computed from sealed Canal crossings.
GET /client/driftX-LayerDrift across every active system owned by the caller, plus a worst-case aggregate. Systems with no scan return INSUFFICIENT_DATA, never a passing state.

Intelligence — the combined read

GET /intelligence/:source (X-Layer) runs the full sequence for one system and returns a single object: Sentinel-X drift, the Predictive-X forecast, the Prism-X narrative, and the Fusion-X unified state, with the underlying drift windows attached.

FieldValues
predictionDRIFT_IMMINENT (0–7 days) · DRIFT_LIKELY (7–21 days) · WATCH_CLOSELY · STABLE
trendSEVERE_RISK · HIGH_RISK · WATCH · STABLE
risk_levelCRITICAL · AT_RISK · STABLE

Cannot-assess is an explicit state, not a passing one. Where there is too little governed decision history, or the history is too noisy for a confident reading, the response carries INSUFFICIENT_DATA or INSUFFICIENT_CONFIDENCE with confidence: 0 and assessable: false. These propagate unchanged through the forecast and the narrative. They must not be read as an all-clear.

No endpoint returns a calibrated probability. Forecasts are weighted bands with a stated horizon.

ANASSA-X — board brief

POST /anassa/brief (X-Layer) generates a board-level strategic brief from the caller's own governance record. Rate-limited independently of the other endpoints.

Chronicle-Chain-X — sealed records

EndpointEntitlementNotes
POST /chain/appendX-Layer + ChainRuns the engine sequence, seals the result as a report with a SHA-256 content hash, and appends it to the caller's chain. Returns report_id, link_hash, previous_link_hash and chain_position. This endpoint seals a link; it does not verify the chain.
GET /chain/verify/:client_idX-Layer + ChainRecomputes every link's hash from its stored basis and checks that each link points at its predecessor.
GET /chain/public-verify/:chr_idpublicVerifies one Chronicle Chain link by its CHR- reference. No key required; mounted before the auth middleware, by design.

Verification returns one of four states, and never reports intact without having checked:

statusMeaning
CHAIN_INTACTEvery link recomputed and every linkage matched.
CHAIN_BROKENAt least one HASH_MISMATCH or BROKEN_LINKAGE, reported with its exact chain position.
UNVERIFIABLE_LEGACYLinkage checked, but one or more links predate content-basis storage and cannot be hash-recomputed.
EMPTYNo links on record.

If the registry is unreachable the endpoint returns 503 with verified: false and UNVERIFIABLE. It does not assume intact.

⚠ GET /chain/verify/:client_id may also return 403 REFUSED or 503 UNAVAILABLE for a reason concerning ANAX's own authority to perform the read — not the caller's entitlement. A well-entitled client can see either if ANAX's own Mandate is exhausted or unconfigured; it is not a statement about your access.

Scope. GET /chain/verify/:client_id is owner-scoped: a caller may verify only its own chain. X-Layer chain links verify publicly, no key: GET /chain/public-verify/:chr_id (mounted before auth, by design). Five states, not three: 200 valid:true (sealed) · 200 valid:false (well-formed, not found — a verdict, not an error) · 400 MALFORMED_ID · 429 RATE_LIMITED · 503 UNAVAILABLE (a lookup failure, never valid:false). The rate limiter runs before the id-format check, so a malformed id from an over-limit caller gets 429, not 400. Window: 60 seconds, 60 requests per IP per instance (the real ceiling is 60 × instances) — X-RateLimit-Limit / X-RateLimit-Window / X-RateLimit-Scope are set on every response, 200 through 503, so a caller who never sees a 429 can still read what the limit is. A 429 carries Cache-Control: no-store, and no Retry-After header is sent on any path. The response's attestation block (fixed, compiled-in facts about the verifier, not row data — marked "source": "constant") appears only on 200, on both valid:true and valid:false; 400, 429 and 503 attest nothing. Public verification also exists for Canal crossing seals (below).

AGC — not implemented

POST /agc/assess returns 501 NOT_IMPLEMENTED. The AGC certification engine does not exist, and this endpoint issues nothing and writes nothing.

It previously derived a certification level from fields supplied in the request body and returned a SHA-256-sealed certificate. A certificate minted from the caller's own inputs is not an assessment, and the endpoint was disabled rather than left to look like one. It will return a certificate when the engine exists and every input is derived server-side.

GET /agc/status/:client_id is owner-scoped: a caller may read only its own status (master keys excepted). It answers in one of three states, and an absence is never reported as a grade.

statusHTTPMeaning
— (the certificate row)200A certificate is on record. The stored row is returned as held.
NO_GRADE_ON_RECORD200The registry was read and holds no certificate for this client. agc_level is null.
UNAVAILABLE503The registry could not be read. agc_level is null. This is a read failure, not a finding about the client.

Absence is null, never 0. A grade of 0 is a measured verdict; “we could not look” and “we looked and found nothing” are not verdicts at all, and the two are kept distinct so a caller knows whether to retry or to act. No level name is returned beside a null level.

Canal — governed crossings

Canal evaluates a set of declared governance attributes about one AI decision — sector, risk class, personal-data flag, criticality, automation level, jurisdiction, and the regulation to cross it against — not the decision's actual content. The decision itself (what it decided, on what input) is never sent to Canal and never sealed; every attribute is caller-asserted, taken as declared and not independently verified by Canal. Canal seals its verdict on those attributes into a hash-chained, independently verifiable record: the declared attributes and the verdict are covered by the same seal — neither can be changed without invalidating verification. This is detectable tampering, not physical immutability: GET /canal/verify/:seal_id recomputes the hash from the stored basis, so an altered record fails verification rather than silently passing. The crossing is chained when it carries a clearance envelope (system_clearance_id), sealed but unchained otherwise — meta.chain_linked in the response reflects which actually happened, derived from the write itself, not asserted. A subscriber calls Canal directly with an API key. Two endpoints are public and need no account: GET /canal/frameworks (the framework catalogue) and GET /canal/verify/:seal_id (independent verification of any seal).

EndpointAuthNotes
POST /canal/envelopeAPI keyDeclare an AI system and mint its clearance envelope — the first call, prerequisite for a chained/clearing crossing. Returns clearance_id (env_...). Sending system_id / clearance_id is refused 400 (server-derived).
GET /canal/frameworkspublicThe framework catalogue with the tier each one requires.
GET /canal/verify/:seal_idpublicIndependent verification of a crossing seal. Returns the sealed governance-attribute record and verdict with secrets removed — never the decision's own content, which Canal was never sent.
POST /canal/evaluateAPI keyEvaluate and seal one crossing from six simple fields — self-serve, and gates on the base tier. For a chained, clearing crossing, first POST /canal/envelope.
POST /canal/evaluate/fullAPI keyEvaluate and seal one crossing from a complete canal-crossing-transaction-v2.1 contract object.
GET /canal/chain/verify/:clearance_idAPI keyVerify an envelope's crossing chain.
GET /canal/chain/export/:clearance_idAPI keyExport the full chain.
POST /fusionAPI key + X-Layer subUnified intelligence (Sentinel + Predictive + Prism + Fusion). 403 UPGRADE_REQUIRED without X-Layer. Pure computation — nothing sealed.
GET /canal/evidence/:seal_idAPI key, owner-scopedFull evidence bundle for a seal you own. 403 on someone else's seal.
GET /canal/crossingsAPI key, owner-scopedList your own crossings (owner scope).
POST /canal/attestationAPI key, owner-scopedRecord a self-declared compliance attestation (SOC 2 v1; other frameworks pre-provisioned, not yet selectable). Immutable once written. Not verified by ANAX.
GET /canal/attestationAPI key, owner-scopedList your own attestations, optionally filtered by subject_ref.

Quickstart — your first crossing

An active Canal API key, and a framework your tier licenses, is all you need. Send the key in the X-API-Key header and post two fields — the decision's governance attributes and the framework to cross them against. A framework outside your tier is refused 403 not_licensed (see below), not silently accepted. This minimal call is unchained by design; an envelope (POST /canal/envelope) is the setup step for a chained crossing, not required for this one:

curl -X POST https://<your-canal-host>/canal/evaluate \
  -H "X-API-Key: sk-canal-..." \
  -H "Content-Type: application/json" \
  -d '{"input":{"source":"my-system","type":"loan_decision"},"regulation":"eu_ai_act_2024"}'

The crossing is sealed and returns an outcome, a seal_id and a seal hash. This minimal call is sealed but not chained and returns CLEARED_WITH_VICCA_REQUIRED — the onboarding gate. To get a chained, clearing crossing, add a system_clearance_id from an envelope you created with POST /canal/envelope.

What a first call returns. An ungoverned first call on the six-field path — no Mandate presented — returns a CLEARED-family outcome: CLEARED_WITH_CONDITIONS inside an envelope, CLEARED_WITH_VICCA_REQUIRED without one; never bare CLEARED, which requires an authority-sealed VICCA assessment.

BLOCKED is itself a verdict — sealed and returned 200 like any other outcome, never an error status — and it has two independent gates. The prohibited-practice gate is not reachable from this six-field path: prohibited_practice is fixed false here and can only be set true via POST /canal/evaluate/full. The Mandate authority gate is reachable from both paths: present a mandate together with a commitment_class, and Canal judges the declared class against the act's floor and the Mandate's ceiling on the same reversible…binding ladder used throughout this API. A class below the floor, above the ceiling, or off the ladder entirely is refused as class_below_floor / class_exceeded / malformed_action — sealed as BLOCKED, not returned as an error. No mandate at all is the ungoverned path: not a refusal, and every existing caller's default. A mandate that is itself invalid or expired never reaches this gate — it is refused earlier, customer-actionable, 403 mandate_invalid / mandate_expired (below). ESCALATE_TO_VICCA is a retired alias, kept only for backward compatibility — CLEARED_WITH_VICCA_RECOMMENDED is what current code returns in its place. Do not expect bare CLEARED from a first call, and do not assume BLOCKED is unreachable just because you skipped /full.

Request — POST /canal/evaluate

Header: X-API-Key: <key> (the key is read from the header only). Body:

FieldTypeRequiredDefault / effect
inputobjectyesGovernance attributes about the decision being crossed — not the decision's content itself, which is never sent. Missing → 400 {"ok":false,"reason":"Missing input"}.
input.sourcestringin practice yesBecomes the AI system's system_id and provider — the identity the seal is chained under.
input.typestringrecommendedThe decision type, matched against the envelope's permitted decision types.
input.payload.sectorstringoptionalDefault "general". (.domain is accepted as an alias.)
input.payload.risk_classenumoptionalDefault "limited_risk". One of prohibited · high_risk · limited_risk · minimal_risk · gpai.
input.payload.personal_databooleanoptionalDefault false.
input.payload.criticalitystringoptionalDefault "medium".
regulationstringyesOne framework id from GET /canal/frameworks (e.g. eu_ai_act_2024, gdpr_2016_679, dora_2022, iso27001_2022). Must be in the key's licensed set, else 403 not_licensed. Missing → 400 {"ok":false,"reason":"Missing regulation"}.
automation_levelenumoptionalDefault "ai_assisted". One of fully_automated · ai_assisted · ai_informed · human_primary.
jurisdictionsstring[]optionalDefault ["EU"]. Only the first entry is used — for origin, destination and processing location alike.
system_clearance_idstringoptionalA clearance envelope you own (from POST /canal/envelope). Absent or unknown → the onboarding gate: sealed as CLEARED_WITH_VICCA_REQUIRED, not chained. An envelope you do not own is treated as absent.
commitment_classenumoptionalDefault null. One of observe · reversible · irreversible · financial · binding. Judged only when a mandate is presented.
mandatestringoptionalAn SD-JWT Open Mandate. When present it is verified, judged and spent (one unit). Absent = the ungoverned path every existing caller takes.
phase_flagsstring[]optionalDefault [] (regulatory-phase flags).

Do not send these — they are server-assigned and a sent value is either ignored or refused:

Worked example (from the repository's own seeder)

Posted against a MedTriage AI envelope created one step earlier:

{
  "system_clearance_id": "<clearance_id from POST /canal/envelope>",
  "input": {
    "source": "medtriage-triage",
    "type": "symptom_triage",
    "payload": { "sector": "general", "risk_class": "limited_risk", "personal_data": true, "criticality": "medium" }
  },
  "regulation": "eu_ai_act_2024",
  "automation_level": "ai_assisted",
  "jurisdictions": ["EU"]
}

→ 200, outcome: "CLEARED_WITH_CONDITIONS", reason: "Transaction within acceptable parameters. Cleared subject to standard oversight conditions." — the default branch when no VICCA assessment is on record. Not bare CLEARED, by design.

Response — 200

One body serves every sealed verdict. A governance block (a prohibited practice, or a Mandate declaring a class below the act's floor or above its ceiling) is this same body with outcome: "BLOCKED" — sealed and chained like any other verdict. There is no error key on any 200.

{
  "ok": true,
  "seal_id": "canal-<ms>-<12hex>",
  "seal": "<64-hex sha256 of the sealed basis>",
  "timestamp": "<ISO-8601>",
  "outcome": "<CANAL_OUTCOMES word>",
  "reason": "<determination basis>",
  "flags": ["<conflict flag>", "..."],
  "triggers": ["..."],
  "conditions": ["..."],
  "vicca_recommendation": <object | null>,
  "regulation": "<regulation>",
  "automation_level": "<automation_level>",
  "jurisdictions": ["<...>"],
  "canal_intelligence": <object | null>,
  "temporal_assessment": <object>,
  "crossings_remaining": <int>,
  "meta": {
    "engine": "CANAL-API", "version": "2.1", "mode": "sovereign-port",
    "evidence_stored": <bool>,
    "chain_linked": <bool>,
    "chain_seq": <int | null>
  }
}

meta.chain_linked is derived from the actual write and is false only for a crossing with no clearance envelope (nothing to chain to).

CANAL_OUTCOMES — the only values outcome can take:

BLOCKED · CLEARED · CLEARED_WITH_CONDITIONS · CLEARED_WITH_VICCA_RECOMMENDED · CLEARED_WITH_VICCA_REQUIRED · ESCALATE_TO_VICCA · REGISTERED

How to read a response — the three classes

The single rule: a governance-result field exists only when a gate evaluation ran and was sealed — /canal/evaluate returns it as outcome, /canal/evaluate/full as canal_evaluation.result. A pre-evaluation 4xx/5xx on either endpoint carries neither field and no seal. A refusal before evaluation — commercial, credential, or ours — carries an error word and seals nothing. A client that branches on outcome therefore never mistakes “your plan ran out” or “our service had a fault” for a governance verdict.

ClassWho actsHTTPWire markerSealed?
Governance verdict — a gate ruled on the crossingnobody: it is the answer200outcome + seal_id + sealyes
Customer-actionable — licence, plan, credentialyou remediate401 / 403 / 429an error word; never outcomeno
ANAX-side — a dependency we could not read, or our own credentialwe repair; you retry503an error word, or a reason ending “no verdict issued. Retry.”; never outcomeno — the allowance unit is refunded where one was taken

No Retry-After header is sent on any path. A 503 is a service-side condition an operator repairs, not a fixed wait; your normal backoff is the right pace.

Customer-actionable — 401 / 403 / 429 — you remediate

Authentication

ConditionResponse
no header401 {"ok":false,"reason":"X-API-Key header required","docs":"https://anaxstandard.ai/docs/api"}
unknown or inactive key401 {"ok":false,"reason":"Invalid or inactive API key"}
key suspended403 {"ok":false,"reason":"API key suspended"}
licence period ended403 {"ok":false,"reason":"License expired. Renew at app.anaxstandard.ai"}

Only a sealed verdict carries a governance result, and only at 200 — BLOCKED included: it is itself a verdict, sealed and returned 200 like any other outcome, never an error status. On /canal/evaluate, outcome and seal appear together at 200; on /canal/evaluate/full, the equivalent verdict field is canal_evaluation.result, with the same seal fields alongside it. A failure before evaluation carries neither a governance-result field nor a seal. Within that pre-verdict space, a named licence, allowance or Mandate fault carries an error word beside reason ({"ok": false, "error": "...", "reason": "..."}) — not_licensed, allowance_exhausted, mandate_invalid / mandate_expired / mandate_exhausted / mandate_refused, allowance_counter_unavailable, mandate_unavailable, chain_write_unavailable. All other failure classes are reason-only unless explicitly documented with an error code.

Framework not licensed

403 {"ok":false,"error":"not_licensed",
     "reason":"Regulation '<regulation>' not in your <license_tier> license. Upgrade at app.anaxstandard.ai",
     "your_frameworks":["<framework>","..."]}

your_frameworks is your own licensed set (never another tenant's). A key with no frameworks at all carries "reason":"No frameworks are licensed on this key. Contact app.anaxstandard.ai" and "your_frameworks":[].

Allowance exhausted

Emitted only on positive proof from a successful counter read (used >= limit); every other counter state is a 503, never a 429.

429 {"ok":false,"error":"allowance_exhausted",
     "reason":"Monthly crossing limit of <N,NNN> reached. Upgrade at app.anaxstandard.ai",
     "crossings_remaining":0}

The ceiling is the tier's: entry 5,000 · professional 20,000 · institutional 100,000 crossings per calendar month (UTC).

Mandate — customer-actionable

When you present a mandate that is yours to fix. Two moments, one shape — at load (verification: signature, expiry, audience) and at spend (the Registration Authority refused the unit):

403 {"ok":false,"error":"mandate_invalid" | "mandate_expired" | "mandate_exhausted",
     "reason":"<the same word>","allowance_refunded":<true | false>}

A customer-attributable RA refusal outside the named customer vocabulary returns "error":"mandate_refused". ANAX-side RA failures are normalised to 503 mandate_unavailable and are never exposed as customer Mandate faults. allowance_refunded is the measured result of the refund (true only when the crossing unit was actually returned), never “attempted”. Nothing is sealed on any of these.

ANAX-side — 503 — we repair, you retry

A 503 here means our service could not do its part: a dependency we could not read, a counter we could not trust, or our own credential refused by the Registration Authority. It is not “the network is down”, and it is never your Mandate or plan. The body carries one normalised word — no raw database, RA or library text ever reaches the wire. Nothing is sealed; the allowance unit is refunded wherever one had been taken.

ConditionResponse
allowance counter unreadable503 {"ok":false,"error":"allowance_counter_unavailable","reason":"Crossing counter temporarily unavailable - crossing not recorded. Retry shortly."} — no crossings_remaining: unknown is not zero.
RA unreachable / our credential refused503 {"ok":false,"error":"mandate_unavailable","reason":"mandate_unavailable","allowance_refunded":<bool>}
verifier unreachable (before any spend)503 {"ok":false,"reason":"Mandate could not be verified - no verdict issued. Retry."}
chain head could not be taken503 {"ok":false,"error":"chain_write_unavailable","reason":"chain_write_unavailable","allowance_refunded":<bool>}
a read we could not make (clearance envelope, VICCA assessment, chain position, trust weight)503 {"ok":false,"reason":"<dependency> unavailable - no verdict issued. Retry."}

Transport faults — 400 / 413

Raised before any route runs; the key is not read.

ConditionResponse
malformed JSON body400 {"ok":false,"reason":"Malformed JSON body"}
body over 10 MB413 {"ok":false,"reason":"Request body exceeds 10mb"}
malformed request URI400 {"ok":false,"reason":"Malformed request URI"}

POST /canal/evaluate/full — the full contract

Where /canal/evaluate builds the transaction object for you from six simple fields, /canal/evaluate/full takes the whole canal-crossing-transaction-v2.1 object — exposing the decision_impact, oversight_controls and richer data_profile blocks the evaluation reads directly. Admission requires contract === "canal-crossing-transaction-v2.1" and a caller-supplied transaction_id (a second seal of the same id for the same account → 409). Self-declared trust is neutralised server-side — a caller cannot assert its own evidence level.

Schema, not a proven example. The object below is the contract builder's output with its defaults, verbatim — but no /full fixture exists in the repository, so unlike the /evaluate example above it has not been measured against a live call. Derive your object from it; do not treat it as a tested request.

{
  "contract": "canal-crossing-transaction-v2.1",
  "transaction_id": "<caller-supplied, unique per account>",
  "timestamp": "<ISO-8601>",
  "ai_system": {
    "system_id": null, "provider": null, "provider_jurisdiction": null, "model_type": null,
    "deployment_environment": null, "hosting_jurisdiction": null, "system_clearance_id": null
  },
  "transaction_context": {
    "regulation": null, "decision_type": null, "sector": null, "use_case": null,
    "criticality": null, "automation_level": null, "human_review": null
  },
  "jurisdiction": {
    "origin": null, "processing_location": null, "destination": null,
    "affected_subject_jurisdiction": null, "additional_hops": []
  },
  "data_profile": {
    "personal_data": false, "special_category_data": false, "biometric_data": false,
    "financial_data": false, "health_data": false, "children_data": false
  },
  "ai_risk_profile": {
    "risk_class": null, "ai_act_domain": null, "prohibited_practice": false, "gpai_component": false
  },
  "decision_impact": {
    "affects_individual_rights": false, "economic_impact": false, "legal_effect": false,
    "service_access": false, "reversible": true
  },
  "oversight_controls": {
    "human_in_the_loop": false, "explainability_available": false,
    "appeal_mechanism": false, "decision_logging": false
  }
}

On /canal/evaluate/full the verdict is canal_evaluation.result, not a top-level outcome; the seal fields are the same.

Governed bilateral crossings — not self-serve

POST /v1/crossing:seal is the governed bilateral crossing path. It requires Closed Mandates held by both parties — issued by the Registration Authority against each party's Open Mandate and bound to one agreed Terms Document — and it is not self-serve: it is not callable with an API key alone (the credential is the Mandate). Its error vocabulary is a JSON-RPC extension documented in the Governed Crossing specification, Addendum A-3.5. For governed bilateral crossings, contact us.

What a third party can do without an account is verify a bilateral seal: GET /canal/verify/:seal_id and the public verification portal need no credential.

Self-declared compliance attestations — /canal/attestation

POST /canal/attestation records that a subject holds, or is pursuing, a named compliance framework — today, v1, only soc2 is selectable (the other three framework ids in the schema are pre-provisioned for future intake). Two shapes: status: "held" requires cert_number, issuing_body and a future-dated expiry_date; status: "in_progress" requires auditor_name, engagement_start and expected_completion. The source of every field is the caller, always — a constant, not a variable: Canal stores what you declare and stamps the row's sealed_label with “NOT verified by ANAX” verbatim. Rows are immutable once written. GET /canal/attestation lists your own rows (optionally filtered by subject_ref), each carrying a server-derived expired and superseded flag — never accepted from the caller.

Verification for third parties

A passport or certificate holder can direct anyone — an auditor, a governor, a counterparty — to the public verification portals. No account is required.

What the Canal API issues. A Decision Passport is issued by the VICCA ONE governance path, not by Canal — nothing documented in the Canal section above produces one. Every Canal call that reaches a verdict issues a seal_id and a seal hash only, independently verified with GET /canal/verify/:seal_id. If your integration needs a Decision Passport, that is a separate, VICCA ONE-issued artifact, not a Canal API response field.

Certificates resolve at .ai. Both certificate families carry an ANAX-<year>- reference, and the .ai portal resolves both: an earned-era reference (-EG) is answered by re-deriving its Evidence Grade from the sealed crossing, and a legacy -T1 reference is shown as a registry record with no grade. The .com registry holds only the legacy family and answers NOT FOUND for an -EG reference.

PortalVerifies
verify.anaxstandard.aiDecision Passports, Canal crossing seals, certificates, and Chronicle Chain links (CHR-)
verify.anaxstandard.comLegacy VICCA ONE Governance Defensibility Certificates (-T1) only

Conventions