{"openapi":"3.1.0","info":{"title":"Advisors Crypto — Agent API","version":"0.2.0","description":"Programmatic access for AI agents to AC's policy layer. Agents read state, validate proposed trades against the user IPS, and submit trades for human attestation. There is no execute endpoint by design — every action that mutates the portfolio requires user attestation in the AC app.","contact":{"name":"Advisors Crypto","url":"https://advisorscrypto.com"}},"servers":[{"url":"https://api.advisorscrypto.com","description":"Production"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Agent API key minted at https://app.advisorscrypto.com/agent. Format: `ac_live_<64 hex>` (production) or `ac_test_<64 hex>` (sandbox — fixed IPS, no real ServiceRequests / audit writes; responses carry `X-AC-Sandbox: true`)."}},"schemas":{"TradePayload":{"type":"object","required":["action","assetSymbol","assetType","amountUsd"],"properties":{"action":{"type":"string","enum":["buy","sell"]},"assetSymbol":{"type":"string","example":"BTC"},"assetType":{"type":"string","enum":["crypto","tradfi"]},"amountUsd":{"type":"number","minimum":0,"example":5000},"expectedSlippagePct":{"type":"number","example":0.5}}},"Violation":{"type":"object","required":["rule","message"],"properties":{"rule":{"type":"string","enum":["no_active_ips","no_portfolio_snapshot","action_not_allowed","crypto_max_allocation","crypto_min_allocation","tradfi_max_allocation","tradfi_min_allocation","cash_floor","insufficient_cash","drawdown_exceeded","slippage_exceeded","agent_aum_cap","asset_authority_monitor","asset_authority_protect_sell","standing_rule_blocked","standing_rule_requires_approval","invalid_payload"]},"message":{"type":"string"},"limit":{"type":"number"},"actual":{"type":"number"}}},"ValidateResponse":{"type":"object","required":["allowed","reason","violations","checkedAt"],"properties":{"allowed":{"type":"boolean"},"reason":{"type":"string"},"violations":{"type":"array","items":{"$ref":"#/components/schemas/Violation"}},"ipsVersion":{"type":["integer","null"]},"snapshot":{"type":["object","null"],"properties":{"totalValue":{"type":"number"},"cryptoValue":{"type":"number"},"tradfiValue":{"type":"number"},"cashValue":{"type":"number"},"cryptoPct":{"type":"number"},"tradfiPct":{"type":"number"},"cashPct":{"type":"number"},"snapshotDate":{"type":["string","null"]},"currentDrawdownPct":{"type":"number"}}},"checkedAt":{"type":"string","format":"date-time"}}},"ProposeResponse":{"type":"object","properties":{"proposed":{"type":"boolean"},"decision":{"$ref":"#/components/schemas/ValidateResponse"},"serviceRequestId":{"type":"integer"},"message":{"type":"string"}}},"WhoamiResponse":{"type":"object","properties":{"userId":{"type":"string","format":"uuid"},"scopes":{"type":"array","items":{"type":"string","enum":["read","validate","propose"]}},"keyType":{"type":"string","enum":["live","test"]},"rateLimit":{"type":"object","description":"Current rate-limit budget — same numbers as the X-RateLimit-* headers, echoed in the body so agents can read them without parsing headers.","properties":{"perSecond":{"type":"integer","example":50},"remainingThisSecond":{"type":"integer","example":49},"resetSeconds":{"type":"integer","example":1}}},"productLine":{"type":"string"}}},"BatchValidateRequest":{"type":"object","required":["trades"],"properties":{"trades":{"type":"array","maxItems":25,"items":{"$ref":"#/components/schemas/TradePayload"}}}},"BatchValidateResponse":{"type":"object","properties":{"count":{"type":"integer"},"results":{"type":"array","items":{"type":"object","properties":{"index":{"type":"integer"},"payload":{"$ref":"#/components/schemas/TradePayload"},"decision":{"$ref":"#/components/schemas/ValidateResponse"}}}}}},"UsageResponse":{"type":"object","description":"Per-key call telemetry rolled up over `days`. Totals, daily series, top paths and keys, plus the last 10 raw calls.","properties":{"since":{"type":"string","format":"date-time"},"totals":{"type":"object","properties":{"total":{"type":"integer"},"errors":{"type":"integer"},"p50Latency":{"type":"integer"},"p95Latency":{"type":"integer"}}},"series":{"type":"array","items":{"type":"object","properties":{"day":{"type":"string","example":"2026-06-15"},"total":{"type":"integer"},"errors":{"type":"integer"}}}},"topPaths":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"count":{"type":"integer"}}}},"topKeys":{"type":"array","items":{"type":"object","properties":{"agentKeyId":{"type":"string","format":"uuid"},"count":{"type":"integer"}}}},"recent":{"type":"array","items":{"type":"object","properties":{"agentKeyId":{"type":"string","format":"uuid"},"path":{"type":"string"},"status":{"type":"integer"},"latencyMs":{"type":"integer"},"createdAt":{"type":"string","format":"date-time"}}}}}}}},"paths":{"/api/agent/v1/whoami":{"get":{"summary":"Identify the caller","description":"Returns the user this key acts for and the scopes it carries.","tags":["Read"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhoamiResponse"}}}},"401":{"description":"Missing or invalid bearer token"}}}},"/api/agent/v1/mandate":{"get":{"summary":"Read the active mandate","description":"Returns the user IPS as a structured policy bundle, including allocation bands, drawdown limit, and current utilization. Scope: `read`.","tags":["Read"],"responses":{"200":{"description":"OK"},"401":{"description":"Unauthorized"}}}},"/api/agent/v1/holdings":{"get":{"summary":"Read current holdings","description":"Portfolio snapshot across all custodians. Scope: `read`.","tags":["Read"],"responses":{"200":{"description":"OK"},"401":{"description":"Unauthorized"}}}},"/api/agent/v1/audit":{"get":{"summary":"Recent guardrail decisions","description":"Returns the most recent guardrail decisions on this user, from any actor. Scope: `read`.","tags":["Read"],"parameters":[{"in":"query","name":"limit","schema":{"type":"integer","minimum":1,"maximum":100,"default":25}}],"responses":{"200":{"description":"OK"},"401":{"description":"Unauthorized"}}}},"/api/agent/v1/policy/validate":{"post":{"summary":"Dry-run a trade against the IPS","description":"Validates a proposed trade against the user IPS without executing or creating an attestation request. The decision is audit-logged. Scope: `validate`.","tags":["Validate"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TradePayload"}}}},"responses":{"200":{"description":"Decision","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidateResponse"}}}},"401":{"description":"Unauthorized"}}}},"/api/agent/v1/policy/validate-batch":{"post":{"summary":"Dry-run up to 25 trades in one call","description":"Same decision logic as `/policy/validate`, evaluated sequentially so per-key daily caps (USD/day, calls/day) reflect what an agent would actually see if the calls were spaced out one at a time. Scope: `validate`.","tags":["Validate"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchValidateRequest"}}}},"responses":{"200":{"description":"Batch result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchValidateResponse"}}}},"401":{"description":"Unauthorized"}}}},"/api/agent/v1/policy/events":{"get":{"summary":"Signed policy-event chain (Phase 1)","description":"Append-only signed log of every event that shapes the policy enforced on your account: IPS changes, key mutations, guardrail decisions. Each row carries prevHash + hash + ed25519 signature so verifiers can prove the chain is intact without trusting AC. See https://app.advisorscrypto.com/agents/docs/concepts/verifiability. Scope: `read`.","tags":["Read"],"parameters":[{"in":"query","name":"limit","schema":{"type":"integer","minimum":1,"maximum":500,"default":100}},{"in":"query","name":"cursor","schema":{"type":"string"},"description":"Opaque cursor returned in the previous response as `nextCursor`. Use to paginate."},{"in":"query","name":"eventType","schema":{"type":"string","enum":["ips.created","ips.amended","key.minted","key.revoked","key.rotated","guardrail.decision","policy.drift_detected","policy.drift_cleared","broker.trade_observed","signing_key.rotated"]}},{"in":"query","name":"since","schema":{"type":"string","format":"date-time"}},{"in":"query","name":"order","schema":{"type":"string","enum":["asc","desc"],"default":"asc"}}],"responses":{"200":{"description":"OK"},"401":{"description":"Unauthorized"}}}},"/api/agent/v1/signing-keys":{"get":{"summary":"Public signer-key manifest","description":"Returns the public ed25519 keys verifiers use to check signatures on /policy/events. Public — no auth required.","tags":["Read"],"security":[],"responses":{"200":{"description":"OK"}}}},"/api/agent/v1/usage":{"get":{"summary":"Usage telemetry for the calling user","description":"Returns aggregates (totals + daily series + top paths/keys) plus the last 10 raw calls. Useful for agents that want to self-report their own footprint to a dashboard. Scope: `read`.","tags":["Read"],"parameters":[{"in":"query","name":"days","schema":{"type":"integer","minimum":1,"maximum":30,"default":7}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsageResponse"}}}},"401":{"description":"Unauthorized"}}}},"/api/agent/v1/trades/propose":{"post":{"summary":"Submit a trade for human attestation","description":"Validates first; if blocked, returns the violations and creates no request. If allowed, creates a `agent_proposed_trade` ServiceRequest the user resolves in the AC app. Scope: `propose`.\n\nPass `Idempotency-Key` to safely retry: a second request with the same key inside 24h returns the existing record instead of creating a duplicate.","tags":["Propose"],"parameters":[{"in":"header","name":"Idempotency-Key","required":false,"schema":{"type":"string","maxLength":128},"description":"Stable client-generated string. Same key within 24h returns the existing record."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/TradePayload"},{"type":"object","properties":{"rationale":{"type":"string","description":"Short note shown to the user at attestation time."}}}]}}}},"responses":{"200":{"description":"Result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProposeResponse"}}}},"401":{"description":"Unauthorized"}}}}}}