openapi: 3.2.0 info: title: ForceDream API (SDK-verified surface) Workforce 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: Workforce paths: /v1/workforce/proof/public-key: get: operationId: getProofPublicKey summary: Fetch the real Ed25519 public key used to sign all proofs description: Keyless. Required to verify any proof's signature. responses: '200': description: Public key content: application/json: schema: type: object properties: public_key_pem: type: string key_id: type: string tags: - Workforce /v1/workforce/proof/{task_id}/public: get: operationId: getProof summary: Fetch a real, signed proof for a completed task description: 'Keyless. Returns the real proof object needed for client-side Ed25519 verification. **Honest gap**: the exact behavior for a task_id that has not yet completed (404, a 200 with a null proof, or otherwise) is not directly confirmed by tonight''s testing -- every real verification tested used an already-completed task. Documented as `200` below for the confirmed case only; do not assume the shape of an unconfirmed case.' parameters: - name: task_id in: path required: true schema: type: string responses: '200': description: Proof found (confirmed only for already-completed tasks) content: application/json: schema: type: object properties: proof: $ref: '#/components/schemas/FdProof' tags: - Workforce components: schemas: FdProof: type: object description: The exact fields signed and verified. See x-forcedream-canonicalization for the precise reduced "signable" object this maps to -- it is not simply this whole object. required: - task_id - agent_id - input_hash - output_hash - cost_pence - budget_pence - started_at - completed_at properties: task_id: type: string agent_id: type: string input_hash: type: string output_hash: type: string cost_pence: description: Real-world value is numeric but may arrive as a JSON string; coerce with Number(x) semantics before canonicalizing. oneOf: - type: number - type: string budget_pence: oneOf: - type: number - type: string external_cost_hash: type: string nullable: true description: When present, the signable includes 10 fields instead of 8. retrieved_count: oneOf: - type: number - type: string nullable: true started_at: oneOf: - type: number - type: string completed_at: description: Coerced to a string exactly as JS's String(x) would, not left as a raw number. oneOf: - type: number - type: string algorithm: type: string example: Ed25519 signature: type: string description: Base64-encoded Ed25519 signature over the SHA-256 digest of the canonical signable string. key_id: type: string worm_seal: type: string proof_id: type: string 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.'