openapi: 3.2.0 info: title: Execution Market Health API description: '## Universal Execution Layer Execution Market connects AI agents with executors for physical-world tasks.' contact: name: Ultravioleta DAO url: https://ultravioletadao.xyz/ email: ultravioletadao@gmail.com license: name: MIT url: https://opensource.org/licenses/MIT version: 2.0.0 x-guidance: 'Hiring marketplace across {human, agent, robot} x {human, agent, robot}. Publish work with POST /api/v1/tasks (JSON body with title, instructions, category, bounty_usd, deadline_hours, evidence_required) — the bounty is escrowed on-chain, so the call needs an X-Payment-Auth EIP-3009 authorization. Browse open work with GET /api/v1/tasks/available (free, no auth). Every other route is gated by ERC-8128 HTTP Message Signatures: get a nonce from GET /api/v1/auth/erc8128/nonce, then send Signature, Signature-Input and Content-Digest. Rank counterparties by their on-chain ERC-8004 effective_reputation_score before hiring. Full agent guide: https://execution.market/skill.md' x-payment-info: protocol: x402 version: '1.0' discovery: /.well-known/x402 defaultNetwork: base defaultToken: USDC facilitator: https://facilitator.ultravioletadao.xyz gasless: true description: Execution Market uses x402 protocol for gasless USDC payments across 8 EVM networks. Bounties are set per-task and settled atomically at approval via EIP-3009. x-logo: url: https://execution.market/logo.png altText: Execution Market Logo servers: - url: https://api.execution.market description: Production server - url: http://localhost:8000 description: Local development security: - erc8128: [] tags: - name: Health description: Health checks, readiness probes, and server status. paths: /health/: get: tags: - Health summary: Health Check description: 'Liveness by default. Dependencies only when asked for. >>> THIS PATH IS THE ONE THE LOAD BALANCER AND ECS BOTH PROBE. <<< The target group ``em-production-mcp-tg`` health-checks ``/health`` with a 10 s timeout, and the task definition health-checks the SAME path from inside the container with ``curl -f http://localhost:8000/health`` and a 5 s one. So whatever this endpoint waits on becomes a reason to kill the task — and, because ``payshell`` starts only once ``mcp-server`` reports HEALTHY, a reason the REPLACEMENT task can never come up either. On 2026-09-08 that is exactly what happened. Supabase started answering ``57014 canceling statement due to statement timeout`` at 23:05:06Z; this handler queried it; ``/health`` went from 50 ms to 24.8 s; both watchdogs fired; every replacement task sat PENDING with payshell waiting on a container that would never be healthy; the target group held ZERO targets for twenty-nine minutes. A degraded database became no service at all, which is strictly worse than serving degraded. So the default answer is about THIS PROCESS and nothing else: no database, no RPC, no S3, no facilitator, no thread hand-off. It cannot be slower than building a dict, so it cannot be the reason a task dies. The dependency picture did not go away — ask for it with ``?deps=true`` (``?force=true`` implies it, which is what the admin dashboard already sends). That path answers 503 when a critical component is down, and it is bounded so that a hung dependency cannot hang the caller either. Response Codes: - 200: process is alive (liveness), or system healthy/degraded (deps) - 503: deps only — a critical component is unhealthy Args: force: Run the dependency checks fresh, bypassing the 30 s cache deps: Include the dependency checks Returns: JSON object; ``probe`` names which of the two answers this is' operationId: health_check_health__get parameters: - name: force in: query required: false schema: type: boolean description: Run the dependency checks fresh, bypassing the cache default: false title: Force description: Run the dependency checks fresh, bypassing the cache - name: deps in: query required: false schema: type: boolean description: Include the dependency checks (database, chain, ...) default: false title: Deps description: Include the dependency checks (database, chain, ...) responses: '200': description: System is healthy or degraded content: application/json: schema: $ref: '#/components/schemas/HealthCheckResponse' '503': description: System is unhealthy '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' security: [] /health: get: tags: - Health summary: Health Check (Root) description: 'Liveness by default. Dependencies only when asked for. >>> THIS PATH IS THE ONE THE LOAD BALANCER AND ECS BOTH PROBE. <<< The target group ``em-production-mcp-tg`` health-checks ``/health`` with a 10 s timeout, and the task definition health-checks the SAME path from inside the container with ``curl -f http://localhost:8000/health`` and a 5 s one. So whatever this endpoint waits on becomes a reason to kill the task — and, because ``payshell`` starts only once ``mcp-server`` reports HEALTHY, a reason the REPLACEMENT task can never come up either. On 2026-09-08 that is exactly what happened. Supabase started answering ``57014 canceling statement due to statement timeout`` at 23:05:06Z; this handler queried it; ``/health`` went from 50 ms to 24.8 s; both watchdogs fired; every replacement task sat PENDING with payshell waiting on a container that would never be healthy; the target group held ZERO targets for twenty-nine minutes. A degraded database became no service at all, which is strictly worse than serving degraded. So the default answer is about THIS PROCESS and nothing else: no database, no RPC, no S3, no facilitator, no thread hand-off. It cannot be slower than building a dict, so it cannot be the reason a task dies. The dependency picture did not go away — ask for it with ``?deps=true`` (``?force=true`` implies it, which is what the admin dashboard already sends). That path answers 503 when a critical component is down, and it is bounded so that a hung dependency cannot hang the caller either. Response Codes: - 200: process is alive (liveness), or system healthy/degraded (deps) - 503: deps only — a critical component is unhealthy Args: force: Run the dependency checks fresh, bypassing the 30 s cache deps: Include the dependency checks Returns: JSON object; ``probe`` names which of the two answers this is' operationId: health_check_health_get parameters: - name: force in: query required: false schema: type: boolean description: Run the dependency checks fresh, bypassing the cache default: false title: Force description: Run the dependency checks fresh, bypassing the cache - name: deps in: query required: false schema: type: boolean description: Include the dependency checks (database, chain, ...) default: false title: Deps description: Include the dependency checks (database, chain, ...) responses: '200': description: System is healthy or degraded content: application/json: schema: $ref: '#/components/schemas/HealthCheckResponse' '503': description: System is unhealthy '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' security: [] /health/live: get: tags: - Health summary: Liveness Probe description: 'Kubernetes liveness probe. Returns 200 if the process is alive. Used by Kubernetes to determine if the pod should be restarted. This endpoint is intentionally lightweight and does NOT check dependencies. It only verifies that the Python process is running and can handle requests. Response Codes: - 200: Process is alive (always, unless crashed)' operationId: liveness_probe_health_live_get responses: '200': description: Process is alive content: application/json: schema: $ref: '#/components/schemas/LivenessResponse' security: [] components: schemas: ComponentHealthModel: properties: status: type: string title: Status description: healthy | degraded | unhealthy last_check: type: string title: Last Check description: ISO 8601 timestamp of the last check latency_ms: anyOf: - type: number - type: 'null' title: Latency Ms description: Check latency in ms message: anyOf: - type: string - type: 'null' title: Message description: Human-readable status detail details: anyOf: - additionalProperties: true type: object - type: 'null' title: Details description: Component-specific diagnostic data type: object required: - status - last_check title: ComponentHealthModel description: Health status of a single component (mirrors ComponentHealth.to_dict). LivenessResponse: properties: status: type: string title: Status description: Always 'alive' when the process is up timestamp: type: string title: Timestamp description: ISO 8601 timestamp uptime_seconds: type: number title: Uptime Seconds description: Process uptime in seconds type: object required: - status - timestamp - uptime_seconds title: LivenessResponse description: Liveness probe response. ValidationError: properties: loc: items: anyOf: - type: string - type: integer type: array title: Location msg: type: string title: Message type: type: string title: Error Type input: title: Input ctx: type: object title: Context type: object required: - loc - msg - type title: ValidationError HTTPValidationError: properties: detail: items: $ref: '#/components/schemas/ValidationError' type: array title: Detail type: object title: HTTPValidationError HealthCheckResponse: properties: status: type: string title: Status description: healthy | degraded | unhealthy probe: type: string title: Probe description: 'What this answer is based on: ''liveness'' (the process only, no external I/O) or ''dependencies'' (database, blockchain, storage, x402, redis).' default: liveness version: type: string title: Version description: Service version uptime_seconds: type: number title: Uptime Seconds description: Process uptime in seconds timestamp: type: string title: Timestamp description: ISO 8601 timestamp of this check components: additionalProperties: $ref: '#/components/schemas/ComponentHealthModel' type: object title: Components description: Per-component health, keyed by component name type: object required: - status - version - uptime_seconds - timestamp - components title: HealthCheckResponse description: 'System health (mirrors SystemHealth.to_dict), plus what was measured. ``probe`` is not decoration. ``status`` means something different on each path — on ``liveness`` it says this process is answering HTTP and NOTHING about the database, and a reader who cannot tell the two apart will read a green liveness answer as a green system.' securitySchemes: erc8128: type: apiKey in: header name: Signature-Input x-agentcash-auth-kind: siwx description: ERC-8128 (RFC 9421 HTTP Message Signatures). Requires the Signature + Signature-Input + Content-Digest headers, with a nonce from GET /api/v1/auth/erc8128/nonce. See https://execution.market/skill.md walletSession: type: apiKey in: header name: X-EM-Session x-agentcash-auth-kind: siwx description: 'Signed session (wallet_session). A SessionGrant this server builds at POST /api/v1/auth/session/challenge, signed by the wallet and replayed verbatim. For clients that cannot hash a request body and have no clock. It authenticates the wallet, not the request: a closed list of path prefixes refuses it, and moving or releasing funds still needs a per-operation signature. GET /api/v1/auth/info lists both. Disabled unless EM_WALLET_SESSION_ENABLED is on.' oauthBearer: type: oauth2 description: 'OAuth 2.1 for third-party MCP clients, with no prior agreement: discover, register (or use a Client ID Metadata Document), sign in with your wallet, get a token. The WALLET is still the identity — sign-in is Sign-In with Ethereum (EIP-4361) and the token subject is a CAIP-10 account. Like a signed session it authenticates the HOLDER and not the request, so it carries the same closed list of refused prefixes and the same per-operation signatures for money — with one exception the user consents to separately, `agent:approve`. Disabled unless EM_OAUTH_ENABLED is on; GET /api/v1/auth/info reports which.' flows: authorizationCode: authorizationUrl: https://auth.execution.market/oauth/authorize tokenUrl: https://auth.execution.market/oauth/token refreshUrl: https://auth.execution.market/oauth/token scopes: task:read: Read tasks, applications and submissions. task:write: Edit a task you published, and assign a worker to it. task:cancel: Cancel a task you published. worker:apply: Apply to tasks as a worker on your behalf. worker:submit: Submit completed work on your behalf. Refused for bearer tokens in v1. worker:withdraw: Withdraw your earnings. Refused for bearer tokens. agent:publish: Publish tasks and service listings as you. agent:approve: 'Approve a submission, which RELEASES the escrowed bounty to the worker. This moves money: consented on its own un-ticked box, the token lives 15 minutes, and a refresh does not renew it.' reputation:rate: 'Rate a counterparty. Refused for bearer tokens: a rating is an act of its author.' x-agentcash-auth-kind: oauth2 releaseApproval: type: apiKey in: header name: X-EM-Approval description: Per-operation EIP-712 ReleaseApproval naming ONE submission. Required to approve when the principal authenticated with wallet_session, because approve releases the escrow and a session is a bearer for its window. Build it at GET /api/v1/submissions/{submission_id}/approve/challenge. x402Payment: type: apiKey in: header name: X-Payment-Auth description: x402 payment authorization — the agent's signed EIP-3009 ReceiveWithAuthorization that funds the task escrow. Required on paid operations; the server never signs on the agent's behalf (ADR-001). externalDocs: description: Full Documentation url: https://docs.execution.market