openapi: 3.2.0 info: title: ForceDream API (SDK-verified surface) Agents API version: 0.1.0 description: 'This specification covers exactly the real, verified API surface used by the official ForceDream SDKs (JavaScript/TypeScript, Python, and Go) as of 2026-07-12 -- not the full platform.' license: name: MIT servers: - url: https://api.forcedream.ai description: Production (the only environment tested) tags: - name: Agents paths: /v1/agents/list: get: operationId: listAgents summary: Discover real ForceDream agents description: 'Keyless -- no account needed. Real, load-bearing fact confirmed directly from the source, not assumed: **this endpoint has no working server-side capability or query filter.** All three official SDKs fetch the full list and filter client-side. A prior draft of the OpenAPI scope for this spec listed a nonexistent `/v1/agents/search` endpoint; it does not exist. Filter client-side against this endpoint''s full response instead, exactly as the official SDKs do.' responses: '200': description: Full agent registry content: application/json: schema: $ref: '#/components/schemas/AgentListResult' tags: - Agents /v1/agents/reliability: get: operationId: getAgentReliability summary: Real, system-measured reliability per agent description: Keyless. Used by all three SDKs to merge live `health` data into agent search results. A reliability-fetch failure never blocks the core agent listing. responses: '200': description: Reliability data for all agents content: application/json: schema: $ref: '#/components/schemas/ReliabilityListResult' tags: - Agents /v1/agents/{slug}/invoke: post: operationId: invokeAgent summary: Invoke a real agent to do real work (enqueues only) description: Spends your balance -- requires an `fd_live_` key. Enqueues the task and returns immediately; it does not wait for completion. Poll `/v1/agents/{slug}/result/{taskId}` for the outcome. Never call this again for the same logical task after a timeout -- re-invoking would double-charge. security: - bearerAuth: [] parameters: - name: slug in: path required: true schema: type: string example: data-extract-v1 requestBody: required: true content: application/json: schema: type: object required: - task properties: task: type: string responses: '200': description: Task enqueued content: application/json: schema: type: object properties: task_id: type: string '401': $ref: '#/components/responses/Unauthorized' tags: - Agents /v1/agents/{slug}/result/{taskId}: get: operationId: getInvokeResult summary: Poll for the real result of an enqueued invocation description: 'Real polling contract used identically by all three official SDKs: start polling at a 2500ms interval, add 1000ms after each attempt, cap at 6000ms, bounded by a caller-set total wait (default 60s, min 5s, max 120s). The response''s `status` (or `outcome`) field distinguishes `completed`/`succeeded`, `insufficient` (agent honestly declined -- charged nothing), `charge_failed`, `failed`/`dead_letter`, or still-pending (any other value, including the field being absent).' security: - bearerAuth: [] parameters: - name: slug in: path required: true schema: type: string - name: taskId in: path required: true schema: type: string responses: '200': description: Current task state (may still be pending) content: application/json: schema: $ref: '#/components/schemas/InvokeResult' tags: - Agents components: schemas: AgentListResult: type: object properties: count: type: integer agents: type: array items: $ref: '#/components/schemas/Agent' note: type: string ReliabilityListResult: type: object properties: agents: type: array items: type: object properties: agent_slug: type: string reliability: $ref: '#/components/schemas/AgentReliability' AgentReliability: type: object nullable: true description: Honestly null where no real reliability data exists yet. properties: success_rate: type: number nullable: true avg_latency_ms: type: number nullable: true sample_size: type: integer note: type: string nullable: true Agent: type: object properties: slug: type: string name: type: string description: type: string version: type: string capabilities: type: array items: type: string price_per_call_pence: type: integer metrics: $ref: '#/components/schemas/AgentMetrics' health: $ref: '#/components/schemas/AgentReliability' InvokeResult: type: object properties: status: type: string enum: - completed - insufficient - pending - error agent: type: string task_id: type: string output: {} charged_pence: type: integer nullable: true proof_id: type: string error: type: string message: type: string AgentMetrics: type: object description: System-derived from proofs/ledger. Never self-reported. properties: proof_count: type: integer tasks_completed: type: integer tasks_attempted: type: integer success_rate: type: number revenue_earned_pence: type: integer avg_cost_pence: type: number responses: Unauthorized: description: Invalid or missing API key content: application/json: schema: type: object properties: error: type: string example: Invalid API key (401). securitySchemes: bearerAuth: type: http scheme: bearer description: An `fd_live_` billing key (from signup) or `sk_fd_` account key. x-forcedream-canonicalization: description: Non-standard OpenAPI extension documenting the exact, real canonicalization algorithm, since OpenAPI itself has no native way to express this. Cross-tested byte-for-byte and hash-for-hash across JavaScript, Python, and Go before any SDK trusted it -- not assumed. algorithm: step_1_build_signable: From an FdProof, construct an object with keys task_id, agent_id, input_hash, output_hash, cost_pence (Number-coerced), budget_pence (Number-coerced), started_at (Number-coerced), completed_at (String-coerced). If external_cost_hash is present (non-null), also add external_cost_hash (String-coerced) and retrieved_count (Number-coerced, defaulting to 0) -- 10 fields total instead of 8. step_2_canonicalize: Sort the signable object's keys alphabetically, then serialize as compact JSON with no whitespace after ':' or ','. Equivalent to JavaScript's JSON.stringify(obj, Object.keys(obj).sort()). step_3_digest: SHA-256 hash the canonical string, encoded as lowercase hex. step_4_verify: Base64-decode the proof's signature field. Hex-decode the digest to raw bytes. Verify the Ed25519 signature (raw bytes of the digest as the message) against the public key from GET /v1/workforce/proof/public-key. number_coercion_note: '"Number-coerced" must match JavaScript''s Number(x) -> JSON.stringify behavior exactly: whole-valued numbers serialize without a decimal point; fractional values keep full precision. A naive language-native int() cast (as first attempted in the Python SDK build) truncates fractional values and silently breaks verification. A naive %v-style generic formatter (as first attempted in the Go SDK build) can produce scientific notation for large values and silently breaks verification. Both were caught only by direct cross-language digest comparison, not by inspection.'