{
  "openapi": "3.1.0",
  "info": {
    "title": "PolPilot MCP and OAuth API",
    "version": "1.0.0",
    "summary": "Read-only access to one business's PolPilot data from an AI assistant.",
    "description": "PolPilot is an AI-agent platform for Argentine distributors, agribusinesses and food banks. Its public machine interface is a **read-only MCP server** (Model Context Protocol, Streamable HTTP) authenticated with **OAuth 2.1** (authorization code + PKCE S256, dynamic client registration) or a personal access token. Every tool answers a question about the caller's own business with the same deterministic calculations the app uses; none changes data. What a caller sees is limited by the role of the PolPilot person who granted access.\n\nThere is one OAuth scope, `mcp:read`: read the data the granting person can read, through `/mcp` only. Tokens of this scope are refused by every other PolPilot endpoint. A personal access token can additionally be limited to some modules when it is created.\n\nThere is no open self-serve signup: an organization starts with a demo request (https://www.polpilot.com/#demo). Once a person has a PolPilot account they connect an MCP client with OAuth, or create a personal access token in My profile > Security > Access tokens.\n\nThe business that issued a token is the one read; there is no tenant parameter. Docs: https://www.polpilot.com/developers",
    "contact": {
      "name": "PolPilot",
      "email": "teampolpilot@gmail.com",
      "url": "https://www.polpilot.com/contact"
    },
    "x-audience": "AI agents and MCP clients acting for a PolPilot customer"
  },
  "servers": [
    {
      "url": "https://app.polpilot.com",
      "description": "PolPilot application host"
    }
  ],
  "externalDocs": {
    "description": "Developer documentation",
    "url": "https://www.polpilot.com/developers"
  },
  "tags": [
    {
      "name": "MCP",
      "description": "The Model Context Protocol server."
    },
    {
      "name": "OAuth",
      "description": "OAuth 2.1 authorization server used to connect an MCP client."
    },
    {
      "name": "Discovery",
      "description": "Machine-readable metadata (RFC 9728, RFC 8414)."
    }
  ],
  "security": [
    {
      "oauth2": [
        "mcp:read"
      ]
    }
  ],
  "paths": {
    "/mcp": {
      "post": {
        "operationId": "callMcp",
        "tags": [
          "MCP"
        ],
        "summary": "Send one MCP JSON-RPC message",
        "description": "MCP Streamable HTTP endpoint (stateless, JSON responses). Send `Accept: application/json, text/event-stream` and the JSON-RPC message as the body. Send `initialize`, `tools/list` and `tools/call` as JSON-RPC 2.0 messages. `tools/list` returns only the read-only tools the caller's role may use (for example business summary, money tied up in stock, customer balances, warehouse expirations, demand forecast). Without a valid token the endpoint answers 401 with a `WWW-Authenticate` challenge whose `resource_metadata` points to the protected-resource metadata.",
        "security": [
          {
            "oauth2": [
              "mcp:read"
            ]
          },
          {
            "personalAccessToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JsonRpcRequest"
              },
              "examples": {
                "initialize": {
                  "summary": "Open a session",
                  "value": {
                    "jsonrpc": "2.0",
                    "id": 1,
                    "method": "initialize",
                    "params": {
                      "protocolVersion": "2025-06-18",
                      "capabilities": {},
                      "clientInfo": {
                        "name": "example",
                        "version": "1.0"
                      }
                    }
                  }
                },
                "listTools": {
                  "summary": "List the tools you may call",
                  "value": {
                    "jsonrpc": "2.0",
                    "id": 2,
                    "method": "tools/list"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A JSON-RPC response (result or error).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcResponse"
                }
              }
            }
          },
          "401": {
            "description": "No token, or an invalid or expired one. Obtain a token as described in `WWW-Authenticate`.",
            "headers": {
              "WWW-Authenticate": {
                "description": "`Bearer resource_metadata=\"…/.well-known/oauth-protected-resource\"`, plus `error=\"invalid_token\"` when a token was sent.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/oauth-protected-resource": {
      "get": {
        "operationId": "getProtectedResourceMetadata",
        "tags": [
          "Discovery"
        ],
        "summary": "Read the protected-resource metadata of /mcp",
        "description": "RFC 9728 metadata: the resource, its authorization server and `scopes_supported` (`mcp:read`). Public, cacheable for 5 minutes, CORS open.",
        "security": [],
        "responses": {
          "200": {
            "description": "The metadata document.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProtectedResourceMetadata"
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/oauth-authorization-server": {
      "get": {
        "operationId": "getAuthorizationServerMetadata",
        "tags": [
          "Discovery"
        ],
        "summary": "Read the authorization server metadata",
        "description": "RFC 8414 metadata: endpoints, supported grant types, PKCE methods and scopes. Public, cacheable for 5 minutes, CORS open.",
        "security": [],
        "responses": {
          "200": {
            "description": "The metadata document.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthorizationServerMetadata"
                }
              }
            }
          }
        }
      }
    },
    "/oauth/register": {
      "post": {
        "operationId": "registerOAuthClient",
        "tags": [
          "OAuth"
        ],
        "summary": "Register an OAuth client dynamically",
        "description": "RFC 7591 dynamic client registration. Public clients only (`token_endpoint_auth_method: none`). Between 1 and 5 `redirect_uris`: https, loopback http, or a private-use scheme, never with a fragment. Rate limited per IP address.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ClientRegistrationRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The client was registered.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClientRegistration"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_client_metadata` or `invalid_redirect_uri`.",
            "headers": {
              "Cache-Control": {
                "description": "Always `no-store`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests from this address. Wait the number of seconds in `Retry-After`, then retry.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          }
        }
      }
    },
    "/oauth/authorize": {
      "get": {
        "operationId": "authorizeOAuthClient",
        "tags": [
          "OAuth"
        ],
        "summary": "Start the browser consent flow",
        "description": "Open this URL in the person's browser. They sign in to PolPilot, check the business named on the page and approve. The browser is then redirected to `redirect_uri` with `code` and `state`, or with `error=access_denied`. Errors in the request that make the redirect unsafe (unknown client, unregistered redirect URI) show an HTML page instead of redirecting.",
        "security": [],
        "parameters": [
          {
            "name": "response_type",
            "in": "query",
            "required": true,
            "description": "Always `code`.",
            "schema": {
              "type": "string",
              "enum": [
                "code"
              ]
            }
          },
          {
            "name": "client_id",
            "in": "query",
            "required": true,
            "description": "The `client_id` returned by registration.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "redirect_uri",
            "in": "query",
            "required": true,
            "description": "One of the URIs registered for the client, byte for byte.",
            "schema": {
              "type": "string",
              "format": "uri"
            }
          },
          {
            "name": "code_challenge",
            "in": "query",
            "required": true,
            "description": "PKCE challenge: base64url(SHA-256(code_verifier)), 43 to 128 characters.",
            "schema": {
              "type": "string",
              "minLength": 43,
              "maxLength": 128
            }
          },
          {
            "name": "code_challenge_method",
            "in": "query",
            "required": true,
            "description": "Only `S256` is accepted.",
            "schema": {
              "type": "string",
              "enum": [
                "S256"
              ]
            }
          },
          {
            "name": "scope",
            "in": "query",
            "required": false,
            "description": "Optional; the only scope is `mcp:read`.",
            "schema": {
              "type": "string",
              "enum": [
                "mcp:read"
              ]
            }
          },
          {
            "name": "state",
            "in": "query",
            "required": false,
            "description": "Opaque value returned unchanged on the redirect.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "resource",
            "in": "query",
            "required": false,
            "description": "RFC 8707 resource indicator; if sent it must be the `/mcp` URL.",
            "schema": {
              "type": "string",
              "format": "uri"
            }
          }
        ],
        "responses": {
          "302": {
            "description": "Redirect to the PolPilot consent page, or back to `redirect_uri` with an OAuth `error`.",
            "headers": {
              "Location": {
                "description": "Where the browser goes next.",
                "schema": {
                  "type": "string",
                  "format": "uri"
                }
              }
            }
          },
          "400": {
            "description": "HTML error page: the client or redirect URI cannot be trusted, so nothing is redirected.",
            "content": {
              "text/html": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/oauth/token": {
      "post": {
        "operationId": "exchangeOAuthToken",
        "tags": [
          "OAuth"
        ],
        "summary": "Exchange a code or a refresh token for an access token",
        "description": "Access tokens (`oat_…`) last 1 hour. The refresh token (`ort_…`) rotates on each use and expires 30 days after last use; presenting an already-rotated one revokes the whole grant. Authorization codes are single use and live 2 minutes.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/TokenRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tokens.",
            "headers": {
              "Cache-Control": {
                "description": "`no-store`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TokenResponse"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_grant` (unknown, expired or reused code; PKCE mismatch), `unsupported_grant_type` or `invalid_request`.",
            "headers": {
              "Cache-Control": {
                "description": "Always `no-store`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "401": {
            "description": "`invalid_client`: unknown `client_id`.",
            "headers": {
              "Cache-Control": {
                "description": "Always `no-store`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests from this address. Wait the number of seconds in `Retry-After`, then retry.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          }
        }
      }
    },
    "/oauth/revoke": {
      "post": {
        "operationId": "revokeOAuthToken",
        "tags": [
          "OAuth"
        ],
        "summary": "Revoke an access or refresh token",
        "description": "RFC 7009. Always answers 200 with an empty object, whether or not the token was known. Revoking either token ends the whole grant.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/RevokeRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Done (or the token was not known).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": false
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "oauth2": {
        "type": "oauth2",
        "description": "OAuth 2.1 authorization code with PKCE S256. Public clients; no client secret.",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://app.polpilot.com/oauth/authorize",
            "tokenUrl": "https://app.polpilot.com/oauth/token",
            "refreshUrl": "https://app.polpilot.com/oauth/token",
            "scopes": {
              "mcp:read": "Read the data the granting person can read, through the MCP server only."
            }
          }
        }
      },
      "personalAccessToken": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "pat_…",
        "description": "A personal access token created by a PolPilot user in My profile > Security > Access tokens. Shown once, revocable, optionally limited to some modules."
      }
    },
    "schemas": {
      "JsonRpcRequest": {
        "type": "object",
        "required": [
          "jsonrpc",
          "method"
        ],
        "properties": {
          "jsonrpc": {
            "type": "string",
            "const": "2.0"
          },
          "id": {
            "description": "Request id. Omit for notifications.",
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "integer"
              }
            ]
          },
          "method": {
            "type": "string",
            "description": "For example `initialize`, `tools/list` or `tools/call`.",
            "examples": [
              "tools/list"
            ]
          },
          "params": {
            "type": "object",
            "description": "Method parameters; for `tools/call`, `{ name, arguments }`.",
            "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",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "integer"
              },
              "message": {
                "type": "string"
              },
              "data": {}
            }
          }
        }
      },
      "OAuthError": {
        "type": "object",
        "description": "RFC 6749 error object.",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Machine-readable code.",
            "examples": [
              "invalid_token",
              "invalid_grant",
              "slow_down"
            ]
          },
          "error_description": {
            "type": "string",
            "description": "Human-readable detail."
          }
        }
      },
      "ClientRegistrationRequest": {
        "type": "object",
        "required": [
          "redirect_uris"
        ],
        "properties": {
          "client_name": {
            "type": "string",
            "maxLength": 100,
            "description": "Shown to the person on the consent page."
          },
          "redirect_uris": {
            "type": "array",
            "minItems": 1,
            "maxItems": 5,
            "items": {
              "type": "string",
              "format": "uri"
            }
          }
        }
      },
      "ClientRegistration": {
        "type": "object",
        "required": [
          "client_id",
          "redirect_uris",
          "grant_types",
          "response_types",
          "token_endpoint_auth_method",
          "scope"
        ],
        "properties": {
          "client_id": {
            "type": "string"
          },
          "client_id_issued_at": {
            "type": "integer",
            "description": "Unix time."
          },
          "client_name": {
            "type": "string"
          },
          "redirect_uris": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri"
            }
          },
          "grant_types": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "authorization_code",
                "refresh_token"
              ]
            }
          },
          "response_types": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "code"
              ]
            }
          },
          "token_endpoint_auth_method": {
            "type": "string",
            "enum": [
              "none"
            ]
          },
          "scope": {
            "type": "string",
            "enum": [
              "mcp:read"
            ]
          }
        }
      },
      "TokenRequest": {
        "type": "object",
        "required": [
          "grant_type",
          "client_id"
        ],
        "properties": {
          "grant_type": {
            "type": "string",
            "enum": [
              "authorization_code",
              "refresh_token"
            ]
          },
          "client_id": {
            "type": "string"
          },
          "code": {
            "type": "string",
            "description": "Required for `authorization_code`."
          },
          "redirect_uri": {
            "type": "string",
            "format": "uri",
            "description": "Required for `authorization_code`; the one used to authorize."
          },
          "code_verifier": {
            "type": "string",
            "minLength": 43,
            "maxLength": 128,
            "description": "Required for `authorization_code`; the PKCE verifier."
          },
          "refresh_token": {
            "type": "string",
            "description": "Required for `refresh_token`."
          }
        }
      },
      "TokenResponse": {
        "type": "object",
        "required": [
          "access_token",
          "token_type",
          "expires_in",
          "refresh_token",
          "scope"
        ],
        "properties": {
          "access_token": {
            "type": "string",
            "description": "`oat_…`, valid for `expires_in` seconds."
          },
          "token_type": {
            "type": "string",
            "enum": [
              "Bearer"
            ]
          },
          "expires_in": {
            "type": "integer",
            "examples": [
              3600
            ]
          },
          "refresh_token": {
            "type": "string",
            "description": "`ort_…`, rotates on every use."
          },
          "scope": {
            "type": "string",
            "enum": [
              "mcp:read"
            ]
          }
        }
      },
      "RevokeRequest": {
        "type": "object",
        "required": [
          "token"
        ],
        "properties": {
          "token": {
            "type": "string",
            "description": "An access or refresh token."
          }
        }
      },
      "ProtectedResourceMetadata": {
        "type": "object",
        "required": [
          "resource",
          "authorization_servers"
        ],
        "properties": {
          "resource": {
            "type": "string",
            "format": "uri"
          },
          "authorization_servers": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri"
            }
          },
          "scopes_supported": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "bearer_methods_supported": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "header"
              ]
            }
          },
          "resource_name": {
            "type": "string"
          }
        }
      },
      "AuthorizationServerMetadata": {
        "type": "object",
        "required": [
          "issuer",
          "authorization_endpoint",
          "token_endpoint"
        ],
        "properties": {
          "issuer": {
            "type": "string",
            "format": "uri"
          },
          "authorization_endpoint": {
            "type": "string",
            "format": "uri"
          },
          "token_endpoint": {
            "type": "string",
            "format": "uri"
          },
          "registration_endpoint": {
            "type": "string",
            "format": "uri"
          },
          "revocation_endpoint": {
            "type": "string",
            "format": "uri"
          },
          "response_types_supported": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "grant_types_supported": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "code_challenge_methods_supported": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "token_endpoint_auth_methods_supported": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "scopes_supported": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      }
    }
  }
}
