{
  "openapi": "3.1.0",
  "info": {
    "title": "Osprey CMS",
    "version": "1.0.0",
    "description": "REST + GraphQL surface for the Osprey headless CMS. Every endpoint is multi-tenant: include `x-tenant-id` (when an api key is shared) or rely on the JWT subject for tenant scoping.",
    "contact": {
      "name": "Altovar",
      "url": "https://altovar.com"
    }
  },
  "servers": [
    {
      "url": "https://cms.altovar.net"
    }
  ],
  "tags": [
    {
      "name": "Pages"
    },
    {
      "name": "Collections"
    },
    {
      "name": "Documents"
    },
    {
      "name": "Media"
    },
    {
      "name": "Globals"
    },
    {
      "name": "GraphQL"
    },
    {
      "name": "Cookie consent"
    },
    {
      "name": "Webhooks"
    }
  ],
  "paths": {
    "/api/v1/pages": {
      "get": {
        "summary": "List pages",
        "tags": [
          "Pages"
        ],
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 20
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "draft",
                "review",
                "published",
                "scheduled"
              ]
            }
          },
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Full-text search"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of pages",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Pagination"
                    },
                    {
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Page"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          },
          {
            "bearerAuth": []
          }
        ]
      },
      "post": {
        "summary": "Create a page",
        "tags": [
          "Pages"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PageInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created page",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Page"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/v1/pages/{id}": {
      "get": {
        "summary": "Get a page",
        "tags": [
          "Pages"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "locale",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Page",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Page"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          },
          {
            "bearerAuth": []
          }
        ]
      },
      "patch": {
        "summary": "Update a page",
        "tags": [
          "Pages"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PageInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated page",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Page"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          },
          {
            "bearerAuth": []
          }
        ]
      },
      "delete": {
        "summary": "Soft-delete a page",
        "tags": [
          "Pages"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "deleted": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/v1/collections": {
      "get": {
        "summary": "List collection definitions",
        "tags": [
          "Collections"
        ],
        "responses": {
          "200": {
            "description": "Array of collection definitions",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/CollectionDefinition"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          },
          {
            "bearerAuth": []
          }
        ]
      },
      "post": {
        "summary": "Create a collection definition",
        "tags": [
          "Collections"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CollectionDefinitionInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created definition",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CollectionDefinition"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/v1/collections/{slug}/documents": {
      "get": {
        "summary": "List documents in a collection",
        "tags": [
          "Documents"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 20
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "draft",
                "published",
                "archived"
              ]
            }
          },
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated documents",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Pagination"
                    },
                    {
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/CollectionDocument"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          },
          {
            "bearerAuth": []
          }
        ]
      },
      "post": {
        "summary": "Create a document",
        "tags": [
          "Documents"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "data": {
                    "type": "object"
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "draft",
                      "published",
                      "archived"
                    ]
                  }
                },
                "required": [
                  "data"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created document",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CollectionDocument"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/v1/media": {
      "get": {
        "summary": "List media assets",
        "tags": [
          "Media"
        ],
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 20
            }
          },
          {
            "name": "tag",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated media",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Pagination"
                    },
                    {
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Media"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/v1/globals/{key}": {
      "get": {
        "summary": "Get a global value",
        "tags": [
          "Globals"
        ],
        "parameters": [
          {
            "name": "key",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Global value"
          }
        },
        "security": [
          {
            "apiKey": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/v1/graphql": {
      "post": {
        "summary": "GraphQL endpoint (read-only Query surface)",
        "tags": [
          "GraphQL"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "query": {
                    "type": "string"
                  },
                  "variables": {
                    "type": "object"
                  },
                  "operationName": {
                    "type": "string"
                  }
                },
                "required": [
                  "query"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL response"
          }
        },
        "security": [
          {
            "apiKey": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/v1/cookie-banner": {
      "get": {
        "summary": "Public cookie-banner config (no auth)",
        "tags": [
          "Cookie consent"
        ],
        "parameters": [
          {
            "name": "tenant",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Banner config"
          }
        }
      }
    },
    "/api/v1/cookie-consent": {
      "post": {
        "summary": "Record a consent decision (no auth)",
        "tags": [
          "Cookie consent"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "tenant": {
                    "type": "string"
                  },
                  "visitorId": {
                    "type": "string"
                  },
                  "consent": {
                    "type": "object"
                  },
                  "action": {
                    "type": "string",
                    "enum": [
                      "accept-all",
                      "reject-all",
                      "save-preferences"
                    ]
                  }
                },
                "required": [
                  "tenant",
                  "visitorId",
                  "consent",
                  "action"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledgement"
          }
        }
      }
    },
    "/api/v1/webhooks": {
      "get": {
        "summary": "List webhooks",
        "tags": [
          "Webhooks"
        ],
        "responses": {
          "200": {
            "description": "Array of webhook subscriptions"
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "post": {
        "summary": "Create a webhook",
        "tags": [
          "Webhooks"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "label": {
                    "type": "string"
                  },
                  "url": {
                    "type": "string"
                  },
                  "events": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "active": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "label",
                  "url",
                  "events"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created webhook (secret is returned in cleartext only here)"
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "x-api-key",
        "description": "Osprey API key. Required for all `/api/v1/*` routes unless noted otherwise."
      },
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "Auris-issued JWT for editor sessions."
      }
    },
    "schemas": {
      "Pagination": {
        "type": "object",
        "properties": {
          "pagination": {
            "type": "object",
            "properties": {
              "page": {
                "type": "integer"
              },
              "limit": {
                "type": "integer"
              },
              "total": {
                "type": "integer"
              },
              "totalPages": {
                "type": "integer"
              }
            }
          }
        }
      },
      "LocalizedString": {
        "type": "object",
        "additionalProperties": {
          "type": "string"
        },
        "description": "Map of locale code → string. Always at least one key.",
        "example": {
          "en": "About us",
          "it": "Chi siamo"
        }
      },
      "Block": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "type": {
            "type": "string"
          },
          "order": {
            "type": "integer"
          },
          "data": {
            "type": "object",
            "additionalProperties": true
          }
        },
        "required": [
          "id",
          "type"
        ]
      },
      "Page": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "title": {
            "$ref": "#/components/schemas/LocalizedString"
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "review",
              "published",
              "scheduled"
            ]
          },
          "publishedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "scheduledAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "seo": {
            "type": "object",
            "nullable": true
          },
          "blocks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Block"
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "slug",
          "title",
          "status",
          "blocks",
          "createdAt",
          "updatedAt"
        ]
      },
      "PageInput": {
        "type": "object",
        "properties": {
          "slug": {
            "type": "string"
          },
          "title": {
            "$ref": "#/components/schemas/LocalizedString"
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "review",
              "published",
              "scheduled"
            ]
          },
          "seo": {
            "type": "object"
          },
          "blocks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Block"
            }
          }
        }
      },
      "CollectionDefinition": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "name": {
            "$ref": "#/components/schemas/LocalizedString"
          },
          "fields": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "listColumns": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true
          },
          "icon": {
            "type": "string",
            "nullable": true
          },
          "singleton": {
            "type": "boolean"
          },
          "permalinkPattern": {
            "type": "string",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CollectionDefinitionInput": {
        "type": "object",
        "required": [
          "slug",
          "name",
          "fields"
        ],
        "properties": {
          "slug": {
            "type": "string",
            "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$"
          },
          "name": {
            "$ref": "#/components/schemas/LocalizedString"
          },
          "fields": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "listColumns": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "icon": {
            "type": "string"
          },
          "singleton": {
            "type": "boolean"
          },
          "permalinkPattern": {
            "type": "string"
          }
        }
      },
      "CollectionDocument": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "collectionSlug": {
            "type": "string"
          },
          "data": {
            "type": "object",
            "additionalProperties": true
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "published",
              "archived"
            ]
          },
          "publishedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Media": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "filename": {
            "type": "string"
          },
          "mimeType": {
            "type": "string"
          },
          "size": {
            "type": "integer"
          },
          "url": {
            "type": "string"
          },
          "alt": {
            "$ref": "#/components/schemas/LocalizedString"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "focalPoint": {
            "type": "object",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      }
    }
  }
}