Halt Gate · free signed door

Is the market open?
Signed. No signup.

One signed attestation per call. Your agent loop cannot trade into a halted venue. The receipt itself is the audit trail.

The one use case

Don't let my agent trade into a halted venue.

Your loop is autonomous. It picks a trade. Before it acts, you want a cryptographic check that the venue is actually open. If anything is unclear — the API is unreachable, the receipt is stale, the signature doesn't match, the venue is HALTED, the venue's status is UNKNOWN — the loop halts. UNKNOWN is treated as CLOSED. That is the fail-closed contract.

  1. 1

    Fetch a signed receipt (free, no key)

    Unauthenticated. Rate-limited at the edge. Returns the same authoritative receipt as the paid /v5/status — byte-equivalent canonical bytes for the same MIC at the same instant.

    $ curl https://headlessoracle.com/v1/status/XNYS
    
    {
      "receipt_id":     "8c2a…",
      "issued_at":      "2026-06-14T13:42:11.000Z",
      "expires_at":     "2026-06-14T13:43:11.000Z",
      "issuer":         "headlessoracle.com",
      "mic":            "XNYS",
      "status":         "OPEN",
      "source":         "SCHEDULE",
      "halt_detection": "active",
      "receipt_mode":   "live",
      "schema_version": "v5.0",
      "public_key_id":  "key_2026_v1",
      "signature":      "f3a…",                          // 64-byte Ed25519, hex
      "receipt":        { …same flat fields above… },    // unsigned wrapper — mirror, do not include in canonical bytes
      "discovery_url":  "…/.well-known/mcp/server-card.json"   // unsigned wrapper — discovery only
    }

    Signed canonical payload is the flat field set above (everything except signature, receipt, and discovery_url), sorted alphabetically and JSON-stringified with no whitespace. receipt and discovery_url are unsigned wrapper fields — exclude them before verifying or you will get INVALID_SIGNATURE. The @headlessoracle/verify SDK strips them automatically via its UNSIGNED_WRAPPER_FIELDS allowlist.

  2. 2

    Drop the guard into your loop (npm)

    Five lines. safeToExecute() fetches the receipt, verifies the signature, enforces your freshness policy, checks the MIC, checks the venue is OPEN. Any failure mode → safe: false with a machine-readable reason. The receipt is returned either way — log it.

    $ npm install @headlessoracle/verify
    
    import { safeToExecute } from '@headlessoracle/verify';
    
    const { safe, reason, receipt } = await safeToExecute('XNYS', {
      max_attestation_age: 30,  // seconds — YOUR freshness policy. No default.
    });
    
    // Log the artifact whichever way the decision went — receipt is the audit trail.
    logEvent({ kind: 'pretrade-gate', safe, reason, receipt });
    
    if (!safe) return;          // fail-closed: do nothing
    await placeOrder(/* ... */);

    max_attestation_age is required. There is no default — the IETF environment.* family rule is that the relying party declares its own freshness policy. Tight is safe — your relying-party policy should typically be shorter than HO's TTL.

  3. 3

    Gate on-chain too — receipt-or-revert (Solidity)

    A Solidity reference implementation of the same pattern: contracts/HaltGuard.sol. Pass the canonical signed bytes; the contract verifies the Ed25519 signature against HO's published public key, binds your freshness window to the signed issued_at, and reverts unless the venue is OPEN. Reference code — not audited for production.

    // In your trading contract, before any state-changing action:
    haltGuard.checkSafe(
        canonicalReceiptBytes,   // alpha-sorted JSON form of receipt minus signature
        signature,               // the Ed25519 signature returned by /v1/status
        issuedAtIso              // receipt.issued_at — bound to signed bytes inside
    );
    // If checkSafe() returns, the venue is OPEN, the receipt is fresh, and the
    // signature is valid. If anything fails — the call reverts.

    Ed25519 on EVM is non-trivial. The reference contract abstracts the verifier via IEd25519Verifier; plug in a chain-native precompile (Solana, Aptos, Sui, some L2s) or a Solidity library where no precompile exists.

What this is, and isn't.

What the receipt is

  • A signed attestation of the venue's session-state as observed by HO at issued_at.
  • Cryptographically verifiable offline using HO's published Ed25519 key.
  • An audit artifact: the bytes that justified your decision, retained as long as you choose.
  • Byte-equivalent across the free /v1/status door and the paid /v5/status door. Same truth, two doors.

What it is not

  • Ground truth. It is HO's observation; the relying contract enforces freshness at action-time.
  • A liveness guarantee. A receipt with status OPEN at issued_at = T does not promise the venue is open at T+Δ.
  • Per-symbol halt coverage at the MIC layer. Single-name halts (LULD pauses, T1 news pauses) do not flip a whole-venue status. For per-symbol detail see the halt archive.
  • A replacement for legal compliance. The receipt anchors decisions; it does not waive them.

Every fail-closed branch.

safeToExecute() returns a machine-readable reason on failure. Match on it; do not parse free text.

reason meaning
NETWORK_ERRORfetch to /v1/status threw — network or DNS failure
BAD_RESPONSEnon-2xx status or unparseable JSON
MISSING_FIELDSreceipt missing signature / public_key_id / issued_at / expires_at
STALE_RECEIPT(now − issued_at) > your max_attestation_age
EXPIREDreceipt's own expires_at has passed (HO's 60-s TTL)
INVALID_SIGNATUREEd25519 signature does not match the canonical payload
INVALID_KEY_FORMATpublic key or signature is not valid hex (Web Crypto rejected the bytes)
UNKNOWN_KEYpublic_key_id not in HO's published registry
KEY_FETCH_FAILED/v5/keys request failed
SPEC_UNAVAILABLE/v5/keys returned no canonical_payload_spec — cannot determine signed-field allowlist
WRONG_MICreceipt MIC does not match the one you asked for (proxy / CDN misroute)
NOT_OPENvenue status is CLOSED, HALTED, or UNKNOWN. UNKNOWN is treated as CLOSED — the fail-closed contract.

Further reading