openapi: 3.1.0
info:
  title: STEADYWRK Dispatch API
  version: "1.0.0"
  summary: The field-service dispatch capability of the STEADYWRK foundry and Studio — public evals, trade index, quote, and order.
  description: >-
    The dispatch capability of STEADYWRK — a foundry and Studio built in Aqaba,
    Jordan. This document describes callable API behavior. The evals
    and trade-index reads are public and require no key, as are
    POST /api/dispatch/v1/estimate and POST /api/agentic-commerce/probe. Quote
    accepts a provisioned integration key or a scoped self-serve OAuth token.
    Order creation requires a provisioned integration key in x-api-key;
    self-serve tokens receive HTTP 402. Quote accepts the provisioned key in
    x-api-key or a scoped OAuth token in Authorization: Bearer.
    The evaluation document includes per-field provenance and methodology;
    it is self-reported and does not establish an independent audit or SLA.
    MCP tools are exposed at POST /api/mcp. The separate A2A skill catalog is
    available at https://steadywrk.app/a2a.
  contact:
    name: STEADYWRK
    email: hello@steadywrk.app
    url: https://steadywrk.app/build
  license:
    name: Proprietary — see terms
    url: https://steadywrk.app/terms
servers:
  - url: https://steadywrk.app
    description: Production
externalDocs:
  description: Agent quickstart — wire the keyless evals tool into your agent
  url: https://steadywrk.app/build
tags:
  - name: dispatch-public
    description: Public, no-auth reads. Safe to call with zero setup.
  - name: dispatch-write
    description: Key-gated dispatch writes (ApiKeyAuth via x-api-key).
  - name: agent-endpoints
    description: JSON-RPC 2.0 transports for MCP tools and A2A skills.
paths:
  /api/dispatch/index:
    get:
      operationId: getDispatchIndex
      tags: [dispatch-public]
      summary: Trade-rate catalog (public)
      description: >-
        Catalog of the 8 dispatch trade verticals. Per trade the payload carries
        the BLS OES hourly WAGE (medianRateDollars / p25RateDollars /
        p75RateDollars — what a technician earns, NOT a price) alongside
        billedHourlyRateRoutineDollars, what a buyer pays per hour at routine
        urgency. The wage_vs_billed block names which field is which. No API key
        required.
      security: []
      responses:
        "200":
          description: Trade catalog.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/IndexResponse"
  /api/dispatch/analytics/evals:
    get:
      operationId: getDispatchEvals
      tags: [dispatch-public]
      summary: Rolling dispatch evals (public)
      description: >-
        Public evaluation document for the selected rolling window. Consult
        provenance.by_field and methodology on the returned document for
        definitions, source classes, and limitations. The top-level estimated
        flag applies to goal_fulfillment, not every field. No API key required.
        This self-reported document is not an independent audit or an SLA.
      security: []
      parameters:
        - name: period
          in: query
          required: false
          description: Rolling window. Defaults to rolling_30d.
          schema:
            type: string
            enum: [rolling_7d, rolling_30d, rolling_90d]
            default: rolling_30d
      responses:
        "200":
          description: Evals snapshot.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EvalsResponse"
  /api/dispatch/v1/quote:
    post:
      operationId: createDispatchQuote
      tags: [dispatch-write]
      summary: Instant dispatch quote
      description: >-
        Request a field-service quote by trade, location, and urgency. Returns
        availability and quote details when available. Accepts a provisioned
        integration key through x-api-key or a scoped self-serve OAuth token
        through Authorization: Bearer.
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/QuoteRequest"
      responses:
        "200":
          description: Quote result; inspect available before using quote details.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/QuoteResponse"
        "401":
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: Validation failed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ValidationError"
        "429":
          description: Rate limit exceeded (20 req/min per IP).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "503":
          description: Dispatch API not configured on the server.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /api/dispatch/v1/order:
    post:
      operationId: createDispatchOrder
      tags: [dispatch-write]
      summary: Create a dispatch work order
      description: >-
        Create a field-service order and return its status URL. HTTP 201 returns
        a created order; HTTP 202 returns an order accepted_pending_fulfilment
        with fulfilled=false. Requires a provisioned integration key through
        x-api-key. A self-serve OAuth token authenticates
        but is not entitled to create orders and receives HTTP 402.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/OrderRequest"
      responses:
        "201":
          description: Order created. Read its returned status and tracking URL.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/OrderResponse"
                  - properties:
                      fulfilled: { const: true }
        "402":
          description: Credential authenticated but is not provisioned for order creation.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrderEntitlementError"
        "202":
          description: Order accepted; fulfilment is pending.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/OrderResponse"
                  - required: [fulfilment]
                    properties:
                      fulfilled: { const: false }
                      order:
                        properties:
                          status: { const: accepted_pending_fulfilment }
                      fulfilment:
                        properties:
                          assigned: { const: false }
        "401":
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: Validation failed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ValidationError"
        "429":
          description: Rate limit exceeded (10 req/min per IP).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "503":
          description: Dispatch API not configured on the server.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /api/dispatch/v1/estimate:
    post:
      operationId: createDispatchEstimate
      tags: [dispatch-public]
      summary: Keyless billed-cost estimate (public rate card)
      description: >-
        Public modeled pricing using the same disclosed assumptions as MCP
        dispatch.estimate. No API key required. Request a quote through
        POST /api/dispatch/v1/quote with an accepted credential.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/EstimateRequest"
      responses:
        "200":
          description: Billed estimate with wage_basis and billed_estimate.
          content:
            application/json:
              schema:
                type: object
        "400":
          description: Invalid JSON body.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: Unknown trade or validation failed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ValidationError"
        "429":
          description: Rate limit exceeded (40 req/min per IP).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /api/agentic-commerce/probe:
    post:
      operationId: agenticCommerceProbe
      tags: [dispatch-public]
      summary: Free storefront readiness snapshot
      description: >-
        Multi-surface snapshot (UCP/ACP discovery, product schema, robots,
        answer-engine maps). Not an attested Store Guard grade. Rate-limited.
        No API key required. Maps to MCP agentic.readiness_probe.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ProbeRequest"
      responses:
        "200":
          description: Probe report with grade, score, and checks.
          content:
            application/json:
              schema:
                type: object
        "400":
          description: Invalid URL.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "429":
          description: Rate limit exceeded.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /api/mcp:
    post:
      operationId: mcpJsonRpc
      tags: [agent-endpoints]
      summary: MCP Streamable-HTTP endpoint (JSON-RPC 2.0)
      description: >-
        Stateless JSON-RPC 2.0 transport. Methods: initialize, tools/list,
        tools/call (agentic.readiness_probe, dispatch.estimate, dispatch.quote,
        dispatch.order, dispatch.evals, dispatch.index), ping. GET returns 405.
        Authentication and entitlement apply per tool. dispatch.quote accepts
        an integration key or a scoped OAuth token. dispatch.order requires a
        provisioned integration key; a self-serve token receives HTTP 402 and
        JSON-RPC error -32002. Missing credentials receive HTTP 401 and -32001.
        See https://steadywrk.app/mcp.json for the
        discovery manifest.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/JsonRpcRequest"
      responses:
        "200":
          description: JSON-RPC response.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/JsonRpcResponse"
        "401":
          description: Missing or invalid credential for a protected tool.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/JsonRpcResponse"
        "402":
          description: Credential authenticated but is not entitled to create orders.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/JsonRpcResponse"
  /api/a2a:
    post:
      operationId: a2aJsonRpc
      tags: [agent-endpoints]
      summary: A2A endpoint (JSON-RPC 2.0, message/send)
      description: >-
        Agent2Agent transport (protocol 0.3.0). Send a message with a Data part
        {"skill":"dispatch.evals","input":{...}} to invoke a skill. The agent
        card lives at https://steadywrk.app/.well-known/agent-card.json.
        dispatch.quote and dispatch.order require a provisioned integration
        key. talent.apply, talent.salaries, and talent.employer-inquiry are
        submissions requiring the A2A submission key. talent.salaries writes
        salary information; it does not query compensation data. Credentials
        use x-api-key or Authorization: Bearer. Authentication errors are
        JSON-RPC errors within HTTP 200 responses.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/JsonRpcRequest"
      responses:
        "200":
          description: JSON-RPC response carrying a Task.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/JsonRpcResponse"
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key
      description: Provisioned integration key. Required for direct REST order creation; also accepted for quoting.
    BearerAuth:
      type: http
      scheme: bearer
      description: Scoped self-serve OAuth token for quoting. These tokens receive HTTP 402 on order creation.
  schemas:
    Location:
      type: object
      required: [state]
      properties:
        state:
          type: string
          minLength: 2
          maxLength: 2
          description: Two-letter US state code (e.g. AZ, TX).
        zip:
          type: string
          maxLength: 10
        city:
          type: string
          maxLength: 255
    OrderLocation:
      allOf:
        - $ref: "#/components/schemas/Location"
        - type: object
          properties:
            address:
              type: string
              maxLength: 1000
            name:
              type: string
              maxLength: 500
    Urgency:
      type: string
      enum: [emergency, urgent, routine, scheduled]
      default: routine
    QuoteRequest:
      type: object
      required: [trade, location]
      properties:
        trade:
          type: string
          minLength: 1
          description: Trade code (plumbing, electrical, HVAC, etc.).
        location:
          $ref: "#/components/schemas/Location"
        urgency:
          $ref: "#/components/schemas/Urgency"
        nte_cents:
          type: integer
          minimum: 1
          description: Not-to-exceed ceiling, in cents.
        description:
          type: string
          maxLength: 2000
      example:
        trade: plumbing
        location:
          state: AZ
          zip: "85001"
        urgency: routine
    EstimateRequest:
      type: object
      required: [trade, location]
      properties:
        trade:
          type: string
          minLength: 1
          description: Trade code (plumbing, electrical, HVAC, etc.).
        location:
          $ref: "#/components/schemas/Location"
        urgency:
          $ref: "#/components/schemas/Urgency"
      example:
        trade: hvac
        location:
          state: TX
        urgency: routine
    ProbeRequest:
      type: object
      required: [url]
      properties:
        url:
          type: string
          maxLength: 255
          description: Public storefront URL or domain.
    QuoteResponse:
      type: object
      properties:
        available:
          type: boolean
        quote:
          type: object
          properties:
            trade:
              type: string
            location:
              $ref: "#/components/schemas/Location"
            urgency:
              $ref: "#/components/schemas/Urgency"
            estimated_eta:
              type: string
              description: ETA band derived from urgency (e.g. "same day").
            best_match:
              type: object
              properties:
                estimated_cost_cents:
                  type: integer
                estimated_cost_usd:
                  type: string
                estimated_response_minutes:
                  type: integer
                match_score:
                  type: number
            alternates_count:
              type: integer
            generated_at:
              type: string
              format: date-time
            valid_for_minutes:
              type: integer
        message:
          type: string
          description: Present when available is false.
    OrderRequest:
      type: object
      required: [trade, location, description]
      properties:
        trade:
          type: string
          minLength: 1
        location:
          $ref: "#/components/schemas/OrderLocation"
        urgency:
          $ref: "#/components/schemas/Urgency"
        nte_cents:
          type: integer
          minimum: 1
        description:
          type: string
          minLength: 1
          maxLength: 5000
        callback_url:
          type: string
          format: uri
        reference_id:
          type: string
          maxLength: 255
      example:
        trade: electrical
        location:
          state: AZ
          city: Phoenix
        urgency: urgent
        description: Breaker panel fault at a commercial site.
    OrderResponse:
      type: object
      required: [created, accepted, fulfilled, order, tracking]
      properties:
        created:
          type: boolean
          const: true
        accepted:
          type: boolean
          const: true
        fulfilled:
          type: boolean
        order:
          type: object
          required: [id, status, trade, location, urgency, quoted_cost_cents, assigned_contractor_ref, estimated_response_minutes, created_at]
          properties:
            id:
              type: string
            status:
              type: string
              enum: [pending_dispatch, accepted_pending_fulfilment]
            trade:
              type: string
            location:
              $ref: "#/components/schemas/OrderLocation"
            urgency:
              $ref: "#/components/schemas/Urgency"
            quoted_cost_cents:
              type: [integer, "null"]
            quoted_cost_usd:
              type: [string, "null"]
            assigned_contractor_ref:
              type: [string, "null"]
              description: Opaque response reference, or null while fulfilment is pending.
            estimated_response_minutes:
              type: [integer, "null"]
            created_at:
              type: string
              format: date-time
        fulfilment:
          type: object
          description: Present when fulfilment is pending.
          required: [assigned, reason, detail, escalation_contact]
          properties:
            assigned:
              type: boolean
              const: false
            reason:
              type: string
            detail:
              type: string
            escalation_contact:
              type: string
        tracking:
          type: object
          required: [status_url]
          properties:
            status_url:
              type: string
              format: uri
            auth:
              type: string
    OrderEntitlementError:
      type: object
      required: [error, message, entitled_credential, provisioning_contact, free_alternatives]
      properties:
        error:
          type: string
          const: payment_required
        message:
          type: string
        entitled_credential:
          type: string
        provisioning_contact:
          type: string
        free_alternatives:
          type: array
          items: { type: string }
    IndexResponse:
      type: object
      properties:
        trades:
          type: array
          items:
            $ref: "#/components/schemas/TradeRate"
        wage_vs_billed:
          type: object
          description: >-
            Names explicitly which fields are wages and which are billed prices,
            and emits the transform constants so a caller can re-derive the
            billed rate or substitute its own assumptions.
          properties:
            warning:
              type: string
            wage_fields:
              type: array
              items: { type: string }
            billed_fields:
              type: array
              items: { type: string }
            labor_burden_multiplier:
              type: number
            overhead_margin_markup:
              type: number
            formula:
              type: string
            urgency_multipliers:
              type: object
              additionalProperties: { type: number }
            full_estimate_tool:
              type: string
        note:
          type: string
        model_assumptions:
          type: string
        updated_at:
          type: string
          format: date-time
          description: >-
            BLS OES source release date, not a freshness signal. Use
            rate_model_version to detect pricing-model changes.
        source_period:
          type: string
        rate_model_version:
          type: string
    TradeRate:
      type: object
      properties:
        trade:
          type: string
        tradeLabel:
          type: string
        blsOccCode:
          type: string
        metro:
          type: string
        state:
          type: string
        urgency:
          type: string
        kind:
          type: string
          enum: [hourly_wage]
          description: What the *RateDollars fields on this row actually are.
        medianRateDollars:
          type: number
          description: >-
            Hourly WAGE a technician earns (BLS OES), NOT a price. Retained under
            its original name for wire compatibility; medianWageDollars is the
            unambiguous alias for the same value.
        p25RateDollars:
          type: number
          description: Hourly wage, 25th percentile. Not a price.
        p75RateDollars:
          type: number
          description: Hourly wage, 75th percentile. Not a price.
        medianWageDollars:
          type: number
        p25WageDollars:
          type: number
        p75WageDollars:
          type: number
        billedHourlyRateRoutineCents:
          type: integer
          description: >-
            What a buyer pays per hour at routine urgency, from the same
            wage->billed transform dispatch.estimate uses.
        billedHourlyRateRoutineDollars:
          type: number
        sampleSize:
          type: integer
        period:
          type: string
    EvalsResponse:
      type: object
      description: >-
        Schema of the public evaluation document. The estimated flag applies
        to goal_fulfillment. Read provenance.by_field and methodology for
        each field's definition, source class, and limitations. No evaluation
        values are reproduced in this specification.
      externalDocs:
        description: Public evaluation document with provenance and methodology.
        url: https://steadywrk.app/api/dispatch/analytics/evals
      properties:
        schema_version:
          type: string
        period:
          type: string
          enum: [rolling_7d, rolling_30d, rolling_90d]
        estimated:
          type: boolean
        goal_fulfillment:
          type: object
          properties:
            completion_rate:
              type: number
            nte_accuracy:
              type: number
            redispatch_rate:
              type: number
            human_override_rate:
              type: number
            source:
              type: string
              enum: [canonical_estimate, live_telemetry]
        operational:
          type: object
          properties:
            latency_source:
              type: string
            avg_dispatch_latency_ms:
              type: integer
            p95_dispatch_latency_ms:
              type: integer
            jobs_dispatched_30d:
              type: "null"
              description: Reserved compatibility field. Always null.
            active_providers:
              type: "null"
              description: Reserved compatibility field. Always null.
            active_states:
              type: string
              description: Compatibility text field; consult the document methodology.
            quote_turnaround_hours:
              type: number
              description: Quote-turnaround target in hours. Not a binding SLA.
            quote_turnaround_is_sla:
              type: boolean
              description: Always false — the hour figure is a target, not an SLA.
            pricing_latency_source:
              type: string
              enum: [live_telemetry, unavailable]
              description: Source class for pricing latency fields; consult provenance.by_field.
            pricing_latency_p50_ms:
              type: [integer, "null"]
              description: Nullable field defined by the document methodology and provenance.
            pricing_latency_p95_ms:
              type: [integer, "null"]
              description: Nullable field defined by the document methodology and provenance.
            pricing_latency_sampled_at:
              type: string
              format: date-time
              description: Optional timestamp associated with the pricing latency fields.
        provenance:
          type: object
          description: >-
            Per-field record of where every published figure came from, so a
            reader can consume the measured subset without trusting the
            estimated one.
          properties:
            by_field:
              type: object
              additionalProperties:
                type: string
                enum: [canonical_estimate, live_telemetry, unavailable]
            measured:
              type: array
              items:
                type: string
            estimated:
              type: array
              items:
                type: string
            unavailable:
              type: array
              items:
                type: string
        methodology:
          type: object
          description: >-
            Self-reported methodology for procurement readers. Metric
            definitions, named-period window start/end (timestamps only),
            source-class vocabulary matching provenance.by_field, and
            exclusion rules. independent_audit is always false. No sample
            size, job count, or provider count is published here.
          properties:
            independent_audit:
              type: boolean
              enum: [false]
            signed_snapshot:
              type: boolean
              enum: [false]
            claim_class:
              type: string
              enum: [self_reported]
            window:
              type: object
              properties:
                period:
                  type: string
                  enum: [rolling_7d, rolling_30d, rolling_90d]
                start:
                  type: string
                  format: date-time
                end:
                  type: string
                  format: date-time
                meaning:
                  type: string
            source_class:
              type: object
              properties:
                vocabulary:
                  type: array
                  items:
                    type: string
                    enum: [canonical_estimate, live_telemetry, unavailable]
                recorded_in:
                  type: string
                  enum: [provenance.by_field]
            definitions:
              type: object
              additionalProperties:
                type: string
            exclusions:
              type: array
              items:
                type: string
    Error:
      type: object
      properties:
        error:
          type: string
    ValidationError:
      type: object
      properties:
        error:
          type: string
          example: Validation failed
        details:
          type: object
          additionalProperties: true
    JsonRpcRequest:
      type: object
      required: [jsonrpc, method]
      properties:
        jsonrpc:
          type: string
          const: "2.0"
        id:
          oneOf:
            - type: string
            - type: integer
            - type: "null"
        method:
          type: string
          description: e.g. tools/list, tools/call (MCP) or message/send (A2A).
        params:
          type: object
          additionalProperties: true
    JsonRpcResponse:
      type: object
      required: [jsonrpc]
      properties:
        jsonrpc:
          type: string
          const: "2.0"
        id:
          oneOf:
            - type: string
            - type: integer
            - type: "null"
        result:
          type: object
          additionalProperties: true
        error:
          type: object
          properties:
            code:
              type: integer
            message:
              type: string
