{
  "openapi": "3.1.0",
  "info": {
    "title": "Intent Hub",
    "version": "1",
    "description": "The chat API of Intent Hub: a person's sentence in, offers from local businesses out, one booking at the end. JSON in, JSON out, no authentication and no key; every route is POST and the four of the booking sequence run in order: /api/intent, /api/bids, /api/hold, /api/confirm. Every answer carries a `session` id that ties the conversation, the bids and the holds together; pass it back into every later call.\n\nAn MCP client needs none of this: POST /mcp is the same market as MCP tools (see /dla-agentow and /.well-known/mcp.json).\n\nRate limits, per client address: 60 requests a minute across all /api routes together, of which 10 bidding rounds a minute, 3 holds and 5 cancels per ten minutes, and 5 guestbook entries an hour. Over budget is 429 with `Retry-After` and `{ \"error\": \"rate-limited\" }`. Outbound calls are bounded too: a business agent has 2 s to answer and a bidding round closes on time whatever the agents do.\n\nThis document is generated at startup from the hub's own Zod schemas, so a request body described here is exactly what the route validates."
  },
  "servers": [
    {
      "url": "https://openintentprotocol.com"
    }
  ],
  "paths": {
    "/api/intent": {
      "post": {
        "summary": "Parse a sentence into an intent",
        "description": "Turns what the person said into a structured intent (trade, town, day) and answers the session id every later call passes back. `complete: false` carries a `question` to put to the person; call this route again with the same `session` and their reply as `text`. `selection` restricts the conversation to one business, as a link from a business page does.",
        "operationId": "intent",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "text": {
                    "type": "string"
                  },
                  "session": {
                    "type": "string",
                    "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
                  },
                  "selection": {
                    "type": "object",
                    "properties": {
                      "business": {
                        "type": "string",
                        "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
                      },
                      "service": {
                        "type": "string",
                        "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
                      }
                    },
                    "required": [
                      "business"
                    ],
                    "additionalProperties": false
                  }
                },
                "required": [
                  "text"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Parse a sentence into an intent",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "The session id, the understood intent, its language, the open question, and whether the intent is complete."
                }
              }
            }
          },
          "400": {
            "description": "The body failed its schema, or the hub refused the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Unknown session, bid or hold.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The slot, the bid or the round is no longer the current one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Out of budget; `Retry-After` says when.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/bids": {
      "post": {
        "summary": "Run one bidding round",
        "description": "Fans the complete intent out to every matching business and answers inside a fixed window (3 s, 7 s with the negotiator on) whatever came back: `bids` ranked, and one `outcome` per business asked. An empty `bids` list is a normal answer and `outcomes` says why. A round started after this one wins: the older one is refused with `stale-round`.",
        "operationId": "bids",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "session": {
                    "type": "string",
                    "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
                  }
                },
                "required": [
                  "session"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Run one bidding round",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "The round: `session`, `intentId`, `date`, ranked `bids`, one `outcome` per business asked and `elapsedMs`."
                }
              }
            }
          },
          "400": {
            "description": "The body failed its schema, or the hub refused the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Unknown session, bid or hold.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The slot, the bid or the round is no longer the current one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Out of budget; `Retry-After` says when.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/bids/stream": {
      "post": {
        "summary": "The same round as a stream",
        "description": "The same bidding round as `/api/bids`, answered as NDJSON (`application/x-ndjson`): one JSON object per line, `\\n`-terminated, so a UI can show each bid the moment its agent answered. A `start` line, then a `bid` or `outcome` line per business, then one `end` line carrying the same body `/api/bids` would have answered, or an `error` line with the same code instead. The `bid` lines are provisional in order and membership; only the `end` line is the list the hold route accepts.",
        "operationId": "bids-stream",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "session": {
                    "type": "string",
                    "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
                  }
                },
                "required": [
                  "session"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The same round as a stream",
            "content": {
              "application/x-ndjson": {
                "schema": {
                  "type": "object",
                  "description": "One `start` line, `bid` and `outcome` lines, and a final `end` or `error` line."
                }
              }
            }
          },
          "400": {
            "description": "The body failed its schema, or the hub refused the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Unknown session, bid or hold.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The slot, the bid or the round is no longer the current one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Out of budget; `Retry-After` says when.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/hold": {
      "post": {
        "summary": "Hold the slot of one bid",
        "description": "Reserves the slot of one bid from this session's latest round for ten minutes, before the person is asked for their details. Never call it to browse: a hold blocks a real slot. With PUBLIC_ORIGIN set the answer carries `link`, the customer's private booking page.",
        "operationId": "hold",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "session": {
                    "type": "string",
                    "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
                  },
                  "bid": {
                    "type": "string",
                    "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
                  }
                },
                "required": [
                  "session",
                  "bid"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Hold the slot of one bid",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "hold": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
                        },
                        "bid": {
                          "type": "string",
                          "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
                        },
                        "business": {
                          "type": "string",
                          "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
                        },
                        "slot": {
                          "type": "string",
                          "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "held",
                            "confirmed",
                            "expired",
                            "released"
                          ]
                        },
                        "createdAt": {
                          "type": "integer",
                          "exclusiveMinimum": 0,
                          "maximum": 9007199254740991
                        },
                        "expiresAt": {
                          "type": "integer",
                          "exclusiveMinimum": 0,
                          "maximum": 9007199254740991
                        },
                        "confirmedAt": {
                          "type": "integer",
                          "exclusiveMinimum": 0,
                          "maximum": 9007199254740991
                        },
                        "contact": {
                          "type": "object",
                          "properties": {
                            "name": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 80
                            },
                            "phone": {
                              "type": "string",
                              "maxLength": 30
                            },
                            "email": {
                              "type": "string",
                              "maxLength": 120,
                              "format": "email",
                              "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
                            }
                          },
                          "required": [
                            "name",
                            "phone"
                          ],
                          "additionalProperties": false
                        },
                        "releasedAt": {
                          "type": "integer",
                          "exclusiveMinimum": 0,
                          "maximum": 9007199254740991
                        }
                      },
                      "required": [
                        "id",
                        "bid",
                        "business",
                        "slot",
                        "status",
                        "createdAt",
                        "expiresAt"
                      ]
                    },
                    "link": {
                      "type": "string",
                      "maxLength": 512
                    },
                    "session": {
                      "type": "string",
                      "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
                    },
                    "bid": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
                        }
                      },
                      "required": [
                        "id"
                      ],
                      "additionalProperties": {}
                    }
                  },
                  "required": [
                    "hold",
                    "session",
                    "bid"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The body failed its schema, or the hub refused the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Unknown session, bid or hold.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The slot, the bid or the round is no longer the current one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Out of budget; `Retry-After` says when.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/confirm": {
      "post": {
        "summary": "Confirm a held slot",
        "description": "Books the held slot with the customer's own name and phone number (nine digits, or a country code and the number). Idempotent: confirming the same hold twice books it once and tells the business once. Ask the person for the contact; never invent one.",
        "operationId": "confirm",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "session": {
                    "type": "string",
                    "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
                  },
                  "hold": {
                    "type": "string",
                    "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
                  },
                  "contact": {
                    "type": "object",
                    "properties": {
                      "name": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 80
                      },
                      "phone": {
                        "type": "string",
                        "maxLength": 30
                      },
                      "email": {
                        "type": "string",
                        "maxLength": 120,
                        "format": "email",
                        "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
                      }
                    },
                    "required": [
                      "name",
                      "phone"
                    ],
                    "additionalProperties": false
                  }
                },
                "required": [
                  "session",
                  "hold",
                  "contact"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Confirm a held slot",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "hold": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
                        },
                        "bid": {
                          "type": "string",
                          "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
                        },
                        "business": {
                          "type": "string",
                          "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
                        },
                        "slot": {
                          "type": "string",
                          "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "held",
                            "confirmed",
                            "expired",
                            "released"
                          ]
                        },
                        "createdAt": {
                          "type": "integer",
                          "exclusiveMinimum": 0,
                          "maximum": 9007199254740991
                        },
                        "expiresAt": {
                          "type": "integer",
                          "exclusiveMinimum": 0,
                          "maximum": 9007199254740991
                        },
                        "confirmedAt": {
                          "type": "integer",
                          "exclusiveMinimum": 0,
                          "maximum": 9007199254740991
                        },
                        "contact": {
                          "type": "object",
                          "properties": {
                            "name": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 80
                            },
                            "phone": {
                              "type": "string",
                              "maxLength": 30
                            },
                            "email": {
                              "type": "string",
                              "maxLength": 120,
                              "format": "email",
                              "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
                            }
                          },
                          "required": [
                            "name",
                            "phone"
                          ],
                          "additionalProperties": false
                        },
                        "releasedAt": {
                          "type": "integer",
                          "exclusiveMinimum": 0,
                          "maximum": 9007199254740991
                        }
                      },
                      "required": [
                        "id",
                        "bid",
                        "business",
                        "slot",
                        "status",
                        "createdAt",
                        "expiresAt"
                      ]
                    },
                    "link": {
                      "type": "string",
                      "maxLength": 512
                    },
                    "session": {
                      "type": "string",
                      "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
                    },
                    "bid": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
                        }
                      },
                      "required": [
                        "id"
                      ],
                      "additionalProperties": {}
                    }
                  },
                  "required": [
                    "hold",
                    "session",
                    "bid"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The body failed its schema, or the hub refused the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Unknown session, bid or hold.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The slot, the bid or the round is no longer the current one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Out of budget; `Retry-After` says when.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/cancel": {
      "post": {
        "summary": "Release a held or confirmed slot",
        "description": "Frees the slot again. A business hosting its own agent may answer `not-supported`.",
        "operationId": "cancel",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "session": {
                    "type": "string",
                    "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
                  },
                  "hold": {
                    "type": "string",
                    "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
                  }
                },
                "required": [
                  "session",
                  "hold"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Release a held or confirmed slot",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "The released hold and the bid it was made from."
                }
              }
            }
          },
          "400": {
            "description": "The body failed its schema, or the hub refused the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Unknown session, bid or hold.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The slot, the bid or the round is no longer the current one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Out of budget; `Retry-After` says when.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/session": {
      "post": {
        "summary": "Read the state of a session",
        "description": "The understood intent and its open question, the bids of the current round and every hold with its status and link. Use it to pick up a conversation you already hold a session id for.",
        "operationId": "session",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "session": {
                    "type": "string",
                    "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
                  }
                },
                "required": [
                  "session"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Read the state of a session",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "The session: the intent, the bids of the current round and every hold made in it."
                }
              }
            }
          },
          "400": {
            "description": "The body failed its schema, or the hub refused the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Unknown session, bid or hold.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The slot, the bid or the round is no longer the current one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Out of budget; `Retry-After` says when.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/hello": {
      "post": {
        "summary": "Sign the agent guestbook",
        "description": "Say hi at /goscie. No address and no raw User-Agent is stored, only a coarse crawler family. Optional, and nothing else on the hub depends on it.",
        "operationId": "hello",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "agent": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "operator": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "purpose": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "url": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  }
                },
                "required": [
                  "agent"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sign the agent guestbook",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "`{ ok: true, id, seen }`."
                }
              }
            }
          },
          "400": {
            "description": "The body failed its schema, or the hub refused the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Unknown session, bid or hold.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The slot, the bid or the round is no longer the current one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Out of budget; `Retry-After` says when.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/mcp": {
      "post": {
        "summary": "The Model Context Protocol endpoint",
        "description": "JSON-RPC 2.0 over streamable HTTP without the stream: one request object per POST, one `application/json` answer, stateless. Methods: `initialize`, `ping`, `tools/list`, `tools/call`. The tools are `find_offers`, `hold_slot`, `confirm_booking`, `cancel_booking` and `get_booking`, thin wrappers over the routes above sharing their sessions and their budgets, plus `register_agent` and `capabilities`, which are the endpoint's own; `tools/list` publishes their input schemas. The three that touch a slot need a key of the agent's own there (`Authorization: Bearer`, from `register_agent`); the routes documented here do not, and keep their per-address budgets. A batch (a JSON array) is refused with `-32600` and a notification is answered 202 with an empty body.",
        "operationId": "mcp",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "One JSON-RPC 2.0 request object: `{ jsonrpc: \"2.0\", id, method, params? }`."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "One JSON-RPC 2.0 response object, carrying either `result` or `error`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "A JSON-RPC 2.0 response object."
                }
              }
            }
          },
          "202": {
            "description": "A notification was accepted; the body is empty."
          },
          "400": {
            "description": "The body failed its schema, or the hub refused the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "This endpoint is POST only.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "A JSON-RPC 2.0 error object."
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Error": {
        "type": "object",
        "description": "A refusal with a stable machine-readable code, such as `rate-limited`, `unknown-session`, `stale-round` or `invalid-input`.",
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ]
      }
    }
  }
}
