Skip to content

Guided flow · ECZ-ID API Passport™

API guided flow

Carry an API through the Accountability Chain: its identity and operator, the caller and its context, authority and Intent, current state, a decision before consequence, the result, and the Receipt and re-verify.

  1. API identity
  2. Operator
  3. Caller and context
  4. Authority
  5. Intent
  6. Current state
  7. Decision
  8. Action and result
  9. Receipt and re-verify

Steps at a glance · what is live today

  1. 01Identify the APILive · public
  2. 02Connect the accountable operatorLive · public
  3. 03Resolve the caller and its contextLive · public
  4. 04Establish authorityLive · signed in
  5. 05Carry IntentLive · signed in
  6. 06Read current stateLive · public
  7. 07Decide before consequenceRelease-gated
  8. 08Act, and record the resultYour system
  9. 09Receipt and re-verifyNo public retrieval

Step 01 of 9

Identify the API#

Identity

Live · public

Live in production and publicly readable, with no account and no key.

GoalGive the API a free ECZ-ID Passport and declare it where its ecosystem already looks.

Why it mattersOne logical API is one Passport. Versions, environments, regional aliases and gateways are bindings; a second distinct API is a second Passport.

Prerequisites

  • An API you operate.
  • One identifier: its base URL, its OpenAPI URL or its repository.

Example

yamlIllustrative OpenAPI declaration: one x-ecz-id object, fields as in the OpenAPI x-ecz-id reference. Take the identifiers from your TrustOps console.
openapi: 3.1.0
info:
  title: Example Payments API
  version: "2.3.0"
  x-ecz-id:
    operator_ecz_id: ECZ-GB-EXAMPL
    api_passport_id: ECZ-GB-EXAMPL::API_PASSPORT-AB12CD
    api_origin: https://api.example.com
    spec_hash: sha256:<hash-of-canonical-spec>
    resolver_url: https://resolver.ecocitizenz.org/p/ECZ-GB-EXAMPL::API_PASSPORT-AB12CD

Check

bashCheck it from the relying side. No install, no account.
npx -y @ecocitizenz/ecz-id-mcp-verifier check --target https://api.ecocitizenz.com --target-type api_url

Expected output

jsonMeasured 2026-10-09 · abridged
{
  "target_type": "api_url",
  "policy_mode": "OPEN",
  "result_state": "NO_PUBLIC_RESOLVER_PROOF_FOUND",
  "reason_codes": ["NO_PUBLIC_RESOLVER_PROOF_FOUND", "RESOLVER_READ_ONLY",
                   "LOCAL_POLICY_DECIDES"],
  "primary_action": "CHOOSE_OPERATOR_PATH",
  "exit_code": 0
}

ECZ-ID’s own public API has not been issued an API Passport. Declare yours in OpenAPI info as one x-ecz-id object, with the fields in the OpenAPI x-ecz-id reference at /bindings/openapi-x-ecz-id/.

What this proves

  • That the machine has a persistent, publicly resolvable identity under an accountable organisation, once issued and declared.

What this does not prove

  • That the machine is safe, secure, correct or verified. A free Passport never claims that.
  • Control of any native identity. A declaration is a pointer; only a proven binding proves.

Next: Connect the accountable operator →

Step 02 of 9

Connect the accountable operator#

IdentityAuthority

Live · public

Live in production and publicly readable, with no account and no key.

GoalResolve the Business Passport of the organisation that answers for the API.

Why it mattersEvery child Passport resolves to one accountable organisation. A relying party who reaches the API can always find who stands behind it, without asking.

Prerequisites

  • The API's ECZ-ID, or the parent ECZ-ID it names.

Example

bashRuns anywhere. No key, no account.
curl -s https://api.ecocitizenz.com/api/p/ECZ-GB-RBS1NW.json

Expected output

jsonMeasured 2026-10-09 · abridged
{
  "ecz_id": "ECZ-GB-RBS1NW",
  "resolver_v2": {
    "record_type": "ECZ_ID_PARENT",
    "state": {
      "lifecycle_state": "ACTIVE",
      "parent_tier": "VERIFIED",
      "reliance_level": "PARENT_IDENTITY_ONLY"
    },
    "reliance": {
      "can_rely_on": ["record_existence", "active_parent_state",
                      "current_parent_tier", "public_resolver_visibility"],
      "reverify_before_reliance": true
    }
  }
}

This is the operator record behind this site. Read parent_tier_context.tier_meaning on a child record: a parent tier is never inherited by a machine.

What this proves

  • An accountable organisation exists and is publicly resolvable.
  • Its current lifecycle state and parent tier, at the moment you read them.

What this does not prove

  • That the organisation authorised any particular action by the API.
  • That the machine itself is verified, safe or compliant.

Next: Resolve the caller and its context →

Step 03 of 9

Resolve the caller and its context#

IdentityCurrent state

Live · public

Live in production and publicly readable, with no account and no key.

GoalBefore serving a consequential request, resolve the calling agent or service by its ECZ-ID.

Why it mattersReciprocal trust runs both ways. The caller checked your API; your API checks the caller. Its record says who operates it and what state it is in now.

Prerequisites

  • The caller’s ECZ-ID, carried by your own authentication or request metadata.

Example

jsRuns today. Node 18+.
// Runs today: your own policy over live Resolver state.
async function mayProceed(eczId) {
  const res = await fetch('https://api.ecocitizenz.com/api/p/' + eczId + '.json');
  if (res.status === 404) return { ok: false, why: 'NO_PUBLIC_ECZ_ID_FOUND' };
  if (!res.ok) return { ok: false, why: 'RESOLVER_UNREADABLE', retry: true };
  const v2 = (await res.json()).resolver_v2;
  const failClosed = v2.machine_policy.fail_closed_states;
  const ok = v2.state.lifecycle_state === 'ACTIVE'
    && !failClosed.includes(v2.state.lifecycle_state)
    && !failClosed.includes(v2.binding.state);
  return { ok, why: v2.state.reliance_level };
}

What this proves

  • The caller’s accountable operator and current published state.

What this does not prove

  • That the caller is authorised for this request. That is authority and Intent, next.

Next: Establish authority →

Step 04 of 9

Establish authority#

Authority

Live · signed-in operation in TrustOps

Live in production and operated by the account holder, signed in, through the TrustOps Dashboard.

GoalHave an owner or admin authorise an ECZ-ID Intent for the calling agent in the TrustOps Dashboard.

Why it mattersAuthority is who authorised the work, recorded where a stranger can later ask. A Passport, a payment or a binding never grants it.

Prerequisites

  • The calling agent's child Passport.
  • An ECZ-ID Intent plan, purchased in TrustOps.
  • An owner or administrator to authorise. Nobody authorises their own request.

Example

jsonIllustrative. The authorisation block Core records on an Intent; placeholder values.
"authorisation": {
  "kind": "PRINCIPAL_ASSERTION",
  "principal_ref": "user:finance-admin-01",
  "principal_role": "ADMIN",
  "authorised_at": "2026-10-11T09:14:02Z",
  "evidence_digest": "sha256:<64 hex>"
}

What this proves

  • Who authorised the calling agent's work, in what role, and when, as ECZ-ID Core recorded it.

What this does not prove

  • Any permission in your own access systems. OAuth scopes and API keys stay yours.
  • That runtime actions stay inside the bounds. That needs a decision before the action.
  • Anything public: the public Intent disclosure is not enabled.

Next: Carry Intent →

Step 05 of 9

Carry Intent#

Intent

Live · signed-in operation in TrustOps

Live in production and operated by the account holder, signed in, through the TrustOps Dashboard.

GoalState what the calling agent is authorised to do, within what bounds and for how long, and make every consequential action name that Intent.

Why it mattersAn Intent can name the counterparty it may act against, how fresh that counterparty’s proof must be, and what happens if its state changes.

Prerequisites

  • An authorised ECZ-ID Intent in force (state ACTIVE).
  • A single choke point in your code where consequential actions pass.

Example

jsonIllustrative, abridged. Field names from the Core contract ecz.intent.v1; placeholder identifiers.
{
  "schema": "ecz.intent.v1",
  "actor": { "kind": "ECZ_PASSPORT", "ecz_id": "ECZ-GB-EXAMPL::AGENT_PASSPORT-AB12CD", "family": "AGENT" },
  "objective_summary": "Refund damaged orders through the payments API",
  "constraints": {
    "actions": { "permitted": ["refund:create"], "excluded": [] },
    "resources": { "permitted": ["orders/*"], "excluded": [] },
    "financial": { "currency": "GBP", "max_per_action": 250, "max_total": 2500 },
    "counterparty": {
      "counterparty_ecz_id": "ECZ-GB-EXAMPL",
      "required_dci_state": "BOUND",
      "max_proof_age_seconds": 300,
      "on_change": "RE_RESOLVE"
    }
  },
  "window": { "not_before": "2026-10-12T00:00:00Z", "expires_at": "2026-10-19T00:00:00Z" },
  "state": "ACTIVE"
}

In your system

jsIllustrative. Your audit log, written before each consequential action.
// Your audit log: one entry per consequential action, written before it runs.
// ECZ-ID defines the Intent reference; the rest belongs to your system.
log.info('consequential_action', {
  actor_ecz_id: 'ECZ-GB-EXAMPL::AGENT_PASSPORT-AB12CD',
  intent_ref: { intent_id: 'INT-0000000000EXAMPLE0', version: 3 },
  request_id: crypto.randomUUID(),
  action: 'refund:create',
  resource: 'orders/88213',
  amount: 120,
  currency: 'GBP',
});

Request identifiers, nonces and correlation IDs belong to the request, not the Intent. ECZ-ID defines no header for carrying an Intent reference over arbitrary protocols, and this site does not invent one.

What this proves

  • What was authorised, as a versioned record with its bounds and window.

What this does not prove

  • That any action stayed inside those bounds. Holding an Intent identifier authorises nothing by itself.
  • That a sandbox Intent covers production. It never does.

Next: Read current state →

Step 06 of 9

Read current state#

Current state

Live · public

Live in production and publicly readable, with no account and no key.

GoalRead both parties’ state live at the moment of the request, not from a cache.

Why it mattersA counterparty that was active this morning can be suspended this afternoon. Records are served no-store, and fail-closed states are listed by the record itself.

Prerequisites

  • Both ECZ-IDs.

Example

bashRuns anywhere.
curl -s https://api.ecocitizenz.com/api/p/ECZ-GB-RBS1NW.json

Expected output

jsonMeasured 2026-10-09 · resolver_v2.machine_policy, abridged
"machine_policy": {
  "recommended_machine_action": "ALLOW_PARENT_IDENTITY_VISIBILITY_ONLY",
  "high_risk_action": "FAIL_CLOSED_UNLESS_LIVE_BINDING_AND_EVIDENCE_AVAILABLE",
  "fail_closed_states": ["REVOKED", "SUSPENDED", "DEGRADED", "MISMATCH",
    "PROOF_UNAVAILABLE", "PUBLIC_PROJECTION_UNAVAILABLE", "ACTIVE_ABUSE_FLAGGED", "UNKNOWN"]
}

What this proves

  • The published state now, and which states the publisher expects you to fail closed on.

What this does not prove

  • State a minute from now.

Next: Decide before consequence →

Step 07 of 9

Decide before consequence#

Current stateDecision

Release-gated · interface not public

Documented ahead of release.

GoalDecide whether the request's proposed action may proceed before it changes anything.

Why it mattersAn HTTP gateway in front of the API is where a request can be held or blocked. IntentControl is designed to evaluate a proposed action against identity, authority, delegation, Intent, policy, current state and constraints. It is documented ahead of release, so today you decide with your own policy over live state.

Prerequisites

  • A choke point in your code in front of the action.
  • The counterparty or target ECZ-ID.

Example

jsRuns today: local policy over live Resolver state. Node 18+.
// Runs today: your own policy over live Resolver state.
async function mayProceed(eczId) {
  const res = await fetch('https://api.ecocitizenz.com/api/p/' + eczId + '.json');
  if (res.status === 404) return { ok: false, why: 'NO_PUBLIC_ECZ_ID_FOUND' };
  if (!res.ok) return { ok: false, why: 'RESOLVER_UNREADABLE', retry: true };
  const v2 = (await res.json()).resolver_v2;
  const failClosed = v2.machine_policy.fail_closed_states;
  const ok = v2.state.lifecycle_state === 'ACTIVE'
    && !failClosed.includes(v2.state.lifecycle_state)
    && !failClosed.includes(v2.binding.state);
  return { ok, why: v2.state.reliance_level };
}

What this proves

  • Your own decision, made on the current published state of the counterparty.

What this does not prove

  • An Intent-bounded decision. That is IntentControl: its interface is release-gated and not callable, and commercially it is coming soon in TrustOps and not yet on sale.
  • Enforcement. A decision only constrains actions routed through an enforcement point you run.

Next: Act, and record the result →

Step 08 of 9

Act, and record the result#

Action

Runs in your system

This happens in the integrator’s own system.

GoalServe the request, or refuse it, and record what you did.

Why it mattersThe action and its result happen in your API. A decision says what was determined; only your system knows what actually happened.

Prerequisites

  • Your audit log entry from the Intent step.

What this proves

  • What your system did, as your system recorded it.

What this does not prove

  • That an allowed request succeeded. An allow is not a result.

Next: Receipt and re-verify →

Step 09 of 9

Receipt and re-verify#

ReceiptEvidenceRe-verify

Recorded · no public retrieval

ECZ-ID Core records it, but public, unauthenticated retrieval and verification are not enabled.

GoalKeep a record of what the API did, and know what ECZ-ID publishes about it.

Why it mattersReceipts record decisions, mediation and lifecycle events in Core. A Receipt proves what the recording system recorded; it does not prove every real-world assertion behind it.

Prerequisites

  • Your own audit log entry from the Intent step.

Example

bashRead the evidence block of a public record.
curl -s https://api.ecocitizenz.com/api/p/ECZ-GB-RBS1NW.json

Expected output

jsonMeasured 2026-10-09 · resolver_v2.evidence, abridged
"evidence": {
  "public_receipts_count": 0,
  "public_receipt_state": "NO_PUBLIC_RECEIPT",
  "public_receipt_retrievable": false,
  "receipt_retrieval_state": "NONE_PUBLISHED",
  "recorded_receipts_count": 1,
  "reason_codes": ["RECEIPTS_RECORDED_NOT_PUBLICLY_RETRIEVABLE"]
}

Recorded is not published. Decide on evidence.public_receipt_state and evidence.public_receipt_retrievable, the fields the record lists for machines.

What this proves

  • Whether Core has recorded receipts for the subject, and whether any are publicly retrievable.

What this does not prove

  • What any receipt says. Public retrieval and public verification are not enabled.
  • That an action happened or succeeded. An allow is not a result, and a decision receipt is not an action receipt.

Where next

This page is explanation, not authority. Current state is only ever the live Resolver record. Commands were run read-only on 2026-10-10; illustrative examples are labelled.