{
  "openapi": "3.1.0",
  "info": {
    "title": "Ridge Reports API",
    "description": "Read, write and render reports, run schedules, manage sites and their members, and sign your application's users in to a site.\n\n**Authentication.** Send an API key with each request: `Authorization: Bearer <key>`. Keys are created for a service account on a site's Members page or on the Users page, and can do whatever that account is allowed to do. The Discovery endpoints, which describe this API, need no key.\n\n**Errors.** An error comes back with a message in `error` and, for most errors, a `code` your application can act on. A `404` can also mean that the account has no access.",
    "license": {
      "name": "Proprietary",
      "url": "https://www.ridgereports.com/"
    },
    "version": "1"
  },
  "paths": {
    "/api/v1/discover": {
      "get": {
        "tags": [
          "discovery"
        ],
        "summary": "Describe the API",
        "description": "A short introduction to this API in Markdown, written for a program or an AI to start from: what\nthe API does, how to authenticate, how errors look, a first session, how a report is written, and\nlinks to the specs that describe it in full - the OpenAPI document, the report document schema\nand the expression language. It follows the `llms.txt` format, and the same text is served at\n`/llms.txt`. No API key is needed.",
        "operationId": "discover",
        "responses": {
          "200": {
            "description": "The introduction, in Markdown",
            "content": {
              "text/markdown": {}
            }
          }
        },
        "security": [
          {}
        ]
      }
    },
    "/api/v1/external-users/sign-in": {
      "post": {
        "tags": [
          "external-users"
        ],
        "summary": "Sign in a user",
        "description": "Creates the user's account in the site, or updates its name, email address and groups, and\nreturns a link that signs the user in. Redirect the user's browser to the link right away: it can\nbe used only once and expires after one minute. Request a new link every time the user signs in;\ndo not create links in advance.\n\nThe account is identified by `external_id` and belongs to this site only. A refused sign-in, for\nexample of an account a site admin has disabled, changes nothing.",
        "operationId": "sign_in_external_user",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ExternalSignIn"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "The sign-in link",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SignInLink"
                }
              }
            }
          },
          "400": {
            "description": "`email` is not a valid email address, or `return_to` is not a page in the site",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing or not valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "404": {
            "description": "The API key is not the key of a site's external sign-in",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "409": {
            "description": "A site admin has disabled the user's account",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/external-users/{external_id}": {
      "put": {
        "tags": [
          "external-users"
        ],
        "summary": "Create or update a user",
        "description": "Creates the user's account in the site, or updates its name, email address and groups, without\nsigning the user in. It does the same as a sign-in, but returns no link. Use it to set up a user\nbefore their first visit, for example to give them access or add them to a schedule, or to apply\na change from your application right away instead of at the user's next sign-in.\n\nA disabled account is updated too, and stays disabled.",
        "operationId": "put_external_user",
        "parameters": [
          {
            "name": "external_id",
            "in": "path",
            "description": "The user's ID in your application",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ExternalUserUpdate"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "The account after the update",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalUser"
                }
              }
            }
          },
          "400": {
            "description": "`email` is not a valid email address",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing or not valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "404": {
            "description": "`external_id` is blank, or the API key is not the key of a site's external sign-in",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "external-users"
        ],
        "summary": "Delete a user",
        "description": "Deletes the user's account from the site, together with its access, group memberships and\ndrafts. Call this when the user is deleted in your application. For a user who is only\ndeactivated there, sign them out instead: that keeps their account for when they are reactivated.\nA user who signs in again after being deleted gets a new account, without the access the old one\nhad.",
        "operationId": "delete_external_user",
        "parameters": [
          {
            "name": "external_id",
            "in": "path",
            "description": "The user's ID in your application",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "The account was deleted"
          },
          "401": {
            "description": "The API key is missing or not valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "404": {
            "description": "No account with this ID, or the API key is not the key of a site's external sign-in",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "409": {
            "description": "The account is the site's last site admin",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/external-users/{external_id}/sign-out": {
      "post": {
        "tags": [
          "external-users"
        ],
        "summary": "Sign out a user",
        "description": "Signs the user out of the site everywhere: ends all of their sessions and cancels any sign-in\nlinks they have not used yet. Their account, groups and access are kept, and they can sign in\nagain as usual. Call this when you deactivate a user in your application, so they lose access\nimmediately instead of when their session expires.",
        "operationId": "sign_out_external_user",
        "parameters": [
          {
            "name": "external_id",
            "in": "path",
            "description": "The user's ID in your application",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "The user was signed out"
          },
          "401": {
            "description": "The API key is missing or not valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "404": {
            "description": "No account with this ID, or the API key is not the key of a site's external sign-in",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/me": {
      "get": {
        "tags": [
          "account"
        ],
        "summary": "Get the current account",
        "description": "Returns the service account the API key belongs to, details of the key itself, and the sites the\naccount is a member of. A useful first call to check that a new key works.",
        "operationId": "get_me",
        "responses": {
          "200": {
            "description": "The account, its key and its sites",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Me"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing or not valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/sites": {
      "get": {
        "tags": [
          "sites"
        ],
        "summary": "List sites",
        "description": "Returns the sites the account is a member of. For a super admin account, returns every site in\nthe deployment; `is_member` shows which of them the account has joined.",
        "operationId": "list_sites",
        "responses": {
          "200": {
            "description": "The sites",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Site"
                  }
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing or not valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "sites"
        ],
        "summary": "Create a site",
        "description": "Creates a site and makes the account its first site admin. Requires a super admin account.",
        "operationId": "create_site",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateSite"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "The new site",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Site"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing or not valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "403": {
            "description": "The account is not a super admin",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "409": {
            "description": "Another site already uses this slug",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/sites/{slug}/categories": {
      "get": {
        "tags": [
          "categories"
        ],
        "summary": "List categories",
        "description": "Returns the site's report categories that the account can see. Categories can be nested:\n`parent_id` refers to the category each one is in.",
        "operationId": "list_categories",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The site's slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The categories",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Category"
                  }
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing or not valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "404": {
            "description": "No site with this slug, or the account has no access to it",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "categories"
        ],
        "summary": "Create a category",
        "description": "Creates a category at the top level, or inside another category given as `parent_id`. The\naccount needs the right to create categories, and inside another category also the right to\nadd to it.",
        "operationId": "create_category",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The site's slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateCategory"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "The new category",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Category"
                }
              }
            }
          },
          "400": {
            "description": "The category would be nested more than four levels deep",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing or not valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "403": {
            "description": "The account may not create categories, or may not add to the category given as `parent_id`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "404": {
            "description": "No site with this slug or no category with the ID given as `parent_id`, or the account has no access to it",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "409": {
            "description": "Another category in the same place already has this name",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/sites/{slug}/categories/{category_id}": {
      "put": {
        "tags": [
          "categories"
        ],
        "summary": "Update a category",
        "description": "Replaces the category's name, description and color. A field left out is cleared. The category\nstays where it is.",
        "operationId": "update_category",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The site's slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "category_id",
            "in": "path",
            "description": "The category's ID",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateCategory"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "The updated category",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Category"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing or not valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "403": {
            "description": "The account can see the category but may not edit it",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "404": {
            "description": "No site with this slug or no category with this ID, or the account has no access to it",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "409": {
            "description": "Another category in the same place already has this name",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "categories"
        ],
        "summary": "Delete a category",
        "description": "Deletes an empty category. A category that still holds other categories or reports cannot be\ndeleted: delete the categories in it, and move or delete its reports, first.",
        "operationId": "delete_category",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The site's slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "category_id",
            "in": "path",
            "description": "The category's ID",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "The category was deleted"
          },
          "401": {
            "description": "The API key is missing or not valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "403": {
            "description": "The account can see the category but may not delete it",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "404": {
            "description": "No site with this slug or no category with this ID, or the account has no access to it",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "409": {
            "description": "The category still holds other categories or reports",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/sites/{slug}/data-sources": {
      "get": {
        "tags": [
          "data-sources"
        ],
        "summary": "List the data sources",
        "description": "Lists the data sources the account can use, ordered by name. A dataset in a report document\nnames its data source by ID, and its query is written in the SQL dialect of the data source's\nkind.",
        "operationId": "list_data_sources",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The site's slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The data sources the account can use",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/DataSource"
                  }
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing or not valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "404": {
            "description": "No site with this slug, or the account has no access to it",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/sites/{slug}/data-sources/{data_source_id}/catalog": {
      "get": {
        "tags": [
          "data-sources"
        ],
        "summary": "List a data source's tables",
        "description": "Lists the tables and views a data source holds, each with its columns, for writing a query\nagainst it. The names come back quoted the way the data source needs them, ready to use in a\nquery. Only what the data source's connection may read is listed, and the database's own system\ntables are left out.",
        "operationId": "get_data_source_catalog",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The site's slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "data_source_id",
            "in": "path",
            "description": "The data source's ID",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The data source's tables and views",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Catalog"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing or not valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "404": {
            "description": "No site with this slug or no data source with this ID, or the account may not use it",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "503": {
            "description": "The data source could not be reached, or its tables could not be read",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/sites/{slug}/members": {
      "post": {
        "tags": [
          "sites"
        ],
        "summary": "Add a member",
        "description": "Adds a person to a site by email address. The new member starts with no access; grant it in the\nsite. If no account has the address yet, one is created. When the deployment can send email, the\nperson is notified: an active account is told it was added, and an account without a password\nyet, such as a new one, gets an invitation to set one. Requires a super admin account.",
        "operationId": "add_member",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The site's slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AddMember"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "The new member",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Member"
                }
              }
            }
          },
          "400": {
            "description": "`email` or `display_name` is blank, or `email` is not a valid email address",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing or not valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "403": {
            "description": "The account is not a super admin",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "404": {
            "description": "No site with this slug",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "409": {
            "description": "The person is already a member of the site",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/sites/{slug}/members/{user_id}": {
      "delete": {
        "tags": [
          "sites"
        ],
        "summary": "Remove a member",
        "description": "Removes a member from a site, together with their group memberships and access there. The\naccount itself is kept, since it may belong to other sites, except for a service account that was\ncreated in this site: that account cannot belong anywhere else, so it is deleted along with its\nAPI keys. Requires a super admin account.\n\nThe site's last site admin cannot be removed. Neither can the service account of the site's\nexternal sign-in; it is removed together with the external sign-in.",
        "operationId": "remove_member",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The site's slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "user_id",
            "in": "path",
            "description": "The member's account ID",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "The member was removed"
          },
          "401": {
            "description": "The API key is missing or not valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "403": {
            "description": "The account is not a super admin",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "404": {
            "description": "No site with this slug, or no member with this ID",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "409": {
            "description": "The member is the site's last site admin, or the service account of the site's external sign-in",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "sites"
        ],
        "summary": "Update a member",
        "description": "Makes a member a site admin, or removes their site admin role. A site always keeps at least one\nsite admin, so the role cannot be removed from the last one. Requires a super admin account.",
        "operationId": "update_member",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The site's slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "user_id",
            "in": "path",
            "description": "The member's account ID",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateMember"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "The updated member",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Member"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing or not valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "403": {
            "description": "The account is not a super admin",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "404": {
            "description": "No site with this slug, or no member with this ID",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "409": {
            "description": "The member is the site's last site admin",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/sites/{slug}/reports": {
      "get": {
        "tags": [
          "reports"
        ],
        "summary": "List reports",
        "description": "Returns the site's reports that the account can see: the published reports it can view, and the\nreports not yet published that it can edit. Only a published report can be rendered, so use\n`published=true` to list just those.",
        "operationId": "list_reports",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The site's slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "published",
            "in": "query",
            "description": "`true` to list only published reports, which are the ones that can be rendered; `false` to\nlist only reports that have not been published. Leave it out to list both.",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "include",
            "in": "query",
            "description": "A comma-separated list of extra fields to include for each report. The only supported value\nis `external_viewers`, which adds the users of your application who can open the report. It\ncan be requested only with the API key of the site's external sign-in. It reflects each\nuser's groups as of their last sign-in or update, so update a user whenever their groups\nchange in your application.",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The reports",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Report"
                  }
                }
              }
            }
          },
          "400": {
            "description": "`include` contains a value that is not supported",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing or not valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "403": {
            "description": "`external_viewers` was requested with an API key other than the key of the site's external sign-in",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "404": {
            "description": "No site with this slug, or the account has no access to it",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "reports"
        ],
        "summary": "Create a report",
        "description": "Creates a report, with `definition` as the content of the account's own draft of it. The report\nis not published, so it cannot be rendered until its draft is. The account needs the right to\ncreate reports and, for a report filed in a category, the right to add to that category. It also\nneeds the right to use each data source the document reads and to view each report it embeds.",
        "operationId": "create_report",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The site's slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateReport"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "The new report",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Report"
                }
              }
            }
          },
          "400": {
            "description": "`definition` is not a report document the schema allows, and `error` says where",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing or not valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "403": {
            "description": "The account may not create reports, may not add to the category, may not use a data source the document reads, or may not view a report it embeds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "404": {
            "description": "No site with this slug, or a category, data source or report the request names does not exist or the account has no access to it",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/sites/{slug}/reports/import-rdl": {
      "post": {
        "tags": [
          "reports"
        ],
        "summary": "Import an RDL report",
        "description": "Creates a report from an RDL file, the report format of SQL Server Reporting Services and Bold\nReports. The file's design becomes the content of the account's draft of the new report, and the\nimages it embeds become the draft's images. The report's name, description, category, tags and\nattributes are taken from the request, since the file holds none of them. The data sources the\nfile names belong to the system it came from, so give `data_source_id` to run every dataset\nagainst one of this site's. The answer's `notes` say what did not come across exactly, or still\nneeds a choice; read them before correcting the draft. The account needs the right to create\nreports, the right to add to the category the report is filed in, and the right to use the data\nsource it names.",
        "operationId": "import_rdl",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The site's slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ImportRdl"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "The new report, with the conversion's notes",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImportedReport"
                }
              }
            }
          },
          "400": {
            "description": "`source` cannot be read as an RDL report, and `error` says why",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing or not valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "403": {
            "description": "The account may not create reports, or may not add to the category",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "404": {
            "description": "No site with this slug, or a category or data source the request names does not exist or the account has no access to it",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "413": {
            "description": "The request is larger than 16 MiB"
          }
        }
      }
    },
    "/api/v1/sites/{slug}/reports/{report_id}": {
      "put": {
        "tags": [
          "reports"
        ],
        "summary": "Update a report",
        "description": "Replaces the report's name, description, category, tags and attributes. A field left out is\ncleared, and a report given no `category_id` is taken out of its category. Moving the report into\na category needs the right to add to that category. The report's content is changed through its\ndraft.",
        "operationId": "update_report",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The site's slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "report_id",
            "in": "path",
            "description": "The report's ID",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateReport"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "The updated report",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Report"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing or not valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "403": {
            "description": "The account can see the report but may not edit it, or may not add to the category",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "404": {
            "description": "No site with this slug, no report with this ID or no category with the ID given, or the account has no access to it",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "reports"
        ],
        "summary": "Delete a report",
        "description": "Deletes the report with its published version, its revisions, every draft of it and its\nschedules. A report that another report embeds cannot be deleted until it is removed from that\nreport and the change is published.",
        "operationId": "delete_report",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The site's slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "report_id",
            "in": "path",
            "description": "The report's ID",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "The report was deleted"
          },
          "401": {
            "description": "The API key is missing or not valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "403": {
            "description": "The account can see the report but may not delete it",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "404": {
            "description": "No site with this slug or no report with this ID, or the account has no access to it",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "409": {
            "description": "Other reports embed this report. `params.reports` names the ones the account can see, and `params.hidden` counts the rest",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/sites/{slug}/reports/{report_id}/draft": {
      "get": {
        "tags": [
          "drafts"
        ],
        "summary": "Get the account's draft",
        "description": "Returns the account's own draft of the report: the report document and the draft's version. If\nthe account has no draft of the report yet, one is started from the published version, or, while\nthe report is unpublished, from its newest revision, or empty if it has none. A draft that holds\nno changes of its own is brought up to date with that version when it is read.",
        "operationId": "get_draft",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The site's slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "report_id",
            "in": "path",
            "description": "The report's ID",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The account's draft",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Draft"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing or not valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "403": {
            "description": "The account can view the report but not edit it",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "404": {
            "description": "No site with this slug or no report with this ID, or the account has no access to it",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "drafts"
        ],
        "summary": "Save the account's draft",
        "description": "Replaces the report document in the account's draft. The draft must still be at\n`expected_version`: if it has changed since, nothing is saved, so read the draft again and\nreapply the change. The document must match the report schema, and the account needs the right\nto use each data source it reads and to view each report it embeds. Other mistakes, such as an\nexpression that does not evaluate, are saved and reported as warnings when the draft is\nrendered.",
        "operationId": "save_draft",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The site's slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "report_id",
            "in": "path",
            "description": "The report's ID",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SaveDraft"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "The saved draft, with its new version",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Draft"
                }
              }
            }
          },
          "400": {
            "description": "`definition` is not a report document the schema allows, and `error` says where",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing or not valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "403": {
            "description": "The account can view the report but not edit it, may not use a data source the document reads, or may not view a report it embeds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "404": {
            "description": "No site with this slug, no report with this ID, or a data source or report the document names does not exist; or the account has no access to it, or no draft of it yet",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "409": {
            "description": "The draft is no longer at `expected_version`. `params.current_version` gives its version now",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "drafts"
        ],
        "summary": "Discard the account's draft",
        "description": "Throws away the changes in the account's draft, which then holds what a new draft starts from:\nthe published version, or, while the report is unpublished, its newest revision, or no content\nif it has none. The draft must still be at `expected_version`. Only this account's draft is\naffected.",
        "operationId": "discard_draft",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The site's slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "report_id",
            "in": "path",
            "description": "The report's ID",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "expected_version",
            "in": "query",
            "description": "The version of the draft this change is based on, as the draft was last read or saved.",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The draft as it is now, with its new version",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Draft"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing or not valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "403": {
            "description": "The account can view the report but not edit it",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "404": {
            "description": "No site with this slug or no report with this ID, or the account has no access to it or no draft of it",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "409": {
            "description": "The draft is no longer at `expected_version`. `params.current_version` gives its version now",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/sites/{slug}/reports/{report_id}/draft/assets": {
      "get": {
        "tags": [
          "drafts"
        ],
        "summary": "List the draft's images",
        "description": "Lists the images in the account's draft of the report, in the order they were added. If the\naccount has no draft of the report yet, one is started the way reading the draft starts one.",
        "operationId": "list_images",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The site's slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "report_id",
            "in": "path",
            "description": "The report's ID",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The draft's images",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Image"
                  }
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing or not valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "403": {
            "description": "The account can view the report but not edit it",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "404": {
            "description": "No site with this slug or no report with this ID, or the account has no access to it",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/sites/{slug}/reports/{report_id}/draft/assets/{name}": {
      "put": {
        "tags": [
          "drafts"
        ],
        "summary": "Add or replace an image",
        "description": "Adds an image to the account's draft under the name in the path, or replaces the image that\nalready has that name. Send the image's bytes as the request body, with its format as the\n`Content-Type`: `image/png`, `image/jpeg`, `image/webp`, `image/gif`, `image/tiff`, `image/bmp`,\n`image/x-icon` or `image/svg+xml`, up to 10 MiB. The image is processed the way an upload in\nthe designer is, so the stored one may be smaller and in another format: a large image is scaled\ndown, and an SVG keeps only what draws, with its text turned into outlines.\n\nThe name is what the report document shows the image by, as `asset:<name>`: a lowercase letter,\nthen lowercase letters, digits and underscores, up to 64 characters.",
        "operationId": "put_image",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The site's slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "report_id",
            "in": "path",
            "description": "The report's ID",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "name",
            "in": "path",
            "description": "The image's name",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "expected_version",
            "in": "query",
            "description": "The version of the draft this change is based on, as the draft was last read or saved.",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          }
        ],
        "requestBody": {
          "description": "The image's bytes",
          "content": {
            "image/bmp": {
              "schema": {
                "type": "array",
                "items": {
                  "type": "integer",
                  "format": "int32",
                  "minimum": 0
                }
              }
            },
            "image/gif": {
              "schema": {
                "type": "array",
                "items": {
                  "type": "integer",
                  "format": "int32",
                  "minimum": 0
                }
              }
            },
            "image/jpeg": {
              "schema": {
                "type": "array",
                "items": {
                  "type": "integer",
                  "format": "int32",
                  "minimum": 0
                }
              }
            },
            "image/png": {
              "schema": {
                "type": "array",
                "items": {
                  "type": "integer",
                  "format": "int32",
                  "minimum": 0
                }
              }
            },
            "image/svg+xml": {
              "schema": {
                "type": "array",
                "items": {
                  "type": "integer",
                  "format": "int32",
                  "minimum": 0
                }
              }
            },
            "image/tiff": {
              "schema": {
                "type": "array",
                "items": {
                  "type": "integer",
                  "format": "int32",
                  "minimum": 0
                }
              }
            },
            "image/webp": {
              "schema": {
                "type": "array",
                "items": {
                  "type": "integer",
                  "format": "int32",
                  "minimum": 0
                }
              }
            },
            "image/x-icon": {
              "schema": {
                "type": "array",
                "items": {
                  "type": "integer",
                  "format": "int32",
                  "minimum": 0
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "The image replaced the one of that name",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SavedImage"
                }
              }
            }
          },
          "201": {
            "description": "The image was added",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SavedImage"
                }
              }
            }
          },
          "400": {
            "description": "The body is empty, the name is not a valid image name, or the body is not an image of a supported format",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing or not valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "403": {
            "description": "The account can view the report but not edit it",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "404": {
            "description": "No site with this slug or no report with this ID, or the account has no access to it or no draft of it",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "409": {
            "description": "The draft is no longer at `expected_version`. `params.current_version` gives its version now",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "413": {
            "description": "The image is larger than 10 MiB"
          }
        }
      },
      "delete": {
        "tags": [
          "drafts"
        ],
        "summary": "Delete an image",
        "description": "Removes the image of this name from the account's draft. A report document that still shows it\nrenders with a warning in its place.",
        "operationId": "delete_image",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The site's slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "report_id",
            "in": "path",
            "description": "The report's ID",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "name",
            "in": "path",
            "description": "The image's name",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "expected_version",
            "in": "query",
            "description": "The version of the draft this change is based on, as the draft was last read or saved.",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The image was removed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DraftVersion"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing or not valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "403": {
            "description": "The account can view the report but not edit it",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "404": {
            "description": "No site with this slug, no report with this ID or no image of this name in the draft; or the account has no access to the report or no draft of it",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "409": {
            "description": "The draft is no longer at `expected_version`. `params.current_version` gives its version now",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/sites/{slug}/reports/{report_id}/draft/render": {
      "post": {
        "tags": [
          "drafts"
        ],
        "summary": "Render the account's draft",
        "description": "Renders the account's draft of the report and returns the file, as rendering the published\nversion does, with the same options. Use it to see what the draft looks like before handing it on\nor publishing it. If the account has no draft of the report yet, one is started the way reading\nthe draft starts one. The draft must be ready to run: every data source it reads must exist,\nevery report it embeds must be published, and its expressions and queries must refer to things\nthat exist. The first rule it breaks is returned as the error, and so is a query the database\nrefuses.\n\nThe file is returned even when some values in the report could not be evaluated. The\n`ridge-render-warnings` header gives the number of such warnings. To learn what they are, send\n`Accept: application/json`: the response is then JSON holding the file in base64 and each\nwarning, with where in the report document it is, so the draft can be corrected and saved again.\nThe warnings cover the whole report, whatever `pages` selects.",
        "operationId": "render_draft",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The site's slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "report_id",
            "in": "path",
            "description": "The report's ID",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RenderRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "The rendered file, or with `Accept: application/json`, the file and its warnings",
            "headers": {
              "ridge-render-warnings": {
                "schema": {
                  "type": "integer",
                  "format": "int32",
                  "minimum": 0
                },
                "description": "The number of values in the report that could not be evaluated. The file was still created."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RenderedFile"
                }
              },
              "application/pdf": {},
              "image/svg+xml": {},
              "image/png": {},
              "image/jpeg": {},
              "text/csv": {}
            }
          },
          "400": {
            "description": "The draft cannot be run, or cannot be rendered with this request, and `error` says why: for example, a query uses a parameter the draft does not declare, the database refused a query, a required parameter has no value, `pages` selects no page the report has, or a CSV file was requested from a report without a table",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing or not valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "403": {
            "description": "The account can view the report but not edit it",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "404": {
            "description": "No site with this slug or no report with this ID, or the account has no access to it",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "408": {
            "description": "The render took longer than the time allowed"
          },
          "409": {
            "description": "The draft has no content: the report has not been published, and nothing has been saved to the draft",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "503": {
            "description": "Too many reports are being rendered at the moment, or a data source could not be reached. Try again shortly; `error` says which",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/sites/{slug}/reports/{report_id}/draft/sample": {
      "post": {
        "tags": [
          "data-sources"
        ],
        "summary": "Try a query",
        "description": "Runs a query against a data source, with the parameters of the account's draft of the report,\nand returns the fields it returns and its first rows. Use it to check a dataset's query before\nsaving it into the draft: the query does not have to be in the draft, but every parameter it\nuses must be declared there. If the account has no draft of the report yet, one is started from\nthe published version, as reading the draft does. The account needs the right to edit the report\nand to use the data source.",
        "operationId": "sample_query",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The site's slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "report_id",
            "in": "path",
            "description": "The report's ID",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SampleRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "The query's fields and first rows",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Sample"
                }
              }
            }
          },
          "400": {
            "description": "The data source refused the query, a field has a type reports cannot read, a value in `parameters` does not suit its parameter, or the query uses a parameter the draft does not declare",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing or not valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "403": {
            "description": "The account can view the report but not edit it",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "404": {
            "description": "No site with this slug, no report with this ID or no data source with this ID; or the account has no access to the report, or may not use the data source",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "409": {
            "description": "The draft has no content: the report has not been published, and nothing has been saved to the draft",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "503": {
            "description": "The data source could not be reached",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/sites/{slug}/reports/{report_id}/parameters": {
      "get": {
        "tags": [
          "reports"
        ],
        "summary": "List a report's parameters",
        "description": "Returns the parameters of the report's published version, in the order the report defines them.\nUse them to fill in `parameters` when rendering the report.",
        "operationId": "list_report_parameters",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The site's slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "report_id",
            "in": "path",
            "description": "The report's ID",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The report's parameters",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Parameter"
                  }
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing or not valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "404": {
            "description": "No site with this slug or no report with this ID, or the account has no access to it",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "409": {
            "description": "The report has not been published",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/sites/{slug}/reports/{report_id}/publish": {
      "post": {
        "tags": [
          "drafts"
        ],
        "summary": "Publish the draft",
        "description": "Saves the account's draft as a named revision and makes it the report's published version, which\nreaders see and renders use from now on. The draft must still be at `expected_version`, and it\nmust be ready to run: every data source it reads must exist and be usable by the account, every\nreport it embeds must be published, and its expressions and queries must refer to things that\nexist. If someone else has published the report since the account's draft was started, nothing\nis published unless `force` is set.",
        "operationId": "publish_draft",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The site's slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "report_id",
            "in": "path",
            "description": "The report's ID",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublishDraft"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "The new published version, and the account's draft after it",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublishedDraft"
                }
              }
            }
          },
          "400": {
            "description": "The draft cannot be run, and `error` says why",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing or not valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "403": {
            "description": "The account can view the report but not edit it, may not use a data source the draft reads, or may not view a report it embeds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "404": {
            "description": "No site with this slug or no report with this ID, or the account has no access to it or no draft of it",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "409": {
            "description": "The draft is no longer at `expected_version` (`params.current_version` gives its version now); the draft has no content; the report already has a revision with this name; or someone else published the report since the draft was started (`params.current` names that publication) and `force` is not set",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/sites/{slug}/reports/{report_id}/render": {
      "post": {
        "tags": [
          "reports"
        ],
        "summary": "Render a report",
        "description": "Renders the report's published version and returns the file: a PDF by default, or an SVG, PNG,\nJPEG or CSV file, as chosen with `format`. The `Content-Disposition` header gives a file name\nbased on the report's name. Send `{}` to render the whole report as a PDF with default parameter\nvalues.\n\nThe file is returned even when some values in the report could not be evaluated. The\n`ridge-render-warnings` header gives the number of such warnings. To learn what they are, send\n`Accept: application/json`: the response is then JSON holding the file in base64 and each\nwarning, with where in the report document it is. The warnings cover the whole report, whatever\n`pages` selects.",
        "operationId": "render_report",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The site's slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "report_id",
            "in": "path",
            "description": "The report's ID",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RenderRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "The rendered file, or with `Accept: application/json`, the file and its warnings",
            "headers": {
              "ridge-render-warnings": {
                "schema": {
                  "type": "integer",
                  "format": "int32",
                  "minimum": 0
                },
                "description": "The number of values in the report that could not be evaluated. The file was still created."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RenderedFile"
                }
              },
              "application/pdf": {},
              "image/svg+xml": {},
              "image/png": {},
              "image/jpeg": {},
              "text/csv": {}
            }
          },
          "400": {
            "description": "The report cannot be rendered with this request, and `error` says why: for example, a required parameter has no value, a parameter value has the wrong type, `pages` selects no page the report has, a CSV file was requested from a report without a table, or the database refused one of the report's queries",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing or not valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "404": {
            "description": "No site with this slug or no report with this ID, or the account has no access to it",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "408": {
            "description": "The render took longer than the time allowed"
          },
          "409": {
            "description": "The report has not been published",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "503": {
            "description": "Too many reports are being rendered at the moment, or a data source could not be reached. Try again shortly; `error` says which",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/sites/{slug}/reports/{report_id}/revisions": {
      "post": {
        "tags": [
          "drafts"
        ],
        "summary": "Save the draft as a revision",
        "description": "Saves the account's draft as a named revision of the report, without publishing it. Everyone who\ncan edit the report sees the revision in its history, where they can preview and publish it. The\ndraft must still be at `expected_version`, and it must be ready to run: the same checks as\npublishing.",
        "operationId": "save_revision",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The site's slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "report_id",
            "in": "path",
            "description": "The report's ID",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SaveRevision"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "The new revision",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Revision"
                }
              }
            }
          },
          "400": {
            "description": "The draft cannot be run, and `error` says why",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing or not valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "403": {
            "description": "The account can view the report but not edit it, may not use a data source the draft reads, or may not view a report it embeds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "404": {
            "description": "No site with this slug or no report with this ID, or the account has no access to it or no draft of it",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "409": {
            "description": "The draft is no longer at `expected_version` (`params.current_version` gives its version now), the draft has no content, or the report already has a revision with this name",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/sites/{slug}/schedules/{schedule_id}/run": {
      "post": {
        "tags": [
          "schedules"
        ],
        "summary": "Run a schedule",
        "description": "Starts a run of the schedule right away, outside its regular timing, and returns without waiting\nfor the run to finish. A schedule that is switched off can still be run this way. The account\nneeds permission to schedule the schedule's report.",
        "operationId": "run_schedule",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The site's slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "schedule_id",
            "in": "path",
            "description": "The schedule's ID",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "The run has started",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StartedRun"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing or not valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "403": {
            "description": "The account is not allowed to schedule this report",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "404": {
            "description": "No site with this slug or no schedule with this ID, or the account has no access to it",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/sites/{slug}/service-accounts/{user_id}": {
      "put": {
        "tags": [
          "sites"
        ],
        "summary": "Add a service account",
        "description": "Adds a service account to a site. The account starts with no access; grant it in the site. If\nthe account is already a member, nothing changes. A service account that was created in another\nsite belongs to that site only and cannot be added. Requires a super admin account.",
        "operationId": "add_service_account",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The site's slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "user_id",
            "in": "path",
            "description": "The service account's ID",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "The service account is a member of the site"
          },
          "401": {
            "description": "The API key is missing or not valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "403": {
            "description": "The account is not a super admin",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "404": {
            "description": "No site with this slug, or no service account with this ID",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "409": {
            "description": "The service account belongs to another site",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "sites"
        ],
        "summary": "Remove a service account",
        "description": "Removes a service account from a site, together with its group memberships and access there. A\nservice account that was created in this site is deleted along with its API keys. Requires a super\nadmin account.",
        "operationId": "remove_service_account",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The site's slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "user_id",
            "in": "path",
            "description": "The service account's ID",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "The service account is no longer a member of the site"
          },
          "401": {
            "description": "The API key is missing or not valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "403": {
            "description": "The account is not a super admin",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "404": {
            "description": "No site with this slug, no service account with this ID, or the service account is not a member of the site",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          },
          "409": {
            "description": "The service account is the site's last site admin, or the service account of the site's external sign-in",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/specs": {
      "get": {
        "tags": [
          "discovery"
        ],
        "summary": "List the specs",
        "description": "Every spec that describes this API and the report documents it works with, each with a link to\ndownload it. Older versions stay listed beside the current ones. No API key is needed.",
        "operationId": "list_specs",
        "responses": {
          "200": {
            "description": "The specs",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Spec"
                  }
                }
              }
            }
          }
        },
        "security": [
          {}
        ]
      }
    },
    "/api/v1/specs/{file}": {
      "get": {
        "tags": [
          "discovery"
        ],
        "summary": "Get a spec",
        "description": "One of the specs that `/discover` links to, by its file name, such as `expr.v1.md`. The version\nis part of the name, and older versions stay available. No API key is needed.",
        "operationId": "get_spec",
        "parameters": [
          {
            "name": "file",
            "in": "path",
            "description": "The spec's file name, as `/discover` lists it",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The spec",
            "content": {
              "application/json": {},
              "text/markdown": {}
            }
          },
          "404": {
            "description": "No spec has this name",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            }
          }
        },
        "security": [
          {}
        ]
      }
    }
  },
  "components": {
    "schemas": {
      "AddMember": {
        "type": "object",
        "description": "A person to add to a site.",
        "required": [
          "email",
          "display_name"
        ],
        "properties": {
          "display_name": {
            "type": "string",
            "description": "The person's name. Used only when a new account is created. Must not be blank."
          },
          "email": {
            "type": "string",
            "description": "The person's email address. If an account with this address exists, that account is added;\notherwise a new account is created for it. Must be a valid email address."
          }
        }
      },
      "Catalog": {
        "type": "object",
        "description": "The tables and views a data source holds.",
        "required": [
          "tables"
        ],
        "properties": {
          "default_schema": {
            "type": [
              "string",
              "null"
            ],
            "description": "The schema a table name without a schema in front of it refers to, or `null` if the data\nsource has none."
          },
          "tables": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CatalogTable"
            },
            "description": "Every table and view, ordered by schema and then by name."
          }
        }
      },
      "CatalogColumn": {
        "type": "object",
        "description": "A column of a table or view.",
        "required": [
          "name",
          "quoted_name",
          "native_type",
          "nullable"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "The column's name."
          },
          "native_type": {
            "type": "string",
            "description": "The column's type as the database names it, with any length or precision it declares, and\n`[]` after it for a list of values."
          },
          "nullable": {
            "type": "boolean",
            "description": "Whether the column can hold no value."
          },
          "quoted_name": {
            "type": "string",
            "description": "The column's name quoted where the data source needs it, ready to use in a query."
          }
        }
      },
      "CatalogTable": {
        "type": "object",
        "description": "A table or view, with its columns.",
        "required": [
          "schema",
          "name",
          "quoted_name",
          "qualified_name",
          "kind",
          "columns"
        ],
        "properties": {
          "columns": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CatalogColumn"
            },
            "description": "The table's columns, in the data source's own order."
          },
          "kind": {
            "$ref": "#/components/schemas/CatalogTableKind",
            "description": "Whether this is a table or a view. Both are queried the same way."
          },
          "name": {
            "type": "string",
            "description": "The table's name, without its schema."
          },
          "qualified_name": {
            "type": "string",
            "description": "The schema and the name, joined and quoted the way the data source writes names, ready to\nuse in a query."
          },
          "quoted_name": {
            "type": "string",
            "description": "The table's name quoted where the data source needs it, for a query where the schema is\nalready implied, such as the default schema."
          },
          "schema": {
            "type": "string",
            "description": "The schema the table is in. For a database without schemas of its own, this is the database\nthe table is in."
          }
        }
      },
      "CatalogTableKind": {
        "type": "string",
        "description": "Whether a catalog entry is a table or a view.",
        "enum": [
          "table",
          "view"
        ]
      },
      "Category": {
        "type": "object",
        "description": "A report category. Categories can be nested.",
        "required": [
          "id",
          "name"
        ],
        "properties": {
          "color": {
            "oneOf": [
              {
                "type": "string",
                "description": "A color as `#` followed by six lowercase hex digits, e.g. `#2563eb`.",
                "pattern": "^#[0-9a-f]{6}$"
              },
              {
                "type": "null"
              }
            ],
            "description": "The category's color, as `#rrggbb`, or `null` if it takes the color of the category it is in."
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "description": "The category's description, or `null` if it has none."
          },
          "id": {
            "allOf": [
              {
                "type": "string",
                "format": "uuid"
              },
              {
                "type": "object",
                "description": "The category's ID."
              }
            ]
          },
          "name": {
            "type": "string",
            "description": "The category's name."
          },
          "parent_id": {
            "oneOf": [
              {
                "type": "string",
                "format": "uuid"
              },
              {
                "type": "null"
              }
            ],
            "description": "The ID of the category this one is in, or `null` for a top-level category."
          }
        }
      },
      "CreateCategory": {
        "type": "object",
        "description": "A new category.",
        "required": [
          "name"
        ],
        "properties": {
          "color": {
            "oneOf": [
              {
                "type": "string",
                "description": "A color as `#` followed by six lowercase hex digits, e.g. `#2563eb`.",
                "pattern": "^#[0-9a-f]{6}$"
              },
              {
                "type": "null"
              }
            ],
            "description": "The category's color, as `#` and six lowercase hexadecimal digits, such as `#2563eb`. Leave it\nout to take the color of the category it is in."
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "description": "A description of the category. Leave it out for none."
          },
          "name": {
            "allOf": [
              {
                "type": "string"
              },
              {
                "type": "object",
                "description": "The category's name. Must not be blank, and must differ from the names of the other\ncategories in the same place."
              }
            ]
          },
          "parent_id": {
            "oneOf": [
              {
                "type": "string",
                "format": "uuid"
              },
              {
                "type": "null"
              }
            ],
            "description": "The ID of the category to create it in. Leave it out for a top-level category. Categories\ncan be nested up to four levels deep."
          }
        }
      },
      "CreateReport": {
        "type": "object",
        "description": "A new report.",
        "required": [
          "name"
        ],
        "properties": {
          "attributes": {
            "type": "object",
            "description": "Custom name and value pairs for use by your application.",
            "additionalProperties": {
              "type": "string"
            },
            "propertyNames": {
              "type": "string"
            }
          },
          "category_id": {
            "oneOf": [
              {
                "type": "string",
                "format": "uuid"
              },
              {
                "type": "null"
              }
            ],
            "description": "The ID of the category to file the report in. Leave it out for a report in no category."
          },
          "definition": {
            "type": [
              "object",
              "null"
            ],
            "description": "The report document, as the report schema listed at `/api/v1/specs` describes it. It becomes\nthe content of the account's draft of the new report. Leave it out to start with no content."
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "description": "A description of the report. Leave it out for none."
          },
          "name": {
            "allOf": [
              {
                "type": "string"
              },
              {
                "type": "object",
                "description": "The report's name. Must not be blank."
              }
            ]
          },
          "tags": {
            "allOf": [
              {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              {
                "type": "object",
                "description": "The report's tags, each in lowercase, not blank, and without spaces at either end. A tag\ngiven twice is kept once."
              }
            ]
          }
        }
      },
      "CreateSite": {
        "type": "object",
        "description": "A new site.",
        "required": [
          "slug",
          "name"
        ],
        "properties": {
          "description": {
            "type": "string",
            "description": "A description of the site. Defaults to empty."
          },
          "name": {
            "allOf": [
              {
                "type": "string"
              },
              {
                "type": "object",
                "description": "The site's name. Must not be blank."
              }
            ]
          },
          "slug": {
            "allOf": [
              {
                "type": "string"
              },
              {
                "type": "object",
                "description": "The site's short name, used to address it in paths such as `/api/v1/sites/{slug}/reports`.\nIt must be unique in the deployment. Up to 60 characters of\nlowercase letters `a-z`, digits and hyphens, starting and ending with a letter or digit, with\nno two hyphens in a row."
              }
            ]
          }
        }
      },
      "DataSource": {
        "type": "object",
        "description": "A data source: a database that report datasets query.",
        "required": [
          "id",
          "name",
          "kind"
        ],
        "properties": {
          "description": {
            "type": [
              "string",
              "null"
            ],
            "description": "The data source's description, or `null` if it has none."
          },
          "id": {
            "allOf": [
              {
                "type": "string",
                "format": "uuid"
              },
              {
                "type": "object",
                "description": "The data source's ID, which a dataset names as its `dataSourceId`."
              }
            ]
          },
          "kind": {
            "$ref": "#/components/schemas/DataSourceKind",
            "description": "The kind of database, which decides the SQL dialect its queries are written in."
          },
          "name": {
            "type": "string",
            "description": "The data source's name."
          }
        }
      },
      "DataSourceKind": {
        "type": "string",
        "description": "The kind of database. `postgres`: PostgreSQL. `sqlite`: SQLite. `mysql`: MySQL. `mariadb`:\nMariaDB. `mssql`: Microsoft SQL Server.",
        "enum": [
          "postgres",
          "sqlite",
          "mysql",
          "mariadb",
          "mssql"
        ]
      },
      "Draft": {
        "type": "object",
        "description": "The account's own draft of a report: its private working copy, which nobody else sees. Readers\nkeep seeing the published version until the draft is published.",
        "required": [
          "version"
        ],
        "properties": {
          "definition": {
            "type": [
              "object",
              "null"
            ],
            "description": "The report document, as the report schema listed at `/api/v1/specs` describes it, or `null`\nif the report has no content yet."
          },
          "version": {
            "type": "integer",
            "format": "int64",
            "description": "The draft's version, which goes up with every change to it. A request that changes the\ndraft names the version it was based on as `expected_version`, so a change made in the\nmeantime is never overwritten."
          }
        }
      },
      "DraftVersion": {
        "type": "object",
        "description": "The draft's version after a change.",
        "required": [
          "version"
        ],
        "properties": {
          "version": {
            "type": "integer",
            "format": "int64",
            "description": "The draft's new version."
          }
        }
      },
      "ErrorBody": {
        "type": "object",
        "description": "The body of an error response.",
        "required": [
          "error"
        ],
        "properties": {
          "code": {
            "type": [
              "string",
              "null"
            ],
            "description": "A stable identifier for the kind of error, such as `not_found_or_no_access`, for a client to\nact on or to show its own message for. Absent for an error that has no code of its own."
          },
          "error": {
            "type": "string",
            "description": "A description of the error, in English."
          },
          "params": {
            "description": "Details of the error that its message refers to, such as the name of the parameter that is\nnot valid. Present only for some codes."
          }
        }
      },
      "ExternalGroup": {
        "type": "object",
        "description": "A group in your application.",
        "required": [
          "code",
          "name"
        ],
        "properties": {
          "code": {
            "allOf": [
              {
                "type": "string"
              },
              {
                "type": "object",
                "description": "A stable identifier for the group, matched to the site's groups without regard to case. Keep\nit unchanged when the group is renamed in your application. Must not be blank."
              }
            ]
          },
          "name": {
            "allOf": [
              {
                "type": "string"
              },
              {
                "type": "object",
                "description": "The name of the site group created for this code. Used only when the group is created:\nlater requests do not change it, and a site admin can rename it. Must not be blank."
              }
            ]
          }
        }
      },
      "ExternalSignIn": {
        "type": "object",
        "description": "A user of your application to sign in to the site.",
        "required": [
          "external_id",
          "display_name"
        ],
        "properties": {
          "display_name": {
            "allOf": [
              {
                "type": "string"
              },
              {
                "type": "object",
                "description": "The user's name, as shown in Ridge Reports. Updated at every sign-in. Must not be blank."
              }
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "description": "The user's email address. Updated at every sign-in, so leaving it out removes an address the\naccount had. Leave it out, or send `null`, for a user without one."
          },
          "external_id": {
            "allOf": [
              {
                "type": "string"
              },
              {
                "type": "object",
                "description": "The user's ID in your application. The site identifies the user by this ID, so it must never\nchange, even when the user's email address or login name does. Must not be blank."
              }
            ]
          },
          "groups": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "$ref": "#/components/schemas/ExternalGroup"
            },
            "description": "The groups the user belongs to in your application. When the list is sent, the user's\nmembership of the site's groups that have a code is made to match it: the user is added to\nthe listed groups and removed from the other groups with a code. Groups without a code are not\naffected. A code that no site group has yet creates a new group, with the given name and no\naccess until a site admin grants it. Leave the list out to keep the user's groups as they are;\nsend an empty list to remove the user from every group that has a code."
          },
          "return_to": {
            "type": [
              "string",
              "null"
            ],
            "description": "The page to open after sign-in: a path within the site, such as `/reports/{report_id}/view`,\nor the full address of a page in the site, such as a report's `viewer_url`. Leave it out to\nopen the site's start page. To open a report with preset parameter values, add them to the\nquery string as `p.<name>=<value>`, repeating the pair for each value of a parameter that\ntakes several: `/reports/{report_id}/view?p.Region=EU&p.From=2026-01-01`."
          }
        }
      },
      "ExternalUser": {
        "type": "object",
        "description": "A user's account in the site.",
        "required": [
          "external_id",
          "display_name",
          "status"
        ],
        "properties": {
          "display_name": {
            "type": "string",
            "description": "The user's name."
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "description": "The user's email address, or `null` if the account has none."
          },
          "external_id": {
            "type": "string",
            "description": "The user's ID in your application."
          },
          "status": {
            "$ref": "#/components/schemas/MemberStatus",
            "description": "The account's state. `disabled` means a site admin has disabled the account: the user cannot\nsign in until it is enabled again."
          }
        }
      },
      "ExternalUserUpdate": {
        "type": "object",
        "description": "A user of your application to create or update in the site.",
        "required": [
          "display_name"
        ],
        "properties": {
          "display_name": {
            "allOf": [
              {
                "type": "string"
              },
              {
                "type": "object",
                "description": "The user's name, as shown in Ridge Reports. Must not be blank."
              }
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "description": "The user's email address. Leaving it out removes an address the account had. Leave it out,\nor send `null`, for a user without one."
          },
          "groups": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "$ref": "#/components/schemas/ExternalGroup"
            },
            "description": "The groups the user belongs to in your application, applied as in a sign-in. Leave the list\nout to keep the user's groups as they are; send an empty list to remove the user from every\ngroup that has a code."
          }
        }
      },
      "FieldType": {
        "type": "string",
        "description": "The type of value a field holds. `string`: text. `number`: a number. `boolean`: `true` or\n`false`. `date`: a date, with a time of day where the database stores one. `binary`: raw bytes,\nsuch as an image stored in the database.",
        "enum": [
          "string",
          "number",
          "boolean",
          "date",
          "binary"
        ]
      },
      "FileFormat": {
        "type": "string",
        "description": "The format of a rendered file. `pdf`, `svg`, `png` and `jpeg` contain the report's pages; `csv`\ncontains the data of the report's first table.",
        "enum": [
          "pdf",
          "svg",
          "png",
          "jpeg",
          "csv"
        ]
      },
      "Image": {
        "type": "object",
        "description": "An image in the account's draft of a report, which the report document shows by its name, as\n`asset:<name>`.",
        "required": [
          "name",
          "content_type",
          "width",
          "height",
          "size"
        ],
        "properties": {
          "content_type": {
            "type": "string",
            "description": "The image's format as stored: `image/png`, `image/jpeg` or `image/svg+xml`."
          },
          "height": {
            "type": "integer",
            "format": "int32",
            "description": "The image's height in pixels."
          },
          "name": {
            "type": "string",
            "description": "The image's name, as the report document refers to it."
          },
          "size": {
            "type": "integer",
            "format": "int64",
            "description": "The size of the stored image in bytes."
          },
          "width": {
            "type": "integer",
            "format": "int32",
            "description": "The image's width in pixels."
          }
        }
      },
      "ImportNote": {
        "type": "object",
        "description": "A note about one part of an imported RDL file.",
        "required": [
          "severity",
          "source",
          "message"
        ],
        "properties": {
          "message": {
            "type": "string",
            "description": "What happened to that part, and what is left to do."
          },
          "severity": {
            "$ref": "#/components/schemas/ImportNoteSeverity",
            "description": "How much it costs the report."
          },
          "source": {
            "type": "string",
            "description": "The part of the file the note is about, named as the file names it, such as an item or a\ndataset."
          }
        }
      },
      "ImportNoteSeverity": {
        "type": "string",
        "description": "How much a note costs the imported report. `blocked`: that part did not come across, and has to\nbe rebuilt. `lossy`: it came across, but not exactly as the file had it. `action`: it came across,\nbut needs a choice the file cannot make, such as which data source a dataset runs against or which\nreport a subreport shows.",
        "enum": [
          "blocked",
          "lossy",
          "action"
        ]
      },
      "ImportRdl": {
        "type": "object",
        "description": "A report to create from an RDL file.",
        "required": [
          "source",
          "name"
        ],
        "properties": {
          "attributes": {
            "type": "object",
            "description": "Custom name and value pairs for use by your application.",
            "additionalProperties": {
              "type": "string"
            },
            "propertyNames": {
              "type": "string"
            }
          },
          "category_id": {
            "oneOf": [
              {
                "type": "string",
                "format": "uuid"
              },
              {
                "type": "null"
              }
            ],
            "description": "The ID of the category to file the report in. Leave it out for a report in no category."
          },
          "data_source_id": {
            "oneOf": [
              {
                "type": "string",
                "format": "uuid"
              },
              {
                "type": "null"
              }
            ],
            "description": "The ID of the data source every dataset in the file runs against. Leave it out to use the only\ndata source the account can use, if there is exactly one. Otherwise the datasets have no data\nsource, and the draft cannot be rendered or published until each one has one."
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "description": "A description of the report. Leave it out for none."
          },
          "name": {
            "allOf": [
              {
                "type": "string"
              },
              {
                "type": "object",
                "description": "The report's name. Must not be blank. The file does not hold one, so this is usually the\nfile's name without `.rdl`."
              }
            ]
          },
          "source": {
            "type": "string",
            "description": "The contents of the `.rdl` file."
          },
          "tags": {
            "allOf": [
              {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              {
                "type": "object",
                "description": "The report's tags, each in lowercase, not blank, and without spaces at either end. A tag\ngiven twice is kept once."
              }
            ]
          }
        }
      },
      "ImportedReport": {
        "type": "object",
        "description": "A report created from an RDL file, and what the conversion could not carry over.",
        "required": [
          "report",
          "notes"
        ],
        "properties": {
          "notes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ImportNote"
            },
            "description": "What did not come across exactly, or still needs a choice, most serious first. Each\ndataset's notes include the data source the file had it running against."
          },
          "report": {
            "$ref": "#/components/schemas/Report",
            "description": "The new report. Its content is in the account's draft of it, which is not yet published."
          }
        }
      },
      "Key": {
        "type": "object",
        "description": "The API key a request was made with.",
        "required": [
          "id",
          "name"
        ],
        "properties": {
          "expires_at": {
            "oneOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "description": "When the key expires, or `null` if it does not expire."
          },
          "id": {
            "allOf": [
              {
                "type": "string",
                "format": "uuid"
              },
              {
                "type": "object",
                "description": "The key's ID."
              }
            ]
          },
          "name": {
            "type": "string",
            "description": "The name the key was given when it was created."
          }
        }
      },
      "Me": {
        "type": "object",
        "description": "The service account an API key belongs to.",
        "required": [
          "id",
          "display_name",
          "is_super_admin",
          "key",
          "sites"
        ],
        "properties": {
          "display_name": {
            "type": "string",
            "description": "The account's name."
          },
          "id": {
            "allOf": [
              {
                "type": "string",
                "format": "uuid"
              },
              {
                "type": "object",
                "description": "The account's ID."
              }
            ]
          },
          "is_super_admin": {
            "type": "boolean",
            "description": "Whether the account is a super admin, which allows it to create sites and manage their\nmembers."
          },
          "key": {
            "$ref": "#/components/schemas/Key",
            "description": "The API key the request was made with."
          },
          "sites": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Site"
            },
            "description": "The sites the account is a member of."
          }
        }
      },
      "Member": {
        "type": "object",
        "description": "A member of a site.",
        "required": [
          "user_id",
          "display_name",
          "is_site_admin",
          "is_service_account",
          "status"
        ],
        "properties": {
          "display_name": {
            "type": "string",
            "description": "The member's name."
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "description": "The member's email address, or `null` for an account without one, such as a service account."
          },
          "is_service_account": {
            "type": "boolean",
            "description": "Whether the member is a service account rather than a person."
          },
          "is_site_admin": {
            "type": "boolean",
            "description": "Whether the member is a site admin."
          },
          "status": {
            "$ref": "#/components/schemas/MemberStatus"
          },
          "user_id": {
            "allOf": [
              {
                "type": "string",
                "format": "uuid"
              },
              {
                "type": "object",
                "description": "The member's account ID."
              }
            ]
          }
        }
      },
      "MemberStatus": {
        "type": "string",
        "description": "The state of an account. `pending`: invited, but no password has been set yet. `active`: in\nnormal use. `disabled`: switched off by an administrator; it cannot sign in until it is enabled\nagain, and keeps its site memberships in the meantime.",
        "enum": [
          "pending",
          "active",
          "disabled"
        ]
      },
      "PageSelector": {
        "oneOf": [
          {
            "type": "integer",
            "format": "int32",
            "description": "A single page number.",
            "minimum": 0
          },
          {
            "type": "array",
            "items": {
              "type": "integer",
              "format": "int32",
              "minimum": 0
            },
            "description": "A range `[first, last]`, or `[first]` for a page and every page after it."
          }
        ],
        "description": "One entry of `pages`: a page number (`3`), a range that includes both ends (`[4, 6]`), or a\npage and every page after it (`[9]`)."
      },
      "Parameter": {
        "type": "object",
        "description": "A report parameter.",
        "required": [
          "name",
          "type",
          "include_time",
          "multiple",
          "required",
          "hidden",
          "prompt"
        ],
        "properties": {
          "hidden": {
            "type": "boolean",
            "description": "Whether the parameter is hidden from people viewing the report. A hidden parameter can still\nbe set through the API, for example to limit a report to one customer's data."
          },
          "include_time": {
            "type": "boolean",
            "description": "For a `date` parameter, whether the value includes a time of day."
          },
          "multiple": {
            "type": "boolean",
            "description": "Whether the parameter takes a list of values. Its value in a render request is then a JSON\narray."
          },
          "name": {
            "type": "string",
            "description": "The parameter's name, used as its key in the `parameters` of a render request."
          },
          "prompt": {
            "type": "string",
            "description": "The label shown for the parameter when the report asks for its values."
          },
          "required": {
            "type": "boolean",
            "description": "Whether the parameter must have a value. When a render request gives no value for a\nrequired parameter, its default value is used; a required parameter without a default\nmust be given a value."
          },
          "type": {
            "$ref": "#/components/schemas/ParameterType",
            "description": "The type of value the parameter takes."
          }
        }
      },
      "ParameterType": {
        "type": "string",
        "description": "The type of value a parameter takes. `string`: text. `number`: a JSON number, or a string that\ncontains one. `boolean`: `true` or `false`. `date`: an ISO 8601 date such as `2026-10-17`, or a\ndate and time such as `2026-10-17T14:30:00` when `include_time` is set.",
        "enum": [
          "string",
          "number",
          "boolean",
          "date"
        ]
      },
      "PublishDraft": {
        "type": "object",
        "description": "The account's draft to publish.",
        "required": [
          "expected_version",
          "name"
        ],
        "properties": {
          "expected_version": {
            "type": "integer",
            "format": "int64",
            "description": "The version of the draft to publish, as the draft was last read or saved."
          },
          "force": {
            "type": "boolean",
            "description": "Publish even when the report has been published by someone else since the account's draft was\nstarted, which replaces that publication with a draft that does not contain its changes. The\nreplaced publication stays among the report's revisions. Defaults to `false`."
          },
          "name": {
            "allOf": [
              {
                "type": "string"
              },
              {
                "type": "object",
                "description": "The name of the new published version. Must not be blank, and must differ from the names of\nthe report's revisions."
              }
            ]
          }
        }
      },
      "PublishedDraft": {
        "type": "object",
        "description": "A published draft: the revision readers now see, and the account's draft after it.",
        "required": [
          "revision",
          "draft"
        ],
        "properties": {
          "draft": {
            "$ref": "#/components/schemas/Draft",
            "description": "The account's draft, which now has no changes of its own, with its new version."
          },
          "revision": {
            "$ref": "#/components/schemas/Revision",
            "description": "The new revision, now the report's published version."
          }
        }
      },
      "RenderMode": {
        "type": "string",
        "description": "How a report is laid out. `paged`: page by page, as designed. `continuous`: the whole report on\na single page, without page breaks.",
        "enum": [
          "paged",
          "continuous"
        ]
      },
      "RenderRequest": {
        "type": "object",
        "description": "The options for rendering a report. Every field is optional.",
        "properties": {
          "delimiter": {
            "type": [
              "string",
              "null"
            ],
            "description": "For `csv`, the single character that separates fields. Any character except `\"` and line\nbreaks. Defaults to `,`."
          },
          "format": {
            "$ref": "#/components/schemas/FileFormat",
            "description": "The file format. Defaults to `pdf`. With `svg`, `png` and `jpeg`, the selected pages are\ncombined into a single image. With `csv`, the file contains the data of the report's first\ntable."
          },
          "pages": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "$ref": "#/components/schemas/PageSelector"
            },
            "description": "The pages to include, numbered from 1. Each entry is a page number (`3`), a range that\nincludes both ends (`[4, 6]`), or a page and every page after it (`[9]`). Leave it out to\ninclude every page. Cannot be used with `csv`."
          },
          "parameters": {
            "type": "object",
            "description": "Values for the report's parameters, keyed by parameter name. Give a JSON array for a\nparameter that takes several values. A parameter that is left out, or set to `null`, uses its\ndefault value. Names the report does not define are ignored."
          },
          "render_mode": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/RenderMode",
                "description": "How the report is laid out: `paged` or `continuous`. Leave it out to use the report's own\nsetting."
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "RenderedFile": {
        "type": "object",
        "description": "A rendered file, with the problems found while rendering it.",
        "required": [
          "file",
          "content_type",
          "file_name",
          "warnings"
        ],
        "properties": {
          "content_type": {
            "type": "string",
            "description": "The file's media type, such as `application/pdf`."
          },
          "file": {
            "type": "string",
            "description": "The file's content, encoded in base64."
          },
          "file_name": {
            "type": "string",
            "description": "A file name based on the report's name."
          },
          "warnings": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Warning"
            },
            "description": "Each value that could not be evaluated, and each item that could not be drawn as designed.\nThe file was still created, with a marker such as `#name` in place of a value that failed."
          }
        }
      },
      "Report": {
        "type": "object",
        "description": "A report the account can see: a published report it can view, or one not yet published that it\ncan edit.",
        "required": [
          "id",
          "name",
          "tags",
          "attributes",
          "created_at",
          "viewer_url"
        ],
        "properties": {
          "attributes": {
            "type": "object",
            "description": "Custom name and value pairs set on the report, for use by your application.",
            "additionalProperties": {
              "type": "string"
            },
            "propertyNames": {
              "type": "string"
            }
          },
          "category_id": {
            "oneOf": [
              {
                "type": "string",
                "format": "uuid"
              },
              {
                "type": "null"
              }
            ],
            "description": "The ID of the category the report is in, or `null` if it is not in a category."
          },
          "created_at": {
            "allOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "object",
                "description": "When the report was created."
              }
            ]
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "description": "The report's description, or `null` if it has none."
          },
          "external_viewers": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "string"
            },
            "description": "The users of your application who can open the report, by their IDs in your application.\nPresent only when requested with `include=external_viewers`."
          },
          "id": {
            "allOf": [
              {
                "type": "string",
                "format": "uuid"
              },
              {
                "type": "object",
                "description": "The report's ID."
              }
            ]
          },
          "name": {
            "type": "string",
            "description": "The report's name."
          },
          "published_at": {
            "oneOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "description": "When the published version was published, or `null` if the report has not been published."
          },
          "published_version": {
            "type": [
              "string",
              "null"
            ],
            "description": "The name of the published version, as given by its author, or `null` if the report has not\nbeen published. Reading and rendering the report always use this version, so a report that\nhas not been published cannot be rendered."
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "The report's tags."
          },
          "viewer_url": {
            "type": "string",
            "description": "The address of the report in the site's report viewer. Someone who is not signed in is asked\nto sign in first. To send a user of your application to the report, sign them in with this\naddress as `return_to` instead."
          }
        }
      },
      "Revision": {
        "type": "object",
        "description": "A revision of a report: a named copy of its content, saved from a draft, which never changes.\nThe published version of a report is one of its revisions.",
        "required": [
          "id",
          "name",
          "created_at"
        ],
        "properties": {
          "created_at": {
            "allOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "object",
                "description": "When the revision was saved."
              }
            ]
          },
          "id": {
            "allOf": [
              {
                "type": "string",
                "format": "uuid"
              },
              {
                "type": "object",
                "description": "The revision's ID."
              }
            ]
          },
          "name": {
            "type": "string",
            "description": "The revision's name."
          },
          "published_at": {
            "oneOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "description": "When the revision was last made the published version, or `null` if it never was."
          }
        }
      },
      "Sample": {
        "type": "object",
        "description": "The first rows a query returns.",
        "required": [
          "fields",
          "rows",
          "truncated",
          "parameters"
        ],
        "properties": {
          "fields": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SampleField"
            },
            "description": "The fields the query returns, in the query's own order, even when it returns no rows."
          },
          "parameters": {
            "type": "object",
            "description": "The values bound into the query, keyed by parameter name: the value given in the request, or\nthe parameter's default where none was given. Each is text, as a cell is, or a list of them\nfor a parameter that takes several values. Only the parameters the query uses are listed."
          },
          "rows": {
            "type": "array",
            "items": {
              "type": "array",
              "items": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "description": "The rows, at most 100, each with one cell per field in the same order. A cell is the value\nas text, the way an expression shows it: a date as `2026-10-17` or `2026-10-17 14:30:00`\nin the report's time zone, and a number without trailing zeros. A cell with no value is\n`null`, and a cell of a `binary` field holds its size in bytes."
          },
          "truncated": {
            "type": "boolean",
            "description": "Whether the query returned more rows than `rows` holds."
          }
        }
      },
      "SampleField": {
        "type": "object",
        "description": "A field a query returns.",
        "required": [
          "name",
          "type",
          "native_type"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "The field's name, as the query returns it, which an expression in the report reads it by."
          },
          "native_type": {
            "type": "string",
            "description": "The field's type as the database names it."
          },
          "type": {
            "$ref": "#/components/schemas/FieldType",
            "description": "The type of value the field holds."
          }
        }
      },
      "SampleRequest": {
        "type": "object",
        "description": "A query to try, with values for the report's parameters.",
        "required": [
          "data_source_id",
          "query",
          "name"
        ],
        "properties": {
          "data_source_id": {
            "allOf": [
              {
                "type": "string",
                "format": "uuid"
              },
              {
                "type": "object",
                "description": "The ID of the data source to run the query against."
              }
            ]
          },
          "name": {
            "type": "string",
            "description": "The name of the dataset the query is for, used only to name it in error messages."
          },
          "parameters": {
            "type": "object",
            "description": "Values for the report's parameters, keyed by parameter name, as in a render request. A\nparameter that is left out, or set to `null`, uses its default value, or no value if it has\nnone, even when it is required."
          },
          "query": {
            "type": "string",
            "description": "The query, in the data source's SQL dialect, as a dataset's `query` holds it. Each `@name`\nin it is a report parameter, which the draft must declare, or a `report.` value such as\n`@report.UserId`, as the expression spec lists them."
          }
        }
      },
      "SaveDraft": {
        "type": "object",
        "description": "A new report document for the account's draft.",
        "required": [
          "expected_version",
          "definition"
        ],
        "properties": {
          "definition": {
            "type": "object",
            "description": "The report document, as the report schema listed at `/api/v1/specs` describes it. A document\nwritten for an earlier version of the schema is accepted and brought up to the current one."
          },
          "expected_version": {
            "type": "integer",
            "format": "int64",
            "description": "The version of the draft this change is based on, as the draft was last read or saved."
          }
        }
      },
      "SaveRevision": {
        "type": "object",
        "description": "A revision to save from the account's draft.",
        "required": [
          "expected_version",
          "name"
        ],
        "properties": {
          "expected_version": {
            "type": "integer",
            "format": "int64",
            "description": "The version of the draft to save, as the draft was last read or saved."
          },
          "name": {
            "allOf": [
              {
                "type": "string"
              },
              {
                "type": "object",
                "description": "The revision's name. Must not be blank, and must differ from the names of the report's other\nrevisions."
              }
            ]
          }
        }
      },
      "SavedImage": {
        "type": "object",
        "description": "An image saved to the account's draft, and the draft's version after it.",
        "required": [
          "image",
          "version"
        ],
        "properties": {
          "image": {
            "$ref": "#/components/schemas/Image",
            "description": "The image as stored."
          },
          "version": {
            "type": "integer",
            "format": "int64",
            "description": "The draft's new version."
          }
        }
      },
      "SignInLink": {
        "type": "object",
        "description": "A single-use sign-in link.",
        "required": [
          "url",
          "expires_at"
        ],
        "properties": {
          "expires_at": {
            "allOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "object",
                "description": "When the link expires: one minute after it was created."
              }
            ]
          },
          "url": {
            "type": "string",
            "description": "The link to redirect the user's browser to. It can be used only once."
          }
        }
      },
      "Site": {
        "type": "object",
        "description": "A site: a separate area of the deployment with its own reports, members and access settings.",
        "required": [
          "id",
          "slug",
          "name",
          "description",
          "is_member"
        ],
        "properties": {
          "description": {
            "type": "string",
            "description": "The site's description. Empty if it has none."
          },
          "id": {
            "allOf": [
              {
                "type": "string",
                "format": "uuid"
              },
              {
                "type": "object",
                "description": "The site's ID."
              }
            ]
          },
          "is_member": {
            "type": "boolean",
            "description": "Whether the account is a member of the site. Only a super admin account is shown sites it is\nnot a member of."
          },
          "name": {
            "type": "string",
            "description": "The site's name."
          },
          "slug": {
            "allOf": [
              {
                "type": "string"
              },
              {
                "type": "object",
                "description": "The site's short name, used to address it in paths such as `/api/v1/sites/{slug}/reports`."
              }
            ]
          }
        }
      },
      "Spec": {
        "type": "object",
        "description": "A spec describing this API or the report documents it works with.",
        "required": [
          "name",
          "title",
          "summary",
          "media_type",
          "url"
        ],
        "properties": {
          "media_type": {
            "type": "string",
            "description": "The file's media type: `application/json` or `text/markdown`."
          },
          "name": {
            "type": "string",
            "description": "The file name, with the version in it, such as `expr.v1.md`."
          },
          "summary": {
            "type": "string",
            "description": "What the spec covers."
          },
          "title": {
            "type": "string",
            "description": "What the spec is, with its version."
          },
          "url": {
            "type": "string",
            "description": "Where to download it."
          }
        }
      },
      "StartedRun": {
        "type": "object",
        "description": "A schedule run that has been started.",
        "required": [
          "occurrence_id"
        ],
        "properties": {
          "occurrence_id": {
            "allOf": [
              {
                "type": "string",
                "format": "uuid"
              },
              {
                "type": "object",
                "description": "The run's ID, as listed in the schedule's history."
              }
            ]
          }
        }
      },
      "TextSpan": {
        "type": "object",
        "description": "A part of a text, as byte offsets into its UTF-8 encoding.",
        "required": [
          "start",
          "end"
        ],
        "properties": {
          "end": {
            "type": "integer",
            "description": "The offset just after the last byte.",
            "minimum": 0
          },
          "start": {
            "type": "integer",
            "description": "The offset of the first byte.",
            "minimum": 0
          }
        }
      },
      "UpdateCategory": {
        "type": "object",
        "description": "A category's new name, description and color. All three are replaced: a field left out is\ncleared. The category stays where it is.",
        "required": [
          "name"
        ],
        "properties": {
          "color": {
            "oneOf": [
              {
                "type": "string",
                "description": "A color as `#` followed by six lowercase hex digits, e.g. `#2563eb`.",
                "pattern": "^#[0-9a-f]{6}$"
              },
              {
                "type": "null"
              }
            ],
            "description": "The category's color, as `#` and six lowercase hexadecimal digits, such as `#2563eb`. Leave it\nout to take the color of the category it is in."
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "description": "A description of the category. Leave it out for none."
          },
          "name": {
            "allOf": [
              {
                "type": "string"
              },
              {
                "type": "object",
                "description": "The category's name. Must not be blank, and must differ from the names of the other\ncategories in the same place."
              }
            ]
          }
        }
      },
      "UpdateMember": {
        "type": "object",
        "description": "A change to a site member.",
        "required": [
          "is_site_admin"
        ],
        "properties": {
          "is_site_admin": {
            "type": "boolean",
            "description": "Whether the member is a site admin."
          }
        }
      },
      "UpdateReport": {
        "type": "object",
        "description": "A report's new name, description, category, tags and attributes. All of them are replaced: a field\nleft out is cleared, and a report given no `category_id` is taken out of its category.",
        "required": [
          "name"
        ],
        "properties": {
          "attributes": {
            "type": "object",
            "description": "Custom name and value pairs for use by your application.",
            "additionalProperties": {
              "type": "string"
            },
            "propertyNames": {
              "type": "string"
            }
          },
          "category_id": {
            "oneOf": [
              {
                "type": "string",
                "format": "uuid"
              },
              {
                "type": "null"
              }
            ],
            "description": "The ID of the category to file the report in. Leave it out for a report in no category."
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "description": "A description of the report. Leave it out for none."
          },
          "name": {
            "allOf": [
              {
                "type": "string"
              },
              {
                "type": "object",
                "description": "The report's name. Must not be blank."
              }
            ]
          },
          "tags": {
            "allOf": [
              {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              {
                "type": "object",
                "description": "The report's tags, each in lowercase, not blank, and without spaces at either end. A tag\ngiven twice is kept once."
              }
            ]
          }
        }
      },
      "Warning": {
        "type": "object",
        "description": "A problem found while rendering: where it is in the report document, and what went wrong.",
        "required": [
          "message",
          "subreports"
        ],
        "properties": {
          "item_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The ID of the item the problem is in, or `null` if it is in the report itself, such as in\nits variables or a parameter's default."
          },
          "item_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "The name of that item, or `null` if the problem is in the report itself."
          },
          "kind": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/WarningKind",
                "description": "For a value that could not be evaluated, the marker shown in its place, without the `#`.\n`null` for a problem with the item as a whole."
              },
              {
                "type": "null"
              }
            ]
          },
          "message": {
            "type": "string",
            "description": "What went wrong."
          },
          "path": {
            "type": [
              "string",
              "null"
            ],
            "description": "Where the problem is, as a path such as `table.measures[1].expression` or\n`table.cellStyles[\"body:m1\"].color`. It starts at the item when `item_id` is set, and at the\nreport document when it is not, such as `parameters[0].default`. `null` when the problem is\nwith the item as a whole, such as a chart that could not be drawn or text cut off by a fixed\nsize, or with a gauge's scale, which is its `min`, `max` and `target` together."
          },
          "span": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/TextSpan",
                "description": "Where in the text at `path` the problem is, or `null` when that is not known or `path` is\n`null`."
              },
              {
                "type": "null"
              }
            ]
          },
          "subreports": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WarningSubreport"
            },
            "description": "The subreports the problem is inside, outermost first. Empty when it is in the rendered\nreport itself; otherwise `item_id` and `path` are in the last of these reports."
          }
        }
      },
      "WarningKind": {
        "type": "string",
        "description": "The marker a value that could not be evaluated shows in its place. `syntax`: the expression is\nmalformed. `name`: it names a field, function, variable or name that does not exist. `type`: a\nvalue has the wrong type, or a function was given the wrong number of arguments. `div0`: a\ndivision by zero. `error`: any other failure.",
        "enum": [
          "syntax",
          "name",
          "type",
          "div0",
          "error"
        ]
      },
      "WarningSubreport": {
        "type": "object",
        "description": "A subreport a warning is inside.",
        "required": [
          "item_id",
          "item_name",
          "report_id",
          "report_name"
        ],
        "properties": {
          "item_id": {
            "type": "string",
            "description": "The ID of the subreport item, in the report that contains it."
          },
          "item_name": {
            "type": "string",
            "description": "The name of the subreport item."
          },
          "report_id": {
            "type": "string",
            "description": "The ID of the report the subreport item shows."
          },
          "report_name": {
            "type": "string",
            "description": "The name of that report."
          }
        }
      }
    },
    "securitySchemes": {
      "api_key": {
        "type": "http",
        "scheme": "bearer",
        "description": "An API key, sent as `Authorization: Bearer <key>`. Keys start with `rr_`."
      }
    }
  },
  "security": [
    {
      "api_key": []
    }
  ],
  "tags": [
    {
      "name": "account",
      "description": "The account and API key a request is made with."
    },
    {
      "name": "sites",
      "description": "Sites and their members. Creating sites and managing members requires a super admin account."
    },
    {
      "name": "categories",
      "description": "The categories reports are filed in, which can be nested up to four levels deep."
    },
    {
      "name": "reports",
      "description": "Reports and their parameters, and rendering reports to files."
    },
    {
      "name": "drafts",
      "description": "Writing a report through the account's own draft of it. Nobody else sees the draft, and readers see its changes only once it is published."
    },
    {
      "name": "data-sources",
      "description": "The databases a report's datasets query: which ones the account can use, the tables they hold, and trying a query before it goes into a report."
    },
    {
      "name": "schedules",
      "description": "Running report schedules on demand."
    },
    {
      "name": "external-users",
      "description": "Signing your application's users in to a site and keeping their accounts up to date. Only the API key of the site's external sign-in can use these endpoints."
    },
    {
      "name": "discovery",
      "description": "What this API is, and the specs that describe it and the report documents it works with. These endpoints need no API key."
    }
  ]
}