5-minute quickstart: decision receipts for trading bots

Decision receipts for trading bots. Send Titan a signal. Get back an allow/deny decision, gate reasons, and a receipt you can inspect later.

This quickstart uses the public API. It does not require broker credentials, and Titan's hosted service never places broker orders — it decides; your own forwarder (if you run one) executes.

Titan is diagnostics infrastructure, not a trading availability SLA. Evaluate latency, errors, and fallback behavior in your own setup before relying on it in live automation.

1. Create or use an agent key

In the Titan dashboard, open /admin/agents, create an agent, and copy the raw key shown once. It starts with tak_.

The agent key is the only credential you need. It binds your account server-side — do not send account_id or agent_id in the body, and never send broker API keys (Titan rejects them with BROKER_KEYS_NOT_ALLOWED).

The canonical API host is wwwhttps://www.titandiagnostics.io. The bare apex (titandiagnostics.io) 307-redirects to www, and curl will not replay a POST body across a redirect unless you pass -L, so always point at the www host directly.

bash
export TITAN_URL="https://www.titandiagnostics.io"
export TITAN_AGENT_KEY="tak_your_agent_key_here"

2. Ask Titan for a full allow/deny eval

POST /api/public/v1/eval?eval_mode=full is the front door — start here. The Python example below is the most reliable path (no shell-quoting pitfalls).

Python (recommended)

Install the one third-party package used by this snippet:

bash
python3 -m pip install requests
python
import os
import requests

base_url = os.environ["TITAN_URL"].rstrip("/")
headers = {"X-Titan-Agent-Key": os.environ["TITAN_AGENT_KEY"]}

signal = {
    "symbol": "AAPL",
    "side": "BUY",
    "close": 190.50,
    "atr": 2.50,
    "ts": "2026-06-07T15:00:00Z",
    "strategy_hint": "quickstart",
}

resp = requests.post(
    f"{base_url}/api/public/v1/eval?eval_mode=full",
    json=signal,
    headers=headers,
    timeout=10,
)
resp.raise_for_status()
decision = resp.json()
print(decision["allow"], decision["reason_code"], decision["reason_label"])
print("trace_id:", decision["trace_id"])

curl (macOS / Linux)

bash
curl -sS "$TITAN_URL/api/public/v1/eval?eval_mode=full" \
  -H "Content-Type: application/json" \
  -H "X-Titan-Agent-Key: $TITAN_AGENT_KEY" \
  -d '{
    "symbol": "AAPL",
    "side": "BUY",
    "close": 190.50,
    "atr": 2.50,
    "ts": "2026-06-07T15:00:00Z",
    "strategy_hint": "quickstart"
  }'

curl (Windows PowerShell)

Single-quoted JSON on the PowerShell command line is not reliable, and a UTF-8 BOM file is rejected as invalid JSON. Write the body to an ASCII (no-BOM) file and post it with --data-binary:

powershell
$env:TITAN_URL = "https://www.titandiagnostics.io"
$env:TITAN_AGENT_KEY = "tak_your_agent_key_here"

Set-Content -LiteralPath signal.json -NoNewline -Encoding ASCII -Value `
  '{"symbol":"AAPL","side":"BUY","close":190.50,"atr":2.50,"ts":"2026-06-07T15:00:00Z","strategy_hint":"quickstart"}'

curl.exe -sS "$env:TITAN_URL/api/public/v1/eval?eval_mode=full" `
  -H "Content-Type: application/json" `
  -H "X-Titan-Agent-Key: $env:TITAN_AGENT_KEY" `
  --data-binary "@signal.json"

Example response

json
{
  "ok": true,
  "allow": true,
  "status": "SUCCESS",
  "reason": "WOULD_SUBMIT_ORDER",
  "reason_code": "WOULD_SUBMIT_ORDER",
  "reason_label": "Signal passed Titan's decision gates",
  "eval_mode": "full",
  "deploy_gate_compatible": true,
  "limited_coverage": false,
  "coverage_scope": "full_pretrade",
  "coverage_note": "Full pretrade includes sizing/envelope diagnostics but still never executes broker orders.",
  "trace_id": "b1aef5a4174e4bf49acf8d6aaebaf1d6",
  "decision_summary_url": "https://www.titandiagnostics.io/analytics/autopsy/b1aef5a4174e4bf49acf8d6aaebaf1d6",
  "trace_viewer_url": "https://www.titandiagnostics.io/traces/b1aef5a4174e4bf49acf8d6aaebaf1d6",
  "next_action_hint": "your_bot_may_proceed",
  "tags": {
    "agent_id": "agt_abc123",
    "agent_name": "quickstart-bot",
    "symbol": "AAPL",
    "side": "BUY",
    "source": "agent",
    "eval_mode": "full"
  }
}

Use allow for the first decision:

3. Request fields

FieldRequiredNotes
symbolyesTicker; an exchange prefix like NASDAQ:AAPL is stripped to AAPL.
sideyesResolves to BUY or EXIT. Aliases: buy/longBUY; sell/short/close/close_long/exit/exit_longEXIT. (So "side":"SELL" is accepted and means EXIT, not an error.)
closeyesReference price the signal was generated at. Must be a number > 0.
atrnoAverage True Range. Number ≥ 0 if present.
tsnoISO-8601 timestamp string.
strategy_hintnoFree-text label, ≤ 128 chars.
signal_idno (/signal)Caller-supplied durable id for idempotent duplicate detection (see §4).

Unknown fields are ignored. Titan accepts and silently drops keys it does not model — including quantity and notional. Titan sizes orders from its own configuration, so sending notional/quantity has no effect. Do not rely on them.

4. /eval vs /signal

POST /api/public/v1/evalPOST /api/public/v1/signal
PurposeOne-shot pre-trade decision checkRecord a live-style signal through the ingestion path
Evaluates gates?Yes (eval_mode=full for sizing/envelope diagnostics)Yes (full live cascade)
Creates a trace/receipt?YesYes
Submits broker orders?No (hosted Titan never does)No (hosted Titan never does; your forwarder executes if you run one)
Start here?YesOnly once you want the ingestion/forwarder path

Most bots only need /eval. Use /signal when you specifically want the live ingestion behavior.

bash
curl -sS "$TITAN_URL/api/public/v1/signal" \
  -H "Content-Type: application/json" \
  -H "X-Titan-Agent-Key: $TITAN_AGENT_KEY" \
  -d '{
    "symbol": "AAPL",
    "side": "BUY",
    "close": 190.50,
    "atr": 2.50,
    "signal_id": "quickstart-001"
  }'

What you actually get on a fresh account depends on broker state. With no live forwarder pushing state, /signal commonly returns:

json
{
  "ok": true,
  "status": "REJECTED",
  "forwarded": false,
  "reason": "no_broker_state",
  "reason_code": "no_broker_state",
  "reason_label": "Broker state unavailable",
  "trace_id": "quickstart-001",
  "symbol": "AAPL",
  "side": "BUY",
  "blocked_by_gates": [],
  "next_action_hint": null
}

This is expected: drift/position gates need a recent broker-state snapshot, which only exists once your forwarder is connected and pushing. Until then, use /eval for decision checks. When state is available and the signal passes, you'll see status: "DRY_RUN" (sandbox/observe) or SUCCESS with forwarded: true and reason_label: "Signal passed Titan's decision gates".

If the market is closed, /signal may block at MARKET_CLOSED before broker-state checks. Keep /eval?eval_mode=full as the first-run path when you are just confirming that the agent key and receipt flow work.

Duplicate signal_id. If you re-send the same signal_id, /signal returns 200 with status: "REJECTED" and reason: "DUPLICATE_SIGNAL_ID". This is a deliberate duplicate-reject, not a silent success and not a replay of the original decision — to fetch the original, look it up by its trace_id (§6). Use a fresh signal_id per distinct signal.

Keep the returned trace_id.

5. Note: the response is the receipt's summary

The /eval and /signal responses are the decision summary. The full receipt — including the redacted signal, the gate-by-gate outcomes, the execution environment, and the proof boundary — is fetched by trace_id in the next step.

6. Retrieve the decision receipt

bash
curl -sS "$TITAN_URL/api/public/v1/decisions/b1aef5a4174e4bf49acf8d6aaebaf1d6" \
  -H "X-Titan-Agent-Key: $TITAN_AGENT_KEY"
json
{
  "trace_id": "b1aef5a4174e4bf49acf8d6aaebaf1d6",
  "decision": "SUCCESS",
  "allow": true,
  "reason": "WOULD_SUBMIT_ORDER",
  "reason_code": "WOULD_SUBMIT_ORDER",
  "reason_label": "Signal passed Titan's decision gates",
  "symbol": "AAPL",
  "side": "BUY",
  "evaluated_at": "2026-06-07T18:00:48.125945+00:00",
  "signal": {
    "raw_redacted": { "symbol": "AAPL", "side": "BUY", "close": 190.50, "atr": 2.50, "strategy_hint": "quickstart" },
    "normalized": { "symbol": "AAPL", "side": "BUY", "close": 190.50, "atr": 2.50 }
  },
  "gates": [
    { "name": "duplicate_detection", "status": "pass", "reason": null },
    { "name": "side_valid", "status": "pass", "reason": null },
    { "name": "market_open", "status": "pass", "reason": null }
  ],
  "gates_note": null,
  "decision_signature": "12f7aa66297e43db…",
  "eval_hash": "0a84a1dd4e7ad742…",
  "input_hash": "65e2ce17c65a39e2…",
  "config_hash": "4de62bf3a4319e8d",
  "versions": { "engine": "render-paper", "strategy": "S1.0", "diagnostics": "D1.0" },
  "state_source": null,
  "state_age_ms": null,
  "execution_environment": {
    "trading_mode": "sandbox",
    "dry_run": true,
    "would_forward": false,
    "money_at_risk": false,
    "settles_via": "forwarder",
    "hosted_broker_mutation": false,
    "explanation": "Hosted Titan evaluated this decision. Broker mutation, if any, is performed by the user-controlled forwarder."
  },
  "proof_boundary": {
    "proves": [
      "Titan received this signal.",
      "Titan evaluated it against these decision gates.",
      "Titan recorded a timestamped decision commitment, anchored to Bitcoin once the daily root is built."
    ],
    "does_not_prove": [
      "That a broker accepted, filled, or even received an order.",
      "That the decision was profitable or strategically sound.",
      "That the submitted signal input was correct or complete.",
      "Legal or compliance-grade nonrepudiation."
    ]
  },
  "audit_anchor": {
    "state": "awaiting_daily_root",
    "block_height": null,
    "submitted_at": null,
    "confirmed_at": null,
    "proof_available": false,
    "anchor_provider": null,
    "proof_url": null
  }
}

Notes:

7. Download proof if available

Fresh receipts usually do not have proof bytes yet. Check audit_anchor.proof_available first.

bash
curl -fS "$TITAN_URL/api/public/v1/decisions/b1aef5a4174e4bf49acf8d6aaebaf1d6/audit-anchor.ots" \
  -H "X-Titan-Agent-Key: $TITAN_AGENT_KEY" \
  -o "decision.audit-anchor.ots"

If proof is not ready, the API returns 404 with:

json
{
  "error": {
    "code": "NO_PROOF_AVAILABLE",
    "message": "No downloadable proof for this decision yet.",
    "details": {
      "anchor_state": "awaiting_daily_root"
    }
  }
}

8. Errors and conventions

All public API errors use one envelope: {"error": {"code": ..., "message": ...}} (validation errors add a details array). This includes oversized bodies (413) — a JSON API never returns HTML here.

HTTPCodeMeaning
400INVALID_JSON_OBJECTBody wasn't a JSON object. On Windows, this usually means a BOM or shell-quoting issue — use an ASCII no-BOM file (§2).
400BROKER_KEYS_NOT_ALLOWEDDo not send broker API keys or secrets to hosted Titan.
401AGENT_KEY_REQUIREDAdd the X-Titan-Agent-Key header.
401INVALID_AGENT_KEYThe key is malformed, revoked, or does not exist.
413REQUEST_TOO_LARGEBody exceeds the 1 MB limit.
422VALIDATION_ERRORA required field is missing or has the wrong type; see details[].
404NOT_FOUNDThe trace/receipt does not exist for this agent/account (existence is hidden cross-tenant).
404NO_PROOF_AVAILABLEThe receipt exists but no downloadable proof is ready.
429AGENT_RATE_LIMITPer-key rate limit exceeded; honor the Retry-After header and retry.

Conventions:

9. Optional: attach a forwarder-reported outcome

If your own forwarder later submits to a broker, it can attach the outcome to a trace:

bash
curl -sS "$TITAN_URL/tv/execution_result" \
  -H "Content-Type: application/json" \
  -H "X-Titan-Agent-Key: $TITAN_AGENT_KEY" \
  -d '{
    "trace_id": "b1aef5a4174e4bf49acf8d6aaebaf1d6",
    "order_id": "broker-order-123",
    "symbol": "AAPL",
    "action": "buy",
    "status": "filled",
    "submitted_price": 190.50,
    "filled_price": 190.52,
    "qty": 1,
    "filled_at": "2026-06-07T15:00:03Z"
  }'

Read it back:

bash
curl -sS "$TITAN_URL/v1/traces/b1aef5a4174e4bf49acf8d6aaebaf1d6/execution" \
  -H "X-Titan-Agent-Key: $TITAN_AGENT_KEY"

Forwarder-reported broker outcomes are attached evidence, not independent broker truth.

What Titan proves and does not prove

Titan can give you:

Titan does not prove:

Detailed decision event data follows account retention policy. Receipt commitments and proof artifacts are timestamped evidence commitments, not proof of correctness, profitability, or broker execution.