{
    "openapi": "3.1.0",
    "info": {
        "title": "ChristianPicks AI Discovery API",
        "description": "Public REST API for LLM agents. Search, look up, and compare Christian software and faith-based businesses. Catalog GET endpoints require no authentication. Optional anonymous catalog:read tokens are available through /oauth/token.",
        "version": "1.0.0",
        "contact": {
            "name": "ChristianPicks",
            "email": "contact@christianpicks.com",
            "url": "https://christianpicks.com/developers"
        }
    },
    "servers": [
        {
            "url": "https://christianpicks.com",
            "description": "Production"
        },
        {
            "url": "https://christianpicks.com",
            "description": "Sandbox (read-only catalog; send X-Sandbox: true or use /api/sandbox)"
        }
    ],
    "tags": [
        {
            "name": "Catalog",
            "description": "Public read-only directory data"
        },
        {
            "name": "Ask",
            "description": "Grounded question answering"
        },
        {
            "name": "Auth",
            "description": "Anonymous agent registration"
        }
    ],
    "paths": {
        "/api": {
            "get": {
                "operationId": "getCatalogManifest",
                "tags": [
                    "Catalog"
                ],
                "summary": "Site manifest",
                "description": "Returns catalog statistics, endpoint links, and featured listings.",
                "responses": [
                    {
                        "description": "Manifest with statistics and endpoint links",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object"
                                }
                            }
                        }
                    }
                ]
            }
        },
        "/api/v1": {
            "get": {
                "operationId": "getCatalogManifestV1",
                "tags": [
                    "Catalog"
                ],
                "summary": "Versioned site manifest",
                "description": "Alias of GET /api for agents that look for /api/v1.",
                "responses": [
                    {
                        "description": "Manifest with statistics and endpoint links",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object"
                                }
                            }
                        }
                    }
                ]
            }
        },
        "/api/sandbox": {
            "get": {
                "operationId": "getSandboxManifest",
                "tags": [
                    "Catalog"
                ],
                "summary": "Sandbox catalog manifest",
                "description": "Read-only sandbox alias of the catalog manifest. Never writes production data.",
                "responses": [
                    {
                        "description": "Sandbox manifest",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object"
                                }
                            }
                        }
                    }
                ]
            }
        },
        "/api/ai": {
            "get": {
                "operationId": "getAiManifest",
                "tags": [
                    "Catalog"
                ],
                "summary": "AI discovery manifest",
                "description": "Structured JSON catalog entry point for LLM crawlers.",
                "responses": [
                    {
                        "description": "Manifest with statistics and endpoint links",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object"
                                }
                            }
                        }
                    }
                ]
            }
        },
        "/api/ai/products": {
            "get": {
                "operationId": "listProducts",
                "tags": [
                    "Catalog"
                ],
                "summary": "Paginated products",
                "description": "List active Christian software and digital products.",
                "responses": [
                    {
                        "description": "Paginated product list",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object"
                                }
                            }
                        }
                    }
                ],
                "parameters": [
                    {
                        "name": "page",
                        "in": "query",
                        "schema": {
                            "type": "integer",
                            "minimum": 1
                        },
                        "description": "Page number"
                    },
                    {
                        "name": "per_page",
                        "in": "query",
                        "schema": {
                            "type": "integer",
                            "minimum": 1,
                            "maximum": 100
                        },
                        "description": "Results per page"
                    }
                ]
            }
        },
        "/api/ai/products/{slug}": {
            "get": {
                "operationId": "getProduct",
                "tags": [
                    "Catalog"
                ],
                "summary": "Single product",
                "description": "Return one active product by slug.",
                "responses": [
                    {
                        "description": "Product payload",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object"
                                }
                            }
                        }
                    },
                    {
                        "description": "Product not found",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        }
                    }
                ],
                "parameters": [
                    {
                        "name": "slug",
                        "in": "path",
                        "required": true,
                        "description": "Listing slug",
                        "schema": {
                            "type": "string"
                        }
                    }
                ]
            }
        },
        "/api/ai/businesses": {
            "get": {
                "operationId": "listBusinesses",
                "tags": [
                    "Catalog"
                ],
                "summary": "Paginated businesses",
                "description": "List active Christian-owned businesses.",
                "responses": [
                    {
                        "description": "Paginated business list",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object"
                                }
                            }
                        }
                    }
                ],
                "parameters": [
                    {
                        "name": "page",
                        "in": "query",
                        "schema": {
                            "type": "integer",
                            "minimum": 1
                        },
                        "description": "Page number"
                    },
                    {
                        "name": "per_page",
                        "in": "query",
                        "schema": {
                            "type": "integer",
                            "minimum": 1,
                            "maximum": 100
                        },
                        "description": "Results per page"
                    }
                ]
            }
        },
        "/api/ai/businesses/{slug}": {
            "get": {
                "operationId": "getBusiness",
                "tags": [
                    "Catalog"
                ],
                "summary": "Single business",
                "description": "Return one active business by slug.",
                "responses": [
                    {
                        "description": "Business payload",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object"
                                }
                            }
                        }
                    },
                    {
                        "description": "Business not found",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        }
                    }
                ],
                "parameters": [
                    {
                        "name": "slug",
                        "in": "path",
                        "required": true,
                        "description": "Listing slug",
                        "schema": {
                            "type": "string"
                        }
                    }
                ]
            }
        },
        "/api/ai/blog": {
            "get": {
                "operationId": "listBlogPosts",
                "tags": [
                    "Catalog"
                ],
                "summary": "Paginated blog posts",
                "description": "List published ChristianPicks articles.",
                "responses": [
                    {
                        "description": "Paginated blog post list",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object"
                                }
                            }
                        }
                    }
                ],
                "parameters": [
                    {
                        "name": "page",
                        "in": "query",
                        "schema": {
                            "type": "integer",
                            "minimum": 1
                        },
                        "description": "Page number"
                    },
                    {
                        "name": "per_page",
                        "in": "query",
                        "schema": {
                            "type": "integer",
                            "minimum": 1,
                            "maximum": 100
                        },
                        "description": "Results per page"
                    }
                ]
            }
        },
        "/api/ai/categories": {
            "get": {
                "operationId": "listCategories",
                "tags": [
                    "Catalog"
                ],
                "summary": "Category tree",
                "description": "Return the product category hierarchy.",
                "responses": [
                    {
                        "description": "Category hierarchy",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object"
                                }
                            }
                        }
                    }
                ]
            }
        },
        "/api/ai/search": {
            "get": {
                "operationId": "searchCatalog",
                "tags": [
                    "Catalog"
                ],
                "summary": "LLM-friendly search",
                "description": "Search products and businesses. Requires q with at least 2 characters.",
                "responses": [
                    {
                        "description": "Search results",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object"
                                }
                            }
                        }
                    }
                ],
                "parameters": [
                    {
                        "name": "q",
                        "in": "query",
                        "required": true,
                        "description": "Search query",
                        "schema": {
                            "type": "string",
                            "minLength": 2
                        }
                    }
                ]
            }
        },
        "/api/ai/reports/comparison": {
            "get": {
                "operationId": "compareProducts",
                "tags": [
                    "Catalog"
                ],
                "summary": "Compare ChristianPicks products",
                "description": "Basic tier is free. When premium payments are configured, full tier returns a payment challenge when no valid x402 payment is attached.",
                "parameters": [
                    {
                        "name": "products",
                        "in": "query",
                        "required": true,
                        "style": "form",
                        "explode": false,
                        "schema": {
                            "type": "array",
                            "minItems": 2,
                            "maxItems": 5,
                            "items": {
                                "type": "string"
                            }
                        },
                        "description": "Two to five active product slugs."
                    },
                    {
                        "name": "tier",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "basic",
                                "full"
                            ],
                            "default": "basic"
                        }
                    },
                    {
                        "name": "goal",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "maxLength": 500
                        },
                        "description": "Optional decision goal to carry into the report."
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Free basic or paid full comparison report",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object"
                                }
                            }
                        },
                        "headers": {
                            "PAYMENT-RESPONSE": {
                                "description": "Base64-encoded x402 settlement receipt for a paid full report.",
                                "schema": {
                                    "type": "string"
                                }
                            }
                        }
                    },
                    "402": {
                        "description": "Payment required for the full tier",
                        "headers": {
                            "PAYMENT-REQUIRED": {
                                "description": "Base64-encoded x402 PaymentRequired object.",
                                "schema": {
                                    "type": "string"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "One or more active products were not found",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        }
                    },
                    "503": {
                        "description": "Premium payment service is unavailable or not configured",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/ask": {
            "post": {
                "operationId": "askChristianPicks",
                "tags": [
                    "Ask"
                ],
                "summary": "Ask ChristianPicks",
                "description": "Answer a natural-language question grounded in catalog listings. Set prefer.streaming=true for NLWeb SSE (start, result, complete).",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "query"
                                ],
                                "properties": {
                                    "query": {
                                        "type": "string",
                                        "minLength": 3,
                                        "maxLength": 500
                                    },
                                    "prefer": {
                                        "type": "object",
                                        "properties": {
                                            "streaming": {
                                                "type": "boolean"
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Answer, recommendations, and sources",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object"
                                }
                            },
                            "text/event-stream": {
                                "schema": {
                                    "type": "string"
                                }
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "Idempotency-Key",
                        "in": "header",
                        "required": false,
                        "description": "Client-supplied key so retries of this write do not create duplicate records. Reuse the same key and body for 24 hours.",
                        "schema": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 255
                        }
                    }
                ]
            },
            "get": {
                "operationId": "askChristianPicksGet",
                "tags": [
                    "Ask"
                ],
                "summary": "Ask ChristianPicks (GET)",
                "description": "GET variant of Ask ChristianPicks for NLWeb clients. Use prefer.streaming=true for SSE.",
                "parameters": [
                    {
                        "name": "query",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "minLength": 3
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Answer or SSE stream"
                    }
                }
            }
        },
        "/oauth/register": {
            "post": {
                "operationId": "registerAgentClient",
                "tags": [
                    "Auth"
                ],
                "summary": "Register an anonymous agent client",
                "description": "RFC 7591-style registration advertised as agent_auth.register_uri. Returns a client_id for client_credentials.",
                "responses": {
                    "201": {
                        "description": "Registered client"
                    }
                },
                "parameters": [
                    {
                        "name": "Idempotency-Key",
                        "in": "header",
                        "required": false,
                        "description": "Client-supplied key so retries of this write do not create duplicate records. Reuse the same key and body for 24 hours.",
                        "schema": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 255
                        }
                    }
                ]
            },
            "options": {
                "operationId": "optionsRegisterAgentClient",
                "tags": [
                    "Auth"
                ],
                "summary": "CORS preflight for register_uri",
                "responses": {
                    "204": {
                        "description": "No content"
                    }
                }
            }
        },
        "/oauth/token": {
            "post": {
                "operationId": "createAgentToken",
                "tags": [
                    "Auth"
                ],
                "summary": "Issue an anonymous catalog:read token",
                "responses": {
                    "200": {
                        "description": "Access token"
                    }
                },
                "parameters": [
                    {
                        "name": "Idempotency-Key",
                        "in": "header",
                        "required": false,
                        "description": "Client-supplied key so retries of this write do not create duplicate records. Reuse the same key and body for 24 hours.",
                        "schema": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 255
                        }
                    }
                ]
            },
            "options": {
                "operationId": "optionsCreateAgentToken",
                "tags": [
                    "Auth"
                ],
                "summary": "CORS preflight for the token endpoint",
                "responses": {
                    "204": {
                        "description": "No content"
                    }
                }
            }
        },
        "/oauth/claim": {
            "get": {
                "operationId": "claimAnonymousIdentity",
                "tags": [
                    "Auth"
                ],
                "summary": "Claim step for anonymous agents",
                "description": "Anonymous access does not require a human claim. Returns status=not_required.",
                "responses": {
                    "200": {
                        "description": "Claim is not required"
                    }
                }
            },
            "post": {
                "operationId": "claimAnonymousIdentityPost",
                "tags": [
                    "Auth"
                ],
                "summary": "Claim step for anonymous agents (POST)",
                "responses": {
                    "200": {
                        "description": "Claim is not required"
                    }
                },
                "parameters": [
                    {
                        "name": "Idempotency-Key",
                        "in": "header",
                        "required": false,
                        "description": "Client-supplied key so retries of this write do not create duplicate records. Reuse the same key and body for 24 hours.",
                        "schema": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 255
                        }
                    }
                ]
            },
            "options": {
                "operationId": "optionsClaimAnonymousIdentity",
                "tags": [
                    "Auth"
                ],
                "summary": "CORS preflight for claim_uri",
                "responses": {
                    "204": {
                        "description": "No content"
                    }
                }
            }
        },
        "/oauth/revoke": {
            "post": {
                "operationId": "revokeAgentToken",
                "tags": [
                    "Auth"
                ],
                "summary": "Revoke an anonymous catalog token",
                "responses": {
                    "200": {
                        "description": "Token revoked"
                    }
                },
                "parameters": [
                    {
                        "name": "Idempotency-Key",
                        "in": "header",
                        "required": false,
                        "description": "Client-supplied key so retries of this write do not create duplicate records. Reuse the same key and body for 24 hours.",
                        "schema": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 255
                        }
                    }
                ]
            },
            "options": {
                "operationId": "optionsRevokeAgentToken",
                "tags": [
                    "Auth"
                ],
                "summary": "CORS preflight for revocation_uri",
                "responses": {
                    "204": {
                        "description": "No content"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "ErrorResponse": {
                "type": "object",
                "required": [
                    "error"
                ],
                "properties": {
                    "error": {
                        "type": "object",
                        "required": [
                            "code",
                            "message",
                            "hint"
                        ],
                        "properties": {
                            "code": {
                                "type": "string"
                            },
                            "message": {
                                "type": "string"
                            },
                            "hint": {
                                "type": "string"
                            }
                        }
                    }
                }
            }
        },
        "securitySchemes": {
            "anonymousBearer": {
                "type": "http",
                "scheme": "bearer",
                "description": "Optional anonymous catalog:read token. Catalog GET endpoints also work without a token."
            }
        },
        "parameters": {
            "IdempotencyKey": {
                "name": "Idempotency-Key",
                "in": "header",
                "required": false,
                "description": "Client-supplied key so retries of this write do not create duplicate records. Reuse the same key and body for 24 hours.",
                "schema": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 255
                }
            }
        }
    }
}
