{
  "openapi": "3.1.1",
  "info": {
    "title": "Voxgig Public API",
    "version": "1.0.0",
    "summary": "Read the Voxgig SDK catalog without an API key. The two forms are for people and require a browser challenge.",
    "description": "The public API behind voxgig.com. Everything an agent can usefully call is read-only, needs no authentication, and is safe to call. The two write endpoints, the contact form and the job application form, require a Cloudflare Turnstile token that only a browser can obtain. They are documented here but cannot be called programmatically.\n\nVoxgig is an open-source SDK generator for API-first SaaS. It turns one OpenAPI description into six surfaces: an SDK, a CLI, an MCP Server, Agent Skills, a REPL, and a type-safe Semantic Model. This API exposes the catalog of 600+ SDKs already generated that way, so you can find a worked example shaped like your own API.\n\nErrors are RFC 9457 problem details with media type `application/problem+json`. Every problem carries a stable `code`, and its `type` links to the matching section of https://voxgig.com/developers/errors.\n\nThere is also a Model Context Protocol server at `https://voxgig.com/mcp` if you would rather call Voxgig as a set of tools than as REST.",
    "termsOfService": "https://voxgig.com/notices",
    "contact": {
      "name": "Voxgig",
      "url": "https://voxgig.com/contact",
      "email": "info@voxgig.com"
    },
    "license": {
      "name": "MIT",
      "identifier": "MIT"
    }
  },
  "externalDocs": {
    "description": "Developer and agent resources",
    "url": "https://voxgig.com/developers"
  },
  "servers": [
    {
      "url": "https://voxgig.com",
      "description": "Production"
    }
  ],
  "security": [],
  "tags": [
    {
      "name": "catalog",
      "description": "The Voxgig SDK catalog: 600+ SDKs generated from real public OpenAPI descriptions."
    },
    {
      "name": "contact",
      "description": "Send a message to a human at Voxgig."
    },
    {
      "name": "jobs",
      "description": "Apply for an open role at Voxgig."
    },
    {
      "name": "service",
      "description": "Service health and machine-readable descriptions."
    },
    {
      "name": "agents",
      "description": "Endpoints intended for AI agents."
    }
  ],
  "paths": {
    "/api/sdk/catalog.json": {
      "get": {
        "operationId": "list_sdk_catalog",
        "tags": [
          "catalog"
        ],
        "summary": "Download the whole Voxgig SDK catalog in one JSON document, every generated example SDK with its slug, title, description, documentation URL and GitHub repository.",
        "description": "Use this when you want the full set and intend to filter it yourself. It is a static document, so it is cheap to fetch and safe to cache.\n\nPrefer `search_sdk_catalog` when you are looking for a handful of matches; this document is several hundred kilobytes.",
        "responses": {
          "200": {
            "description": "The complete catalog.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SdkCatalog"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/UnexpectedError"
          }
        }
      }
    },
    "/api/sdk/search": {
      "get": {
        "operationId": "search_sdk_catalog",
        "tags": [
          "catalog",
          "agents"
        ],
        "summary": "Search the Voxgig SDK catalog by keyword and return the matching example SDKs, sorted by title, with a documentation URL and a raw README URL for each.",
        "description": "The keyword is matched against each entry's slug, title, description and topics, case-insensitively. Every whitespace-separated word in `q` must match somewhere in the entry.\n\nUse this endpoint when a user asks whether Voxgig has an example for some kind of API. Follow `readme_url` on a result to read the generated README as markdown.\n\nOmit `q` to page through the whole catalog.",
        "parameters": [
          {
            "$ref": "#/components/parameters/SearchQuery"
          },
          {
            "$ref": "#/components/parameters/SearchLimit"
          },
          {
            "$ref": "#/components/parameters/SearchOffset"
          }
        ],
        "responses": {
          "200": {
            "description": "Matching catalog entries.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SdkSearchResult"
                },
                "example": {
                  "query": "weather",
                  "total": 12,
                  "limit": 2,
                  "offset": 0,
                  "results": [
                    {
                      "slug": "realtime-weather-sdk",
                      "title": "Realtime Weather SDK",
                      "description": "Generated SDK for the Realtime Weather API.",
                      "url": "https://voxgig.com/voxgig-sdk/realtime-weather-sdk",
                      "readme_url": "https://voxgig.com/voxgig-sdk/realtime-weather-sdk.md",
                      "github_url": "https://github.com/voxgig-sdk/realtime-weather-sdk",
                      "topics": [
                        "weather"
                      ]
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "default": {
            "$ref": "#/components/responses/UnexpectedError"
          }
        }
      }
    },
    "/api/sdk/{slug}.json": {
      "get": {
        "operationId": "get_sdk_entry",
        "tags": [
          "catalog"
        ],
        "summary": "Fetch one Voxgig catalog entry by its slug, returning its title, description, topics, documentation URL, raw README URL and GitHub repository.",
        "description": "Slugs come from `search_sdk_catalog` or `list_sdk_catalog`. They always end in `-sdk`. The response does not embed the README; follow `readme_url` for that, which returns `text/markdown`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/SdkSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "The catalog entry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SdkEntry"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "default": {
            "$ref": "#/components/responses/UnexpectedError"
          }
        }
      }
    },
    "/voxgig-sdk/{slug}.md": {
      "get": {
        "operationId": "get_sdk_readme",
        "tags": [
          "catalog",
          "agents"
        ],
        "summary": "Fetch the generated README for one catalog SDK as raw markdown, which is the fastest way to see what the generated code looks like.",
        "description": "Returns `text/markdown`. This is the same README published in the SDK's GitHub repository, served without any HTML around it.",
        "parameters": [
          {
            "$ref": "#/components/parameters/SdkSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "The README, as markdown.",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "default": {
            "$ref": "#/components/responses/UnexpectedError"
          }
        }
      }
    },
    "/api/contact": {
      "post": {
        "operationId": "submit_contact_message",
        "tags": [
          "contact"
        ],
        "summary": "Send a message to a human at Voxgig about the SDK generator, an API Experience engagement, or a podcast pitch, and get a reply within 48 hours.",
        "description": "Do not call this on a user's behalf without their explicit say-so, and never use it for unsolicited marketing. It reaches a real inbox.\n\nAll three fields are required. `message` is capped at 5000 characters. There is no rate limit published, but abuse is blocked at the edge.\n\nThis endpoint is not usable programmatically. It requires a Cloudflare Turnstile token, which only a browser solving the challenge on /contact can obtain, and a request without one is refused with 403 `challenge_failed`. To put someone in touch with Voxgig, give them the URL, or the email addresses published on /contact.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactMessage"
              },
              "example": {
                "name": "Ada Lovelace",
                "email": "ada@example.com",
                "message": "We have an OpenAPI 3.1 spec and need Go and Python SDKs plus an MCP server. What does an engagement look like?"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The message was accepted and will be answered by a person.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContactAccepted"
                },
                "example": {
                  "ok": true
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/MissingFields"
          },
          "403": {
            "$ref": "#/components/responses/ChallengeFailed"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "default": {
            "$ref": "#/components/responses/UnexpectedError"
          }
        }
      }
    },
    "/api/jobs": {
      "post": {
        "operationId": "submit_job_application",
        "tags": [
          "jobs"
        ],
        "summary": "Apply for one of the open roles at Voxgig, listed at /jobs, by sending your name, your email address, links to your work, and an introduction.",
        "description": "Never submit an application on someone's behalf. It reaches a real inbox and a person reads every one, so an application nobody asked you to send wastes that person's time and the candidate's.\n\n`role` must be the slug of an open role. The open slugs are published on https://voxgig.com/jobs, and an unknown one is refused with 400 `invalid_request` and the open list in `open_roles`.\n\nAll five fields are required, and each must be a string. `links` is capped at 2000 characters and `message` at 5000. A malformed application is a 400, a missing or spent challenge token a 403, and too many submissions from one address a 429. Every one carries a `code` saying which rule it broke.\n\nThis endpoint is not usable programmatically. It requires a Cloudflare Turnstile token, which only a browser solving the challenge on /jobs can obtain, and a request without one is refused with 403 `challenge_failed`. Give the candidate the URL instead.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JobApplication"
              },
              "example": {
                "name": "Ada Lovelace",
                "email": "ada@example.com",
                "role": "junior-developer-relations-engineer",
                "links": "https://github.com/example\nhttps://example.com/blog",
                "message": "I maintain a small OpenAPI linter, and I write up what breaks when people hand-roll clients. DevRel interests me because the explaining is the part I enjoy most."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The application was accepted and will be read by a person.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContactAccepted"
                },
                "example": {
                  "ok": true
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/JobApplicationRejected"
          },
          "403": {
            "$ref": "#/components/responses/ChallengeFailed"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "default": {
            "$ref": "#/components/responses/UnexpectedError"
          }
        }
      }
    },
    "/api/health": {
      "get": {
        "operationId": "get_service_health",
        "tags": [
          "service"
        ],
        "summary": "Check that the Voxgig site is up, and read the size of the SDK catalog and the links to every machine-readable description it publishes.",
        "description": "Always returns 200 when the site is up. Use it as a liveness probe and as a discovery document: the `links` member points at the OpenAPI description, the MCP endpoint and llms.txt.",
        "responses": {
          "200": {
            "description": "The service is up.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HealthStatus"
                },
                "example": {
                  "status": "ok",
                  "service": "voxgig.com",
                  "version": "1.0.0",
                  "sdk_count": 637,
                  "links": {
                    "openapi": "https://voxgig.com/openapi.json",
                    "mcp": "https://voxgig.com/mcp",
                    "llms_txt": "https://voxgig.com/llms.txt",
                    "developers": "https://voxgig.com/developers"
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/UnexpectedError"
          }
        }
      }
    },
    "/mcp": {
      "post": {
        "operationId": "call_mcp_server",
        "tags": [
          "agents"
        ],
        "summary": "Call the Voxgig Model Context Protocol server over Streamable HTTP with a JSON-RPC 2.0 message, exposing the SDK catalog and product documentation as tools.",
        "description": "A stateless MCP endpoint. It supports `initialize`, `notifications/initialized`, `tools/list`, `tools/call`, `ping` and `server/discover`, and it does not require the handshake first: a cold `tools/list` works.\n\nSend `Content-Type: application/json` and `Accept: application/json, text/event-stream`. Responses are single JSON objects, with no session id issued or expected. Notifications are answered with 202 and an empty body.\n\nMost callers should point an MCP client at this URL rather than speak JSON-RPC by hand.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JsonRpcRequest"
              },
              "example": {
                "jsonrpc": "2.0",
                "id": 1,
                "method": "tools/list"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A JSON-RPC response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcResponse"
                }
              }
            }
          },
          "202": {
            "description": "A JSON-RPC notification was accepted. No body."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "403": {
            "description": "The Origin header was rejected, per the MCP transport security rules. A request with a non-safe method and no Content-Type at all is also refused here, by the site-wide cross-site check, so always send Content-Type: application/json.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "default": {
            "$ref": "#/components/responses/UnexpectedError"
          }
        }
      }
    },
    "/openapi.json": {
      "get": {
        "operationId": "get_openapi_description",
        "tags": [
          "service"
        ],
        "summary": "Fetch this OpenAPI 3.1 description of the Voxgig public API, which is the document you are reading.",
        "description": "The same document is available as YAML at `/openapi.yaml`.",
        "responses": {
          "200": {
            "description": "The OpenAPI description.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/llms.txt": {
      "get": {
        "operationId": "get_llms_txt",
        "tags": [
          "agents"
        ],
        "summary": "Fetch the llms.txt index for voxgig.com, a short markdown summary of what Voxgig is, when an agent should use it, and where the machine-readable resources live.",
        "description": "Follows the llmstxt.org format. `/llms-full.txt` is the long form and lists every SDK in the catalog.",
        "responses": {
          "200": {
            "description": "The llms.txt document.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/sdk/voxgig-sdk.csv": {
      "get": {
        "operationId": "get_sdk_catalog_csv",
        "tags": [
          "catalog"
        ],
        "summary": "Download the Voxgig SDK catalog as CSV, one row per SDK, with the upstream API's own website and OpenAPI URL where the README records them.",
        "description": "Columns: slug, title, voxgig_url, github_url, freepublicapis_url, source_openapi_url, source_website.",
        "responses": {
          "200": {
            "description": "The catalog, as CSV.",
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "parameters": {
      "SdkSlug": {
        "name": "slug",
        "in": "path",
        "required": true,
        "description": "The catalog slug of the SDK, as returned by search_sdk_catalog. Always ends in `-sdk`.",
        "schema": {
          "type": "string",
          "pattern": "^[a-z0-9][a-z0-9-]*$",
          "minLength": 3,
          "maxLength": 120,
          "examples": [
            "openaq-platform-sdk",
            "github-rest-sdk"
          ]
        }
      },
      "SearchQuery": {
        "name": "q",
        "in": "query",
        "required": false,
        "description": "Keywords to match against slug, title, description and topics. Every whitespace-separated word must match. Omit to page through everything.",
        "schema": {
          "type": "string",
          "maxLength": 200,
          "examples": [
            "weather",
            "open data"
          ]
        }
      },
      "SearchLimit": {
        "name": "limit",
        "in": "query",
        "required": false,
        "description": "Maximum number of results to return.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 100,
          "default": 20
        }
      },
      "SearchOffset": {
        "name": "offset",
        "in": "query",
        "required": false,
        "description": "Number of matches to skip, for paging.",
        "schema": {
          "type": "integer",
          "minimum": 0,
          "default": 0
        }
      }
    },
    "schemas": {
      "SdkEntry": {
        "title": "SDK catalog entry",
        "description": "One generated example SDK in the Voxgig catalog.",
        "type": "object",
        "properties": {
          "slug": {
            "type": "string",
            "description": "Stable identifier, and the last path segment of the documentation URL.",
            "examples": [
              "openaq-platform-sdk"
            ]
          },
          "title": {
            "type": "string",
            "description": "Human-readable name of the SDK.",
            "examples": [
              "OpenAQ Platform SDK"
            ]
          },
          "description": {
            "type": "string",
            "description": "One-line summary of the upstream API the SDK wraps."
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Documentation page for this SDK on voxgig.com."
          },
          "readme_url": {
            "type": "string",
            "format": "uri",
            "description": "The generated README as raw markdown."
          },
          "github_url": {
            "type": "string",
            "format": "uri",
            "description": "The generated SDK's GitHub repository."
          },
          "topics": {
            "type": "array",
            "description": "Topic tags taken from the generated repository.",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "slug",
          "title",
          "url",
          "readme_url",
          "github_url"
        ],
        "additionalProperties": false
      },
      "SdkCatalog": {
        "title": "SDK catalog",
        "description": "The complete Voxgig SDK catalog.",
        "type": "object",
        "properties": {
          "generated_at": {
            "type": "string",
            "format": "date-time",
            "description": "When this catalog was generated."
          },
          "count": {
            "type": "integer",
            "description": "Number of entries."
          },
          "sdks": {
            "type": "array",
            "description": "Every catalog entry, sorted by title.",
            "items": {
              "$ref": "#/components/schemas/SdkEntry"
            }
          }
        },
        "required": [
          "count",
          "sdks"
        ],
        "additionalProperties": false
      },
      "SdkSearchResult": {
        "title": "SDK search result",
        "description": "A page of catalog entries matching a keyword search.",
        "type": "object",
        "properties": {
          "query": {
            "type": "string",
            "description": "The keywords that were searched for."
          },
          "total": {
            "type": "integer",
            "description": "Total number of matches, before paging."
          },
          "limit": {
            "type": "integer",
            "description": "The limit that was applied."
          },
          "offset": {
            "type": "integer",
            "description": "The offset that was applied."
          },
          "results": {
            "type": "array",
            "description": "This page of matches, sorted by title.",
            "items": {
              "$ref": "#/components/schemas/SdkEntry"
            }
          }
        },
        "required": [
          "query",
          "total",
          "limit",
          "offset",
          "results"
        ],
        "additionalProperties": false
      },
      "HealthStatus": {
        "title": "Health status",
        "description": "Liveness of voxgig.com, plus links to its machine-readable descriptions.",
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "ok"
            ],
            "description": "Always `ok` when the service answers."
          },
          "service": {
            "type": "string",
            "description": "The service that answered."
          },
          "version": {
            "type": "string",
            "description": "Version of the deployed site."
          },
          "sdk_count": {
            "type": "integer",
            "description": "Number of SDKs in the catalog."
          },
          "links": {
            "type": "object",
            "description": "Absolute URLs of the machine-readable resources.",
            "properties": {
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "mcp": {
                "type": "string",
                "format": "uri"
              },
              "llms_txt": {
                "type": "string",
                "format": "uri"
              },
              "developers": {
                "type": "string",
                "format": "uri"
              }
            },
            "additionalProperties": false
          }
        },
        "required": [
          "status",
          "service",
          "links"
        ],
        "additionalProperties": false
      },
      "ContactMessage": {
        "title": "Contact message",
        "description": "A message for a human at Voxgig.",
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 256,
            "description": "Who is writing. A real name, not a company name."
          },
          "email": {
            "type": "string",
            "format": "email",
            "minLength": 3,
            "maxLength": 256,
            "description": "Where the reply should go."
          },
          "message": {
            "type": "string",
            "minLength": 1,
            "maxLength": 5000,
            "description": "What you want. Be specific; a person reads this."
          }
        },
        "required": [
          "name",
          "email",
          "message"
        ],
        "additionalProperties": false
      },
      "ContactAccepted": {
        "title": "Contact accepted",
        "description": "Acknowledgment that the message was received.",
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean",
            "enum": [
              true
            ],
            "description": "Always true on success."
          }
        },
        "required": [
          "ok"
        ],
        "additionalProperties": false
      },
      "JobApplication": {
        "title": "Job application",
        "description": "An application for one of the open roles listed at /jobs.",
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 256,
            "description": "Who is applying. A real name."
          },
          "email": {
            "type": "string",
            "format": "email",
            "minLength": 3,
            "maxLength": 256,
            "description": "Where the reply should go."
          },
          "role": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "description": "Slug of the role you are applying for. It must be an open role, and the open roles are listed at /jobs.",
            "examples": [
              "junior-developer-relations-engineer"
            ]
          },
          "links": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2000,
            "description": "Links to your work: GitHub, writing, talks, projects. One per line."
          },
          "message": {
            "type": "string",
            "minLength": 1,
            "maxLength": 5000,
            "description": "Your introduction: why the role, what you have built, and how you help other developers."
          }
        },
        "required": [
          "name",
          "email",
          "role",
          "links",
          "message"
        ],
        "additionalProperties": false
      },
      "JsonRpcRequest": {
        "title": "JSON-RPC request",
        "description": "A JSON-RPC 2.0 request or notification, as used by MCP.",
        "type": "object",
        "properties": {
          "jsonrpc": {
            "type": "string",
            "enum": [
              "2.0"
            ],
            "description": "Always the string \"2.0\"."
          },
          "id": {
            "description": "Correlates the response with this request. Omit for a notification.",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "integer"
              }
            ]
          },
          "method": {
            "type": "string",
            "description": "The MCP method to call.",
            "examples": [
              "tools/list",
              "tools/call",
              "initialize",
              "ping",
              "server/discover"
            ]
          },
          "params": {
            "type": "object",
            "description": "Method parameters."
          }
        },
        "required": [
          "jsonrpc",
          "method"
        ],
        "additionalProperties": false
      },
      "JsonRpcResponse": {
        "title": "JSON-RPC response",
        "description": "A JSON-RPC 2.0 response. Exactly one of result or error is present.",
        "type": "object",
        "properties": {
          "jsonrpc": {
            "type": "string",
            "enum": [
              "2.0"
            ],
            "description": "Always the string \"2.0\"."
          },
          "id": {
            "description": "The id of the request this answers. Null when the request was unparsable.",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ]
          },
          "result": {
            "type": "object",
            "description": "The method's result. Present only on success."
          },
          "error": {
            "type": "object",
            "description": "The failure. Present only when the call did not succeed.",
            "properties": {
              "code": {
                "type": "integer",
                "description": "JSON-RPC 2.0 error code."
              },
              "message": {
                "type": "string",
                "description": "Short description of the failure."
              },
              "data": {
                "description": "Extra context, shape depends on the code."
              }
            },
            "required": [
              "code",
              "message"
            ]
          }
        },
        "required": [
          "jsonrpc",
          "id"
        ],
        "additionalProperties": false
      },
      "Problem": {
        "title": "Problem details",
        "description": "An RFC 9457 problem details object. RFC 9457 requires no member, but this API always sends type, title, status and code, and usually detail and resolution. Ignore members you do not recognize.",
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "format": "uri",
            "default": "about:blank",
            "description": "URI identifying the problem type. Dereferences to the matching section of the error reference.",
            "examples": [
              "https://voxgig.com/developers/errors#missing_fields"
            ]
          },
          "title": {
            "type": "string",
            "description": "Short, human-readable summary, stable for a given code.",
            "examples": [
              "Missing required fields"
            ]
          },
          "status": {
            "type": "integer",
            "minimum": 100,
            "maximum": 599,
            "description": "The HTTP status code. Always matches the actual response status."
          },
          "code": {
            "type": "string",
            "description": "Stable machine-readable identifier for the problem type.",
            "enum": [
              "not_found",
              "not_acceptable",
              "method_not_allowed",
              "unsupported_media_type",
              "invalid_request",
              "invalid_json",
              "missing_fields",
              "payload_too_large",
              "unauthorized",
              "challenge_failed",
              "rate_limited",
              "server_error"
            ]
          },
          "detail": {
            "type": "string",
            "description": "Explanation specific to this occurrence. Do not parse it."
          },
          "instance": {
            "type": "string",
            "format": "uri-reference",
            "description": "The path this problem occurred on."
          },
          "resolution": {
            "type": "string",
            "description": "One sentence telling you how to succeed next time."
          },
          "errors": {
            "type": "array",
            "description": "Field-level failures, when the problem is a validation failure.",
            "items": {
              "type": "object",
              "properties": {
                "field": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              },
              "required": [
                "field",
                "message"
              ]
            }
          }
        },
        "required": [
          "type",
          "title",
          "status",
          "code"
        ],
        "additionalProperties": true
      }
    },
    "responses": {
      "NotFound": {
        "description": "The resource does not exist.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "example": {
              "type": "https://voxgig.com/developers/errors#not_found",
              "title": "Not found",
              "status": 404,
              "code": "not_found",
              "detail": "No catalog entry has that slug.",
              "resolution": "Check the slug with search_sdk_catalog, then retry."
            }
          }
        }
      },
      "BadRequest": {
        "description": "A parameter was malformed or out of range.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "example": {
              "type": "https://voxgig.com/developers/errors#invalid_request",
              "title": "Invalid request",
              "status": 400,
              "code": "invalid_request",
              "detail": "limit must be an integer between 1 and 100.",
              "resolution": "Correct the parameter and retry."
            }
          }
        }
      },
      "MissingFields": {
        "description": "A required field was absent or empty.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "example": {
              "type": "https://voxgig.com/developers/errors#missing_fields",
              "title": "Missing required fields",
              "status": 400,
              "code": "missing_fields",
              "detail": "name, email and message are all required.",
              "resolution": "Supply every field listed in errors, then retry.",
              "errors": [
                {
                  "field": "email",
                  "message": "required"
                }
              ]
            }
          }
        }
      },
      "JobApplicationRejected": {
        "description": "The application was rejected before it reached a person. A required field was missing or not a string (`missing_fields`, `invalid_request`), the JSON did not parse (`invalid_json`), or `role` is not an open role (`invalid_request`, with the open slugs in `open_roles`).",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "examples": {
              "missing_fields": {
                "summary": "A required field was absent or empty",
                "value": {
                  "type": "https://voxgig.com/developers/errors#missing_fields",
                  "title": "Missing required fields",
                  "status": 400,
                  "code": "missing_fields",
                  "detail": "A required field is missing, or the email address does not look valid.",
                  "instance": "/api/jobs",
                  "resolution": "Supply every field listed in errors, then retry.",
                  "errors": [
                    {
                      "field": "links",
                      "message": "required"
                    }
                  ]
                }
              },
              "closed_role": {
                "summary": "The role named is not open",
                "value": {
                  "type": "https://voxgig.com/developers/errors#invalid_request",
                  "title": "Invalid request",
                  "status": 400,
                  "code": "invalid_request",
                  "detail": "\"senior-architect\" is not an open role.",
                  "instance": "/api/jobs",
                  "resolution": "Send one of the slugs in open_roles. The open roles are listed at https://voxgig.com/jobs.",
                  "open_roles": [
                    "junior-developer-relations-engineer"
                  ]
                }
              },
              "invalid_json": {
                "summary": "The body was declared as JSON but did not parse",
                "value": {
                  "type": "https://voxgig.com/developers/errors#invalid_json",
                  "title": "Invalid JSON",
                  "status": 400,
                  "code": "invalid_json",
                  "detail": "The request body was declared as JSON but did not parse.",
                  "instance": "/api/jobs",
                  "resolution": "Check for a truncated body or a trailing comma, then retry."
                }
              }
            }
          }
        }
      },
      "ChallengeFailed": {
        "description": "The submission carried no valid Cloudflare Turnstile token. The two forms are for people, not for programmatic callers.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "example": {
              "type": "https://voxgig.com/developers/errors#challenge_failed",
              "title": "Challenge failed",
              "status": 403,
              "code": "challenge_failed",
              "detail": "The challenge token was missing, already used, or had expired.",
              "resolution": "Submit the form in a browser, where the challenge is solved for you, or email info@voxgig.com."
            }
          }
        }
      },
      "RateLimited": {
        "description": "Too many submissions from one address in a short window.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "example": {
              "type": "https://voxgig.com/developers/errors#rate_limited",
              "title": "Too many requests",
              "status": 429,
              "code": "rate_limited",
              "detail": "Too many submissions from this address in a short window.",
              "resolution": "Wait a minute and submit again."
            }
          }
        }
      },
      "MethodNotAllowed": {
        "description": "The endpoint does not accept that HTTP method.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "example": {
              "type": "https://voxgig.com/developers/errors#method_not_allowed",
              "title": "Method not allowed",
              "status": 405,
              "code": "method_not_allowed",
              "detail": "This endpoint only accepts POST.",
              "resolution": "Retry with one of: POST."
            }
          }
        }
      },
      "PayloadTooLarge": {
        "description": "The request body is larger than the endpoint accepts.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "example": {
              "type": "https://voxgig.com/developers/errors#payload_too_large",
              "title": "Payload too large",
              "status": 413,
              "code": "payload_too_large",
              "detail": "message is limited to 5000 characters.",
              "resolution": "Shorten the body and retry."
            }
          }
        }
      },
      "UnsupportedMediaType": {
        "description": "The request Content-Type cannot be parsed.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "example": {
              "type": "https://voxgig.com/developers/errors#unsupported_media_type",
              "title": "Unsupported media type",
              "status": 415,
              "code": "unsupported_media_type",
              "detail": "Send application/json, application/x-www-form-urlencoded or multipart/form-data.",
              "resolution": "Set a supported Content-Type and retry."
            }
          }
        }
      },
      "UnexpectedError": {
        "description": "An unexpected error.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "example": {
              "type": "https://voxgig.com/developers/errors#server_error",
              "title": "Server error",
              "status": 500,
              "code": "server_error",
              "detail": "Something failed on our side.",
              "resolution": "Retry once after a short pause."
            }
          }
        }
      }
    }
  }
}