{
  "openapi": "3.1.0",
  "info": {
    "title": "HumanDispatch API",
    "description": "The API for real-world marketing execution. AI agents can request structured outcomes — product shoots, UGC, events, retail audits — through clean endpoints.",
    "version": "0.1.0",
    "contact": {
      "email": "api@humandispatch.io"
    },
    "license": {
      "name": "MIT"
    }
  },
  "servers": [
    {
      "url": "https://api.humandispatch.io",
      "description": "Production"
    },
    {
      "url": "http://localhost:4100",
      "description": "Local development"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "paths": {
    "/v1/templates": {
      "get": {
        "operationId": "listTemplates",
        "summary": "List all task templates",
        "description": "Returns all available task templates with their schemas, validation rules, and acceptance tests. Call this first to discover what task types are available.",
        "tags": ["Templates"],
        "responses": {
          "200": {
            "description": "List of templates",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "templates": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/TemplateSummary" }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/templates/{taskType}": {
      "get": {
        "operationId": "getTemplate",
        "summary": "Get template details",
        "description": "Returns full template details including task_spec JSON Schema, validation rules, acceptance tests, and a sample payload.",
        "tags": ["Templates"],
        "parameters": [
          {
            "name": "taskType",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": ["PRODUCT_PHOTO_ECOMM", "UGC_VERTICAL_BATCH", "EVENT_PHOTO_COVERAGE", "EVENT_HIGHLIGHT_VIDEO", "ON_SITE_STORY_PACK", "RETAIL_AUDIT_PHOTO_CHECK", "OOH_ASSET_VERIFICATION", "STREET_TEAM_DISTRIBUTION", "POPUP_EVENT_STAFFING", "PODCAST_ON_SITE_CAPTURE"]
            }
          }
        ],
        "responses": {
          "200": { "description": "Template details" },
          "404": { "description": "Template not found" }
        }
      }
    },
    "/v1/tasks": {
      "post": {
        "operationId": "createTask",
        "summary": "Create a task",
        "description": "Create a task from a template. Always fetch the template schema first and validate required fields before calling this endpoint.",
        "tags": ["Tasks"],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": { "type": "string" }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/TaskCreate" }
            }
          }
        },
        "responses": {
          "201": { "description": "Task created" },
          "400": { "description": "Validation failed" },
          "403": { "description": "Policy blocked" }
        }
      },
      "get": {
        "operationId": "listTasks",
        "summary": "List tasks",
        "tags": ["Tasks"],
        "parameters": [
          { "name": "status", "in": "query", "schema": { "type": "string" } },
          { "name": "task_type", "in": "query", "schema": { "type": "string" } },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 50 } },
          { "name": "offset", "in": "query", "schema": { "type": "integer", "default": 0 } }
        ],
        "responses": {
          "200": { "description": "List of tasks" }
        }
      }
    },
    "/v1/tasks/{taskId}": {
      "get": {
        "operationId": "getTask",
        "summary": "Get task details",
        "tags": ["Tasks"],
        "parameters": [
          { "name": "taskId", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": { "description": "Task details" },
          "404": { "description": "Not found" }
        }
      }
    },
    "/v1/tasks/{taskId}/quote_requests": {
      "post": {
        "operationId": "requestQuotes",
        "summary": "Request quotes from talent",
        "tags": ["Quotes"],
        "parameters": [
          { "name": "taskId", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": { "description": "Quote requests sent" }
        }
      }
    },
    "/v1/tasks/{taskId}/quotes": {
      "get": {
        "operationId": "getQuotes",
        "summary": "List quotes for a task",
        "tags": ["Quotes"],
        "parameters": [
          { "name": "taskId", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": { "description": "List of quotes" }
        }
      }
    },
    "/v1/quotes/{quoteId}/accept": {
      "post": {
        "operationId": "acceptQuote",
        "summary": "Accept a quote",
        "tags": ["Quotes"],
        "parameters": [
          { "name": "quoteId", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": { "description": "Quote accepted" }
        }
      }
    },
    "/v1/bookings": {
      "post": {
        "operationId": "createBooking",
        "summary": "Create booking with escrow hold",
        "description": "Creates a booking from an accepted quote. Escrow funds are held via Stripe until work is accepted.",
        "tags": ["Bookings"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["quote_id"],
                "properties": {
                  "quote_id": { "type": "string" }
                }
              }
            }
          }
        },
        "responses": {
          "201": { "description": "Booking created with escrow hold" }
        }
      }
    },
    "/v1/bookings/{bookingId}/checkin": {
      "post": {
        "operationId": "checkin",
        "summary": "Record talent check-in",
        "tags": ["Bookings"],
        "parameters": [
          { "name": "bookingId", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": { "description": "Check-in recorded" }
        }
      }
    },
    "/v1/work_orders/{workOrderId}/deliverables": {
      "post": {
        "operationId": "registerDeliverables",
        "summary": "Register deliverables",
        "tags": ["Work Orders"],
        "parameters": [
          { "name": "workOrderId", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "201": { "description": "Deliverables registered" }
        }
      }
    },
    "/v1/work_orders/{workOrderId}/qa": {
      "get": {
        "operationId": "runQA",
        "summary": "Run QA checks on deliverables",
        "description": "Runs automated acceptance tests (count, resolution, orientation, timestamps, duplicates) and returns pass/fail results.",
        "tags": ["Work Orders"],
        "parameters": [
          { "name": "workOrderId", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": { "description": "QA results" }
        }
      }
    },
    "/v1/work_orders/{workOrderId}/accept": {
      "post": {
        "operationId": "acceptWork",
        "summary": "Accept work and release payout",
        "description": "Accepts the work order, captures the escrow, and initiates payout to talent.",
        "tags": ["Work Orders"],
        "parameters": [
          { "name": "workOrderId", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": { "description": "Work accepted, payout initiated" }
        }
      }
    },
    "/v1/talent/search": {
      "get": {
        "operationId": "searchTalent",
        "summary": "Search talent",
        "tags": ["Talent"],
        "parameters": [
          { "name": "task_type", "in": "query", "schema": { "type": "string" } },
          { "name": "location", "in": "query", "schema": { "type": "string" } },
          { "name": "skills", "in": "query", "schema": { "type": "string" }, "description": "Comma-separated skills" },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 10 } }
        ],
        "responses": {
          "200": { "description": "Talent search results" }
        }
      }
    },
    "/v1/files/presign": {
      "post": {
        "operationId": "presignUpload",
        "summary": "Get presigned upload URL",
        "tags": ["Files"],
        "responses": {
          "200": { "description": "Presigned URL for file upload" }
        }
      }
    },
    "/v1/webhooks": {
      "post": {
        "operationId": "registerWebhook",
        "summary": "Register a webhook endpoint",
        "tags": ["Webhooks"],
        "responses": {
          "201": { "description": "Webhook registered" }
        }
      },
      "get": {
        "operationId": "listWebhooks",
        "summary": "List webhook endpoints",
        "tags": ["Webhooks"],
        "responses": {
          "200": { "description": "List of webhooks" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key in Bearer token format"
      }
    },
    "schemas": {
      "TemplateSummary": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "version": { "type": "string" },
          "name": { "type": "string" },
          "description": { "type": "string" },
          "risk_tier": { "type": "string", "enum": ["low", "medium", "high"] },
          "requires_onsite": { "type": "boolean" }
        }
      },
      "TaskCreate": {
        "type": "object",
        "required": ["task_type", "title", "location", "schedule", "budget", "deliverables"],
        "properties": {
          "task_type": { "type": "string" },
          "title": { "type": "string", "minLength": 8, "maxLength": 120 },
          "brief": { "type": "string", "maxLength": 4000 },
          "location": {
            "type": "object",
            "required": ["mode"],
            "properties": {
              "mode": { "type": "string", "enum": ["onsite", "remote", "hybrid"] },
              "address": { "type": "string" }
            }
          },
          "schedule": {
            "type": "object",
            "required": ["start_at"],
            "properties": {
              "start_at": { "type": "string", "format": "date-time" },
              "duration_hours": { "type": "number" },
              "timezone": { "type": "string" }
            }
          },
          "budget": {
            "type": "object",
            "required": ["currency", "pricing_model", "max_total"],
            "properties": {
              "currency": { "type": "string", "enum": ["CAD", "USD"] },
              "pricing_model": { "type": "string", "enum": ["fixed", "hourly"] },
              "max_total": { "type": "integer", "minimum": 50, "maximum": 500000 }
            }
          },
          "deliverables": {
            "type": "array",
            "items": {
              "type": "object",
              "required": ["kind", "count"],
              "properties": {
                "kind": { "type": "string", "enum": ["photo", "video", "audio", "text"] },
                "count": { "type": "integer" },
                "format": { "type": "string" },
                "due_at": { "type": "string", "format": "date-time" }
              }
            }
          },
          "task_spec": { "type": "object", "description": "Template-specific fields — see GET /v1/templates/{taskType} for schema" }
        }
      },
      "ApiError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "http_status": { "type": "integer" },
              "code": { "type": "string" },
              "message": { "type": "string" },
              "details": { "type": "object" },
              "request_id": { "type": "string" }
            }
          }
        }
      }
    }
  }
}
