{
    "openapi": "3.1.0",
    "info": {
        "title": "URSY.net API",
        "version": "0.3.2",
        "description": "Direct machine-to-machine commerce. Reading the catalogue and taking a quote are open to any agent: a quote is server-priced, GBP excluding VAT, valid 15 minutes and never a charge. Ordering is not open: POST /orders validates the request, then refuses with ordering_closed and takes no payment. Every error uses the ErrorEnvelope shape.",
        "termsOfService": "https://ursy.net/terms/",
        "contact": {
            "name": "URSY",
            "url": "https://ursy.net/contact/",
            "email": "hello@ursy.com"
        }
    },
    "servers": [
        {
            "url": "https://ursy.net/wp-json/ursy-net/v1"
        }
    ],
    "externalDocs": {
        "description": "llms.txt: how this site asks AI readers to use it",
        "url": "https://ursy.net/llms.txt"
    },
    "tags": [
        {
            "name": "public",
            "description": "Open to any agent, no key."
        },
        {
            "name": "owner",
            "description": "Needs a URSY.ID access token for this resource with the named scope (request one at https://ursy.id/oidc with resource set to this API)."
        },
        {
            "name": "mcp",
            "description": "The same operations as MCP tools (JSON-RPC 2.0 over POST)."
        }
    ],
    "paths": {
        "/status": {
            "get": {
                "operationId": "ursyNetStatus",
                "summary": "Read measured service and release status.",
                "tags": [
                    "public"
                ],
                "security": [],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Envelope"
                                }
                            }
                        }
                    },
                    "405": {
                        "description": "Method not allowed; the Allow header lists what this path accepts",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    },
                    "500": {
                        "description": "Internal error (typed error)",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/capabilities": {
            "get": {
                "operationId": "ursyNetCapabilities",
                "summary": "Read implemented and enabled capabilities.",
                "tags": [
                    "public"
                ],
                "security": [],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Envelope"
                                }
                            }
                        }
                    },
                    "405": {
                        "description": "Method not allowed; the Allow header lists what this path accepts",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    },
                    "500": {
                        "description": "Internal error (typed error)",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/catalogue": {
            "get": {
                "operationId": "ursyNetCatalogue",
                "summary": "Read every offer, transcribed from its owning property. Nothing is purchasable.",
                "tags": [
                    "public"
                ],
                "security": [],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Envelope"
                                }
                            }
                        }
                    },
                    "405": {
                        "description": "Method not allowed; the Allow header lists what this path accepts",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    },
                    "500": {
                        "description": "Internal error (typed error)",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/offers/{offer_id}": {
            "get": {
                "operationId": "ursyNetOffers",
                "summary": "Resolve one offer by its ursy:<property>:<sku> identifier.",
                "tags": [
                    "public"
                ],
                "security": [],
                "parameters": [
                    {
                        "name": "offer_id",
                        "in": "path",
                        "required": true,
                        "description": "e.g. ursy:email:email-solo",
                        "schema": {
                            "type": "string",
                            "pattern": "^[A-Za-z0-9:._-]+$"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Envelope"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Not found (typed error)",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    },
                    "405": {
                        "description": "Method not allowed; the Allow header lists what this path accepts",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    },
                    "500": {
                        "description": "Internal error (typed error)",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/mcp": {
            "post": {
                "operationId": "ursyNetMcp",
                "summary": "Product MCP endpoint (JSON-RPC 2.0 over POST).",
                "tags": [
                    "mcp"
                ],
                "security": [],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/JsonRpcRequest"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/JsonRpcResponse"
                                }
                            }
                        }
                    },
                    "202": {
                        "description": "Notification accepted (no body)"
                    },
                    "400": {
                        "description": "Malformed or invalid request (typed error)",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    },
                    "405": {
                        "description": "Method not allowed; the Allow header lists what this path accepts",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    },
                    "500": {
                        "description": "Internal error (typed error)",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/mandates": {
            "get": {
                "operationId": "ursyNetMandates",
                "summary": "Read bounded delegated spending authority. Requires an owner grant.",
                "tags": [
                    "owner"
                ],
                "security": [
                    {
                        "bearerAuth": [
                            "net.mandates.read"
                        ]
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Envelope"
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "No valid URSY.ID access token for this resource (typed: unauthorized, invalid_token or identity_not_configured)",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "Refused: ordering_closed, or a token without the required scope",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    },
                    "405": {
                        "description": "Method not allowed; the Allow header lists what this path accepts",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    },
                    "500": {
                        "description": "Internal error (typed error)",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    },
                    "503": {
                        "description": "The authorization server could not be reached",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/quotes": {
            "post": {
                "operationId": "ursyNetQuotes",
                "summary": "Create an expiring, server-priced quote. No charge is made.",
                "tags": [
                    "public"
                ],
                "security": [],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "offer_id": {
                                        "type": "string",
                                        "description": "Offer to quote, e.g. ursy:email:email-solo"
                                    },
                                    "quantity": {
                                        "type": "integer",
                                        "description": "Whole number 1-100 (default 1)",
                                        "minimum": 1,
                                        "maximum": 100
                                    }
                                },
                                "additionalProperties": false,
                                "required": [
                                    "offer_id"
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Envelope"
                                }
                            }
                        }
                    },
                    "400": {
                        "description": "Malformed or invalid request (typed error)",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Not found (typed error)",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    },
                    "405": {
                        "description": "Method not allowed; the Allow header lists what this path accepts",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    },
                    "409": {
                        "description": "Conflict: expired quote, quote already ordered, or reused idempotency key",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Rate limited; see Retry-After",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    },
                    "500": {
                        "description": "Internal error (typed error)",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/quotes/{quote_id}": {
            "get": {
                "operationId": "ursyNetQuote",
                "summary": "Read one issued quote and whether it has expired.",
                "tags": [
                    "public"
                ],
                "security": [],
                "parameters": [
                    {
                        "name": "quote_id",
                        "in": "path",
                        "required": true,
                        "description": "Issued quote id",
                        "schema": {
                            "type": "string",
                            "pattern": "^[A-Za-z0-9_-]+$"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Envelope"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Not found (typed error)",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    },
                    "405": {
                        "description": "Method not allowed; the Allow header lists what this path accepts",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    },
                    "500": {
                        "description": "Internal error (typed error)",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/orders": {
            "post": {
                "operationId": "ursyNetOrders",
                "summary": "Place an order against an unexpired quote. Validates fully, then refuses while ordering is closed. No charge is ever made by this route.",
                "tags": [
                    "public"
                ],
                "security": [],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "quote_id": {
                                        "type": "string",
                                        "description": "An unexpired quote id from POST /quotes"
                                    },
                                    "idempotency_key": {
                                        "type": "string",
                                        "description": "Optional client key (1-96 of A-Z a-z 0-9 . _ : -); a retry with the same key returns the same order",
                                        "pattern": "^[A-Za-z0-9._:-]{1,96}$"
                                    }
                                },
                                "additionalProperties": false,
                                "required": [
                                    "quote_id"
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Envelope"
                                }
                            }
                        }
                    },
                    "400": {
                        "description": "Malformed or invalid request (typed error)",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "No valid URSY.ID access token for this resource (typed: unauthorized, invalid_token or identity_not_configured)",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "Refused: ordering_closed, or a token without the required scope",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Not found (typed error)",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    },
                    "405": {
                        "description": "Method not allowed; the Allow header lists what this path accepts",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    },
                    "409": {
                        "description": "Conflict: expired quote, quote already ordered, or reused idempotency key",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    },
                    "500": {
                        "description": "Internal error (typed error)",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/orders/{order_id}": {
            "get": {
                "operationId": "ursyNetOrder",
                "summary": "Read one placed order. Requires an owner grant.",
                "tags": [
                    "owner"
                ],
                "security": [
                    {
                        "bearerAuth": [
                            "net.orders.read"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "order_id",
                        "in": "path",
                        "required": true,
                        "description": "Placed order id",
                        "schema": {
                            "type": "string",
                            "pattern": "^[A-Za-z0-9_-]+$"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Envelope"
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "No valid URSY.ID access token for this resource (typed: unauthorized, invalid_token or identity_not_configured)",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "Refused: ordering_closed, or a token without the required scope",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Not found (typed error)",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    },
                    "405": {
                        "description": "Method not allowed; the Allow header lists what this path accepts",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    },
                    "500": {
                        "description": "Internal error (typed error)",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    },
                    "503": {
                        "description": "The authorization server could not be reached",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    }
                }
            }
        }
    },
    "components": {
        "securitySchemes": {
            "bearerAuth": {
                "type": "http",
                "scheme": "bearer",
                "description": "URSY.ID access token for resource https://ursy.net/wp-json/ursy-net/v1"
            }
        },
        "schemas": {
            "Meta": {
                "type": "object",
                "required": [
                    "service",
                    "version",
                    "request_id"
                ],
                "properties": {
                    "service": {
                        "type": "string",
                        "const": "ursy.net"
                    },
                    "version": {
                        "type": "string"
                    },
                    "request_id": {
                        "type": "string"
                    }
                }
            },
            "Envelope": {
                "type": "object",
                "required": [
                    "data",
                    "meta"
                ],
                "properties": {
                    "data": {
                        "type": "object"
                    },
                    "meta": {
                        "$ref": "#/components/schemas/Meta"
                    },
                    "sponsor": {
                        "type": "object",
                        "description": "Paid placement (disclosure: paid placement). Only on GET /catalogue and GET /offers/{id}, only on sponsor days: message, name, url (a tracked link), sponsored, disclosure, rel, campaign_id, placement_id."
                    },
                    "sponsored_by": {
                        "type": "object",
                        "description": "The sponsor's entry doors, present only with sponsor."
                    }
                }
            },
            "ErrorEnvelope": {
                "type": "object",
                "required": [
                    "error",
                    "meta"
                ],
                "properties": {
                    "error": {
                        "type": "object",
                        "required": [
                            "code",
                            "message"
                        ],
                        "properties": {
                            "code": {
                                "type": "string",
                                "description": "Stable machine code, e.g. offer_not_found, ordering_closed, quote_rate_limited"
                            },
                            "message": {
                                "type": "string"
                            },
                            "meta": {
                                "type": "object"
                            }
                        }
                    },
                    "meta": {
                        "$ref": "#/components/schemas/Meta"
                    }
                }
            },
            "JsonRpcRequest": {
                "type": "object",
                "required": [
                    "jsonrpc",
                    "method"
                ],
                "properties": {
                    "jsonrpc": {
                        "type": "string",
                        "const": "2.0"
                    },
                    "id": {
                        "type": [
                            "string",
                            "integer",
                            "null"
                        ]
                    },
                    "method": {
                        "type": "string",
                        "description": "initialize, tools/list, tools/call, ping, notifications/*"
                    },
                    "params": {
                        "type": "object"
                    }
                }
            },
            "JsonRpcResponse": {
                "type": "object",
                "required": [
                    "jsonrpc"
                ],
                "properties": {
                    "jsonrpc": {
                        "type": "string",
                        "const": "2.0"
                    },
                    "id": {
                        "type": [
                            "string",
                            "integer",
                            "null"
                        ]
                    },
                    "result": {
                        "type": "object"
                    },
                    "error": {
                        "type": "object"
                    }
                }
            }
        }
    }
}