Hands for Agents Quotes API

The Quotes API from Hands for Agents — 4 operation(s) for quotes.

GitHub OpenAPI

Operations 6

POST /quotes Send a task to a human for screening and a fixed-price quote #
GET /quotes/{quote_id} get_status for a quote #
POST /quotes/{quote_id} get_status for a quote, with a message to the operator #
POST /quotes/{quote_id}/access-token Recover a lost access_token #
POST /quotes/{quote_id}/files Upload one or more input files to a quote #
GET /quotes/{quote_id}/files List files uploaded to a quote #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/handsforagents-com:handsforagents-com-quotes-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

handsforagents-com-quotes-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Hands for Agents Quotes API
  version: 0.1.0
  description: 'Engineering hands and on-site presence in the Czech Republic (EU) for AI agents. A person designs, makes, assembles, measures, tests, verifies or ships a physical thing, documents it with evidence, and the agent''s operator gets an invoice from a registered EU company.


    Provider: your engineering s.r.o., company ID (IČO) 09738100, Praha 4, Czech Republic.


    Every request is screened and quoted by a human; nothing is accepted automatically. `GET /services` and `POST /quotes` need no authentication. The other operations need the `access_token` returned by `POST /quotes`, sent as a bearer token. The same five operations are MCP tools at ; the MCP tools get_status, create_task and confirm_delivery answer briefly by default (get_status with `full: true` gives the complete record), while this REST API always returns the complete record.'
  termsOfService: https://handsforagents.com/terms.html
  contact:
    name: your engineering s.r.o.
    email: tasks@handsforagents.com
    url: https://handsforagents.com
servers:
- url: https://api.handsforagents.com/v1
tags:
- name: Quotes
paths:
  /quotes:
    post:
      operationId: request_quote
      summary: Send a task to a human for screening and a fixed-price quote
      description: 'Not binding. A human answers within 24 hours (every day, including weekends and Czech public holidays). Required: description, email and name (the name of the person or company for the invoice); delivery_address is required too when a physical item is to be shipped, handed over or collected. The fields can be sent flat, at the top level (QuoteRequestFlat, the shape smaller models get right), or nested under task / client / agent (QuoteRequestNested); both are accepted, also mixed, and if a field is sent both ways the nested one wins. A missing required field is a 400 that names the field and shows an example. Put everything the user told you (quantity, material, colour, size, address) into the fields or into description. For name use the company or person from the shipping address (or the company the user gave); do not ask the user for it, ask only if there is neither. If the user gave an address, its country is the country (ISO 3166-1 alpha-2). A VAT id only if the user gave one: never guess or make one up. Never invent a quote_id or a status: a real quote_id starts with q_ and comes only from this tool''s own response. The answer carries quote_id and access_token: Tell the user to save the access_token now: it is shown once and is the only key to the quote and the task. If the access_token is lost: POST https://api.handsforagents.com/v1/quotes/{quote_id}/access-token (REST only, not a tool); a new one is sent to the contact e-mail. get_status cannot recover it.'
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/QuoteRequest'
            examples:
              shortest_possible:
                value:
                  description: Make 20 rubber gaskets, 60 mm outer diameter, black EPDM.
                  email: ops@example.com
                  name: Example Inc.
              with_address_and_file:
                value:
                  description: Make 20 rubber gaskets, 60 mm outer diameter, black EPDM.
                  email: ops@example.com
                  name: Example Inc.
                  delivery_address: Example GmbH, Musterstr. 1, 10115 Berlin, DE, +49 30 000000
                  input_files:
                  - https://example.com/gasket-drawing.pdf
              nested_form:
                value:
                  task:
                    description: Make 20 rubber gaskets, 60 mm outer diameter, black EPDM.
                    delivery_address: Example GmbH, Musterstr. 1, 10115 Berlin, DE, +49 30 000000
                    input_files:
                    - https://example.com/gasket-drawing.pdf
                  client:
                    email: ops@example.com
                    name: Example Inc.
      responses:
        '201':
          description: Request received
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/QuoteReceipt'
        '400':
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      tags:
      - Quotes
  /quotes/{quote_id}:
    get:
      operationId: get_quote_status
      summary: get_status for a quote
      security:
      - taskToken: []
      description: 'Always read next_action: one or two sentences on what to do now (wait; review the quote and call create_task; pay checkout_url; call confirm_delivery), with paid, quote_valid_until and expected_ship_date. messages is the thread with the operator, newest first (from: operator or agent). Every status can change at any time: call again whenever the user asks, never answer from memory, and report only what the response says, without conclusions of your own. This REST answer is always the complete record; the MCP tools get_status, create_task and confirm_delivery answer briefly by default (get_status with full=true returns this complete record). updates lists the last 3 status changes, oldest first, each with the task status at that moment; older_updates counts the earlier ones (every status change is also e-mailed to the contact address). To answer an operator question, call get_status with message: send the same request as POST with a JSON body {"message": "..."} (plain text, 1-2000 characters); it is stored in the same thread and e-mailed to the operator, and the answer is the usual status, including your message (message_sent: true). Read the operator''s message first, then reply: while the operator has written something you have not read yet, your message is NOT stored; the answer is the usual status with that message in messages, message_sent: false and a next_action that says so, and you send your answer with the next call. Send it once and only with what the user told you: the same text again within 10 minutes is ignored (duplicate_ignored: true). E-mail to tasks@handsforagents.com with the quote_id in the subject still works. test_mode true marks a test order: nothing is paid or invoiced.'
      parameters:
      - name: quote_id
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: The quote, or the refusal with its reason
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Quote'
        '401':
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      tags:
      - Quotes
    post:
      operationId: get_quote_status_with_message
      summary: get_status for a quote, with a message to the operator
      security:
      - taskToken: []
      description: 'This is get_status with the optional message, as a POST so that the text is in the body, not in the URL (which servers log). Without a message (empty body, or {"message": null}) it is a plain status call, like the GET. With a message it is written to the thread and e-mailed to the operator, and the answer is the same as the GET''s, including your message (from: agent) and message_sent: true. Read the operator''s message first, then reply: while the operator has written something you have not read yet, nothing is stored or e-mailed; the answer is the GET''s, with that message in messages, message_sent: false and a next_action that says so, and you send your answer with the next call (HTTP 200 either way, so check message_sent). Plain text, 1-2000 characters, never interpreted as HTML; an empty or longer message is a 400. Limits: 10 messages per hour per quote (its quote_id and task_id share it) and 30 per hour per client address (429). The same text again within 10 minutes, while it is still the newest message of the thread, is not stored or e-mailed a second time (the answer then has duplicate_ignored: true). It needs the same access_token as the GET; without one, or with another quote''s, nothing is stored or sent (401).'
      parameters:
      - name: quote_id
        in: path
        required: true
        schema:
          type: string
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/StatusMessage'
      responses:
        '200':
          description: The same answer as the GET, now including your message; if the operator had written something you had not read, message_sent is false, nothing was stored and the answer shows that message
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Quote'
        '400':
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      tags:
      - Quotes
  /quotes/{quote_id}/access-token:
    post:
      operationId: request_access_token
      summary: Recover a lost access_token
      security: []
      description: Always the same response, whether or not quote_id exists, so this cannot be used to find out which quote ids exist. If the quote exists and has a contact e-mail on file, a new access_token is generated and sent there; any access_token already issued for this quote keeps working. Rate-limited per quote.
      parameters:
      - name: quote_id
        in: path
        required: true
        schema:
          type: string
      responses:
        '202':
          description: Always the same, generic acknowledgement
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AccessTokenRequestAck'
        '429':
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      tags:
      - Quotes
  /quotes/{quote_id}/files:
    post:
      operationId: upload_files
      summary: Upload one or more input files to a quote
      security:
      - taskToken: []
      description: An alternative to a URL in task.input_files or an e-mail attachment. multipart/form-data, one or more parts named `file`. At most 95 MB per request (this hosting's own ceiling), 20 files or 500 MB per quote in total, extensions step, stp, stl, 3mf, obj, iges, igs, dxf, dwg, pdf, png, jpg, jpeg, webp, zip only. Only while the quote, and any task made from it, is open.
      parameters:
      - name: quote_id
        in: path
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  description: One or more files.
                  oneOf:
                  - type: string
                    format: binary
                  - type: array
                    items:
                      type: string
                      format: binary
      responses:
        '201':
          description: Files stored
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UploadedFileList'
        '400':
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '413':
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '415':
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      tags:
      - Quotes
    get:
      operationId: list_files
      summary: List files uploaded to a quote
      security:
      - taskToken: []
      parameters:
      - name: quote_id
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Files uploaded so far
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UploadedFileList'
        '401':
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      tags:
      - Quotes
components:
  schemas:
    UploadedFileList:
      type: object
      required:
      - files
      properties:
        files:
          type: array
          items:
            type: object
            required:
            - name
            - size
            - sha256
            - created_at
            properties:
              name:
                type: string
                description: Sanitized name as stored; may differ from the name you sent.
              size:
                type: integer
              sha256:
                type: string
              created_at:
                type: string
                format: date-time
    QuoteReceipt:
      type: object
      required:
      - quote_id
      - access_token
      - status
      - response_due_at
      - nda_url
      properties:
        quote_id:
          type: string
        access_token:
          type: string
        status:
          type: string
          enum:
          - received
        response_due_at:
          type: string
          format: date-time
        nda_url:
          type: string
          format: uri
          const: https://handsforagents.com/nda.html
          description: 'One-way non-disclosure agreement, pre-filled with the provider''s details. Optional: every task is confidential under the terms of service without it. The operator fills in its details, signs and e-mails the PDF to legal@handsforagents.com.'
        upload_url:
          type: string
          format: uri
          description: POST input files here (multipart, field name `file`) instead of a URL in task.input_files or an e-mail attachment. Same access_token, as a bearer token.
        next_action:
          type: string
          description: 'One or two sentences in English: what to do now (tell the user to save the access_token, when the answer is due, which call to make next).'
    QuoteRequest:
      description: 'A quote request in either shape; the two can also be mixed, and the nested value wins if a field is sent both ways. Required in the flat shape: description, email, name; in the nested shape: task.description, client.email, client.name.'
      anyOf:
      - $ref: '#/components/schemas/QuoteRequestFlat'
      - $ref: '#/components/schemas/QuoteRequestNested'
    AccessTokenRequestAck:
      type: object
      required:
      - message
      properties:
        message:
          type: string
          const: If the quote exists and has a contact e-mail, a new access_token was sent there.
    QuoteRequestNested:
      type: object
      properties:
        task:
          type: object
          properties:
            description:
              type: string
              description: 'What is to be done, in plain language: everything the user told you (quantity, material, colour, size, tolerances, what counts as done). A detail that has no field of its own belongs here.'
              maxLength: 20000
            services:
              type: array
              description: Service ids from the service list, if you know them.
              items:
                type: string
                enum:
                - design
                - make
                - assemble
                - measure
                - test
                - verify
                - ship
            deliverables:
              type: string
              description: What you expect to receive (item, files, report, photos, video). Optional - if you are not sure, leave it out and put it in task.description instead.
              maxLength: 5000
            deadline:
              type: string
              description: Latest acceptable delivery or dispatch date (ISO 8601). Optional; omit if the user gave none.
              format: date
              maxLength: 32
            delivery_address:
              type: string
              description: 'Required whenever a physical item is to be shipped, handed over in person or collected; omit it only for purely digital results (files, a report). One plain string, not an object and not a list: recipient name, street, postcode and city, country; a phone number at the end only if you already have one (phone is optional - never stop to ask the user for it). If you do not have the address, ask the user for it; never invent one.'
              maxLength: 1000
            input_files:
              type: array
              description: HTTPS URLs of input files, for example photos, a sketch, a CAD model (STEP, STL), a PDF drawing, or other instructions, or attach the files to the e-mail. At most 20, each at most 2000 characters.
              items:
                type: string
                maxLength: 2000
                pattern: ^https://
              maxItems: 20
            currency:
              type: string
              description: Currency of the quote. Default EUR.
              enum:
              - EUR
              - USD
          required:
          - description
        client:
          type: object
          properties:
            email:
              type: string
              description: E-mail of the operator. The quote, the payment link and the invoice go here.
              format: email
              maxLength: 254
              pattern: ^[^\s@]+@[^\s@]+\.[^\s@]+$
            name:
              type: string
              description: 'Required. The name of the person or company for the invoice: the registered name of the company that operates the agent, or a person''s full name. Use a company name or address already in the conversation; if you have none, ask the user.'
              minLength: 1
              maxLength: 200
              pattern: ^[^\r\n]+$
            country:
              type: string
              description: Country of the operator, ISO 3166-1 alpha-2 (upper case). Optional, but include it whenever you can derive it (for example from the operator's address) rather than leaving it out or asking the user.
              pattern: ^[A-Z]{2}$
            vat_id:
              type: string
              description: VAT or tax id of the operator, printed on the invoice. Optional. Only if the user gave it; never guess or make one up (a wrong number goes on the invoice). Leave it out otherwise.
              maxLength: 32
          required:
          - email
          - name
        agent:
          type: object
          properties:
            name:
              type: string
              description: Name of the agent or platform sending the request. Optional, for our records.
              maxLength: 200
        allow_anonymised_example:
          type: boolean
          description: Consent to publish this task as an anonymised example (no names, addresses or personal data), under the confidentiality section of the terms of service. Only set true if the client explicitly agreed to this; otherwise omit the field or leave it false. Default false; can be withdrawn.
          default: false
    QuoteRequestFlat:
      type: object
      properties:
        description:
          type: string
          description: 'What is to be done, in plain language: everything the user told you (quantity, material, colour, size, tolerances, what counts as done). A detail that has no field of its own belongs here.'
          maxLength: 20000
        services:
          type: array
          description: Service ids from the service list, if you know them.
          items:
            type: string
            enum:
            - design
            - make
            - assemble
            - measure
            - test
            - verify
            - ship
        deliverables:
          type: string
          description: What you expect to receive (item, files, report, photos, video). Optional - if you are not sure, leave it out and put it in task.description instead.
          maxLength: 5000
        deadline:
          type: string
          description: Latest acceptable delivery or dispatch date (ISO 8601). Optional; omit if the user gave none.
          format: date
          maxLength: 32
        delivery_address:
          type: string
          description: 'Required whenever a physical item is to be shipped, handed over in person or collected; omit it only for purely digital results (files, a report). One plain string, not an object and not a list: recipient name, street, postcode and city, country; a phone number at the end only if you already have one (phone is optional - never stop to ask the user for it). If you do not have the address, ask the user for it; never invent one.'
          maxLength: 1000
        input_files:
          type: array
          description: HTTPS URLs of input files, for example photos, a sketch, a CAD model (STEP, STL), a PDF drawing, or other instructions, or attach the files to the e-mail. At most 20, each at most 2000 characters.
          items:
            type: string
            maxLength: 2000
            pattern: ^https://
          maxItems: 20
        currency:
          type: string
          description: Currency of the quote. Default EUR.
          enum:
          - EUR
          - USD
        email:
          type: string
          description: E-mail of the operator. The quote, the payment link and the invoice go here.
          format: email
          maxLength: 254
          pattern: ^[^\s@]+@[^\s@]+\.[^\s@]+$
        name:
          type: string
          description: 'Required. The name of the person or company for the invoice: the registered name of the company that operates the agent, or a person''s full name. Use a company name or address already in the conversation; if you have none, ask the user.'
          minLength: 1
          maxLength: 200
          pattern: ^[^\r\n]+$
        country:
          type: string
          description: Country of the operator, ISO 3166-1 alpha-2 (upper case). Optional, but include it whenever you can derive it (for example from the operator's address) rather than leaving it out or asking the user.
          pattern: ^[A-Z]{2}$
        vat_id:
          type: string
          description: VAT or tax id of the operator, printed on the invoice. Optional. Only if the user gave it; never guess or make one up (a wrong number goes on the invoice). Leave it out otherwise.
          maxLength: 32
        agent_name:
          type: string
          description: Name of the agent or platform sending the request. Optional, for our records.
          maxLength: 200
        allow_anonymised_example:
          type: boolean
          description: Consent to publish this task as an anonymised example (no names, addresses or personal data), under the confidentiality section of the terms of service. Only set true if the client explicitly agreed to this; otherwise omit the field or leave it false. Default false; can be withdrawn.
          default: false
      required:
      - description
      - email
      - name
    Error:
      type: object
      required:
      - error
      properties:
        error:
          type: object
          required:
          - code
          - message
          properties:
            code:
              type: string
            message:
              type: string
    Quote:
      type: object
      required:
      - quote_id
      - status
      properties:
        quote_id:
          type: string
        status:
          type: string
          enum:
          - received
          - quoted
          - refused
          - expired
        refusal_reason:
          type: string
          description: Present when status is refused. Refused requests are not invoiced.
        refusal_code:
          type: string
          enum:
          - credentials
          - impersonation
          - surveillance
          - social-manipulation
          - verification-bypass
          - referral-fraud
          - weapons-regulated
          - counterfeit
          - reshipping-fraud
          - illegal
          - capability-limit
          - sensitive-unverified
          - other
          description: 'Present when status is refused: the category from the task policy, or capability-limit / sensitive-unverified / other.'
        task_id:
          type: string
          description: Present once create_task was called for this quote.
        price:
          type: object
          required:
          - amount
          - currency
          properties:
            amount:
              type: number
              description: Fixed total. No VAT is added; the provider is not VAT-registered.
            currency:
              type: string
              enum:
              - EUR
              - USD
        payment_schedule:
          type: object
          required:
          - mode
          description: 'Up to 150 EUR: full_prepayment or card_hold. Above: deposit_and_balance.'
          properties:
            mode:
              type: string
              enum:
              - full_prepayment
              - card_hold
              - deposit_and_balance
            deposit:
              type: object
              required:
              - amount
              - currency
              properties:
                amount:
                  type: number
                  description: Fixed total. No VAT is added; the provider is not VAT-registered.
                currency:
                  type: string
                  enum:
                  - EUR
                  - USD
            balance:
              type: object
              required:
              - amount
              - currency
              properties:
                amount:
                  type: number
                  description: Fixed total. No VAT is added; the provider is not VAT-registered.
                currency:
                  type: string
                  enum:
                  - EUR
                  - USD
        scope:
          type: string
        deliverables:
          type: string
        evidence:
          type: array
          items:
            type: string
          description: What the evidence package will contain.
        subcontracted:
          type: string
          description: Processes done by subcontractors, if any.
        delivery_date:
          type: string
          format: date
        inbound_address:
          type: string
          description: Where to send parts or parcels, if the task needs them.
        operator_verification_required:
          type: boolean
          description: True for the sensitive task categories.
        valid_until:
          type: string
          format: date
        terms_version:
          type: string
        next_action:
          type: string
          description: 'One or two sentences in English: what the agent or the client should do now.'
        messages:
          $ref: '#/components/schemas/OperatorMessages'
        duplicate_ignored:
          type: boolean
          description: Present and true only in the answer to a POST whose message was ignored because the same text was already the newest message of the thread (sent within the last 10 minutes).
        message_sent:
          type: boolean
          description: 'Present only in the answer to a POST with a message: true, the message was stored and e-mailed to the operator; false, it was NOT stored because the operator had written something you had not read yet (that message is in messages, now marked as read; next_action says so): send your answer again. Absent when the same text was ignored (duplicate_ignored) and in every plain status call.'
        test_mode:
          type: boolean
          description: 'Present and true only for a test quote: an order made from it is a test order.'
    StatusMessage:
      type: object
      description: Optional body of POST /quotes/{quote_id} and POST /tasks/{task_id}. An empty body is a plain status call.
      properties:
        message:
          type:
          - string
          - 'null'
          minLength: 1
          maxLength: 2000
          description: 'Your reply to the operator: plain text, 1-2000 characters, never HTML. Null or absent: a plain status call.'
    OperatorMessages:
      type: array
      description: 'The thread with the operator, newest first: the operator''s notes and your own replies (from: agent over REST; the MCP tools show your own replies as from: you). An operator message you have not been shown yet is always included, also in the answer to your own reply (a message of yours sent while it is unread is not stored: message_sent is false). Read the operator''s message first, then reply: call get_status with message (POST with a JSON body over REST); e-mailing the tasks address with the quote_id in the subject also works.'
      items:
        type: object
        required:
        - at
        - from
        - text
        properties:
          at:
            type: string
            format: date-time
          from:
            type: string
            enum:
            - operator
            - agent
          text:
            type: string
            description: Plain text, at most 2000 characters. Never HTML.
  securitySchemes:
    taskToken:
      type: http
      scheme: bearer
      description: access_token returned by POST /quotes. Valid for that quote and the task created from it.
x-status: live