{
  "swagger": "2.0",
  "info": {
    "title": "GoatCounter",
    "description": "\u003cp\u003eReference documentation for the \u003ca href=\"https://www.goatcounter.com\"\u003eGoatCounter\u003c/a\u003e API.\u003c/p\u003e \u003cp\u003eSee \u003ca href=\"/help/api\"\u003e/help/api\u003c/a\u003e for a more general introduction and a few examples.\u003c/p\u003e \u003cp\u003eViewing this documentation at https://[my-code].goatcounter.com/api2.html (rather than using the www.goatcounter.com) enables the \"try\" feature.\u003c/p\u003e",
    "version": "0.1",
    "contact": {
      "name": "Martin Tournoij",
      "url": "https://www.goatcounter.com/help/api",
      "email": "support@goatcounter.com"
    }
  },
  "securityDefinitions": {
    "basicAuth": {
      "type": "basic"
    }
  },
  "security": [
    {
      "basicAuth": []
    }
  ],
  "consumes": [
    "application/json"
  ],
  "produces": [
    "application/json"
  ],
  "tags": [
    {
      "name": "count"
    },
    {
      "name": "export"
    },
    {
      "name": "paths"
    },
    {
      "name": "sites"
    },
    {
      "name": "stats"
    },
    {
      "name": "users"
    }
  ],
  "paths": {
    "/api/v0/count": {
      "post": {
        "consumes": [
          "application/json"
        ],
        "description": "This can count one or more pageviews. Pageviews are not persisted\nimmediately, but persisted in the background every 10 seconds.\n\nThe maximum amount of pageviews per request is 500.\n\nErrors will have the key set to the index of the pageview. Any pageviews not\nlisted have been processed and shouldn't be sent again.",
        "operationId": "POST_api_v0_count",
        "parameters": [
          {
            "in": "body",
            "name": "handlers.APICountRequest",
            "required": true,
            "schema": {
              "$ref": "#/definitions/handlers.APICountRequest"
            }
          }
        ],
        "produces": [
          "application/json"
        ],
        "responses": {
          "202": {
            "description": "202 Accepted (no data)"
          },
          "400": {
            "description": "400 Bad Request",
            "schema": {
              "$ref": "#/definitions/handlers.apiError"
            }
          },
          "401": {
            "description": "401 Unauthorized",
            "schema": {
              "$ref": "#/definitions/handlers.authError"
            }
          },
          "403": {
            "description": "403 Forbidden",
            "schema": {
              "$ref": "#/definitions/handlers.authError"
            }
          }
        },
        "summary": "Count pageviews.",
        "tags": [
          "count"
        ]
      }
    },
    "/api/v0/export": {
      "post": {
        "consumes": [
          "application/json"
        ],
        "description": "This starts a new export in the background; this can only be done once an\nhour.",
        "operationId": "POST_api_v0_export",
        "parameters": [
          {
            "in": "body",
            "name": "handlers.apiExportRequest",
            "required": true,
            "schema": {
              "$ref": "#/definitions/handlers.apiExportRequest"
            }
          }
        ],
        "produces": [
          "application/json"
        ],
        "responses": {
          "202": {
            "description": "202 Accepted",
            "schema": {
              "$ref": "#/definitions/v2.Export"
            }
          },
          "400": {
            "description": "400 Bad Request",
            "schema": {
              "$ref": "#/definitions/handlers.apiError"
            }
          },
          "401": {
            "description": "401 Unauthorized",
            "schema": {
              "$ref": "#/definitions/handlers.authError"
            }
          },
          "403": {
            "description": "403 Forbidden",
            "schema": {
              "$ref": "#/definitions/handlers.authError"
            }
          }
        },
        "summary": "Start a new export in the background.",
        "tags": [
          "export"
        ]
      }
    },
    "/api/v0/export/{id}": {
      "get": {
        "operationId": "GET_api_v0_export_{id}",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "type": "integer"
          }
        ],
        "produces": [
          "application/json"
        ],
        "responses": {
          "200": {
            "description": "200 OK",
            "schema": {
              "$ref": "#/definitions/v2.Export"
            }
          },
          "400": {
            "description": "400 Bad Request",
            "schema": {
              "$ref": "#/definitions/handlers.apiError"
            }
          },
          "401": {
            "description": "401 Unauthorized",
            "schema": {
              "$ref": "#/definitions/handlers.authError"
            }
          },
          "403": {
            "description": "403 Forbidden",
            "schema": {
              "$ref": "#/definitions/handlers.authError"
            }
          }
        },
        "summary": "Get details about an export.",
        "tags": [
          "export"
        ]
      }
    },
    "/api/v0/export/{id}/download": {
      "get": {
        "description": "The export may take a while to generate, depending on the size. It will\nreturn a 202 Accepted status code if the export ID is still running.\n\nExport files are kept for 24 hours, after which they're deleted. This will\nreturn a 400 Gone status code if the export has been deleted.",
        "operationId": "GET_api_v0_export_{id}_download",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "type": "integer"
          }
        ],
        "produces": [
          "application/json",
          "text/csv"
        ],
        "responses": {
          "200": {
            "description": "200 OK (text/csv data)"
          },
          "202": {
            "description": "202 Accepted",
            "schema": {
              "$ref": "#/definitions/handlers.apiError"
            }
          },
          "400": {
            "description": "400 Bad Request",
            "schema": {
              "$ref": "#/definitions/handlers.apiError"
            }
          },
          "401": {
            "description": "401 Unauthorized",
            "schema": {
              "$ref": "#/definitions/handlers.authError"
            }
          },
          "403": {
            "description": "403 Forbidden",
            "schema": {
              "$ref": "#/definitions/handlers.authError"
            }
          }
        },
        "summary": "Download an export file.",
        "tags": [
          "export"
        ]
      }
    },
    "/api/v0/me": {
      "get": {
        "operationId": "GET_api_v0_me",
        "produces": [
          "application/json"
        ],
        "responses": {
          "200": {
            "description": "200 OK",
            "schema": {
              "$ref": "#/definitions/handlers.meResponse"
            }
          },
          "400": {
            "description": "400 Bad Request",
            "schema": {
              "$ref": "#/definitions/handlers.apiError"
            }
          },
          "401": {
            "description": "401 Unauthorized",
            "schema": {
              "$ref": "#/definitions/handlers.authError"
            }
          },
          "403": {
            "description": "403 Forbidden",
            "schema": {
              "$ref": "#/definitions/handlers.authError"
            }
          }
        },
        "summary": "Get information about the current user and API key.",
        "tags": [
          "users"
        ]
      }
    },
    "/api/v0/paths": {
      "get": {
        "operationId": "GET_api_v0_paths",
        "parameters": [
          {
            "default": "20",
            "description": "Limit number of returned results",
            "in": "query",
            "maximum": 200,
            "minimum": 1,
            "name": "Limit",
            "type": "integer"
          },
          {
            "description": "Only select paths after this ID, for pagination.",
            "in": "query",
            "name": "After",
            "type": "integer"
          }
        ],
        "produces": [
          "application/json"
        ],
        "responses": {
          "200": {
            "description": "200 OK",
            "schema": {
              "$ref": "#/definitions/handlers.apiPathsResponse"
            }
          },
          "400": {
            "description": "400 Bad Request",
            "schema": {
              "$ref": "#/definitions/handlers.apiError"
            }
          },
          "401": {
            "description": "401 Unauthorized",
            "schema": {
              "$ref": "#/definitions/handlers.authError"
            }
          },
          "403": {
            "description": "403 Forbidden",
            "schema": {
              "$ref": "#/definitions/handlers.authError"
            }
          }
        },
        "summary": "Get an overview of paths on this site (without statistics).",
        "tags": [
          "paths"
        ]
      }
    },
    "/api/v0/sites": {
      "get": {
        "operationId": "GET_api_v0_sites",
        "produces": [
          "application/json"
        ],
        "responses": {
          "200": {
            "description": "200 OK",
            "schema": {
              "$ref": "#/definitions/handlers.apiSitesResponse"
            }
          },
          "400": {
            "description": "400 Bad Request",
            "schema": {
              "$ref": "#/definitions/handlers.apiError"
            }
          },
          "401": {
            "description": "401 Unauthorized",
            "schema": {
              "$ref": "#/definitions/handlers.authError"
            }
          },
          "403": {
            "description": "403 Forbidden",
            "schema": {
              "$ref": "#/definitions/handlers.authError"
            }
          }
        },
        "summary": "List all sites.",
        "tags": [
          "sites"
        ]
      },
      "put": {
        "consumes": [
          "application/json"
        ],
        "operationId": "PUT_api_v0_sites",
        "parameters": [
          {
            "in": "body",
            "name": "goatcounter.Site",
            "required": true,
            "schema": {
              "$ref": "#/definitions/goatcounter.Site"
            }
          }
        ],
        "produces": [
          "application/json"
        ],
        "responses": {
          "200": {
            "description": "200 OK",
            "schema": {
              "$ref": "#/definitions/goatcounter.Site"
            }
          },
          "400": {
            "description": "400 Bad Request",
            "schema": {
              "$ref": "#/definitions/handlers.apiError"
            }
          },
          "401": {
            "description": "401 Unauthorized",
            "schema": {
              "$ref": "#/definitions/handlers.authError"
            }
          },
          "403": {
            "description": "403 Forbidden",
            "schema": {
              "$ref": "#/definitions/handlers.authError"
            }
          }
        },
        "summary": "Create a new site.",
        "tags": [
          "sites"
        ]
      }
    },
    "/api/v0/sites/{id}": {
      "get": {
        "description": "Get all information about one site.",
        "operationId": "GET_api_v0_sites_{id}",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "type": "integer"
          }
        ],
        "produces": [
          "application/json"
        ],
        "responses": {
          "200": {
            "description": "200 OK",
            "schema": {
              "$ref": "#/definitions/goatcounter.Site"
            }
          },
          "400": {
            "description": "400 Bad Request",
            "schema": {
              "$ref": "#/definitions/handlers.apiError"
            }
          },
          "401": {
            "description": "401 Unauthorized",
            "schema": {
              "$ref": "#/definitions/handlers.authError"
            }
          },
          "403": {
            "description": "403 Forbidden",
            "schema": {
              "$ref": "#/definitions/handlers.authError"
            }
          }
        },
        "summary": "Get information about a site.",
        "tags": [
          "sites"
        ]
      },
      "post": {
        "consumes": [
          "application/json"
        ],
        "description": "A POST request will *replace* the entire site with what's sent, blanking out\nany existing fields that may exist. A PATCH request will only update the\nfields that are sent.",
        "operationId": "POST_api_v0_sites_{id}",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "type": "integer"
          },
          {
            "in": "body",
            "name": "handlers.apiSiteUpdateRequest",
            "required": true,
            "schema": {
              "$ref": "#/definitions/handlers.apiSiteUpdateRequest"
            }
          }
        ],
        "produces": [
          "application/json"
        ],
        "responses": {
          "200": {
            "description": "200 OK",
            "schema": {
              "$ref": "#/definitions/goatcounter.Site"
            }
          },
          "400": {
            "description": "400 Bad Request",
            "schema": {
              "$ref": "#/definitions/handlers.apiError"
            }
          },
          "401": {
            "description": "401 Unauthorized",
            "schema": {
              "$ref": "#/definitions/handlers.authError"
            }
          },
          "403": {
            "description": "403 Forbidden",
            "schema": {
              "$ref": "#/definitions/handlers.authError"
            }
          }
        },
        "summary": "Update a site.",
        "tags": [
          "sites"
        ]
      },
      "patch": {
        "consumes": [
          "application/json"
        ],
        "description": "A POST request will *replace* the entire site with what's sent, blanking out\nany existing fields that may exist. A PATCH request will only update the\nfields that are sent.",
        "operationId": "PATCH_api_v0_sites_{id}",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "type": "integer"
          },
          {
            "in": "body",
            "name": "handlers.apiSiteUpdateRequest",
            "required": true,
            "schema": {
              "$ref": "#/definitions/handlers.apiSiteUpdateRequest"
            }
          }
        ],
        "produces": [
          "application/json"
        ],
        "responses": {
          "200": {
            "description": "200 OK",
            "schema": {
              "$ref": "#/definitions/goatcounter.Site"
            }
          },
          "400": {
            "description": "400 Bad Request",
            "schema": {
              "$ref": "#/definitions/handlers.apiError"
            }
          },
          "401": {
            "description": "401 Unauthorized",
            "schema": {
              "$ref": "#/definitions/handlers.authError"
            }
          },
          "403": {
            "description": "403 Forbidden",
            "schema": {
              "$ref": "#/definitions/handlers.authError"
            }
          }
        },
        "summary": "Update a site.",
        "tags": [
          "sites"
        ]
      }
    },
    "/api/v0/stats/hits": {
      "get": {
        "operationId": "GET_api_v0_stats_hits",
        "parameters": [
          {
            "default": "one week ago",
            "description": "Start time, should be rounded to the hour.",
            "format": "date-time",
            "in": "query",
            "name": "start",
            "type": "string"
          },
          {
            "default": "current time",
            "description": "End time, should be rounded to the hour.",
            "format": "date-time",
            "in": "query",
            "name": "end",
            "type": "string"
          },
          {
            "default": "20",
            "description": "Maximum number of pages to get.",
            "in": "query",
            "maximum": 100,
            "minimum": 1,
            "name": "limit",
            "type": "integer"
          },
          {
            "description": "Set Max value in the response to the highest daily, weekly, or\nmonthly value, instead of hourly.\n\nThe Hourly, Daily, Weekly, and Monthly values are always included in\nthe response – this only affects the Max value, which is useful if\nyou want to draw charts like the GoatCounter dashboard: you need to\nknow the maximum Y-axis value of the chart to draw it.",
            "enum": [
              "enum:",
              "hour",
              "day",
              "week",
              "month"
            ],
            "in": "query",
            "name": "group",
            "type": "integer"
          },
          {
            "description": "Get values for include_paths and exclude_paths by path name, rather\nthan path ID. This is more convenient in some cases, but also a bit\nslower.",
            "in": "query",
            "name": "path_by_name",
            "type": "boolean"
          },
          {
            "description": "Deprecated: identical to group=day and will be removed in the future.",
            "in": "query",
            "name": "daily",
            "type": "boolean"
          },
          {
            "description": "Include only these path IDs; default is to include everything.\n\nIf path_by_name is set, it will look up paths by name instead of ID.",
            "in": "query",
            "items": {
              "type": "string"
            },
            "name": "include_paths",
            "type": "array"
          },
          {
            "description": "Exclude these path IDs, for pagination.\n\nIf path_by_name is set, it will look up paths by name instead of ID.",
            "in": "query",
            "items": {
              "type": "string"
            },
            "name": "exclude_paths",
            "type": "array"
          }
        ],
        "produces": [
          "application/json"
        ],
        "responses": {
          "200": {
            "description": "200 OK",
            "schema": {
              "$ref": "#/definitions/handlers.apiHitsResponse"
            }
          },
          "400": {
            "description": "400 Bad Request",
            "schema": {
              "$ref": "#/definitions/handlers.apiError"
            }
          },
          "401": {
            "description": "401 Unauthorized",
            "schema": {
              "$ref": "#/definitions/handlers.authError"
            }
          },
          "403": {
            "description": "403 Forbidden",
            "schema": {
              "$ref": "#/definitions/handlers.authError"
            }
          }
        },
        "summary": "Get an overview of pageviews.",
        "tags": [
          "stats"
        ]
      }
    },
    "/api/v0/stats/hits/{path_id}": {
      "get": {
        "operationId": "GET_api_v0_stats_hits_{path_id}",
        "parameters": [
          {
            "default": "one week ago",
            "description": "Start time, should be rounded to the hour.",
            "format": "date-time",
            "in": "query",
            "name": "start",
            "type": "string"
          },
          {
            "default": "current time",
            "description": "End time, should be rounded to the hour.",
            "format": "date-time",
            "in": "query",
            "name": "end",
            "type": "string"
          },
          {
            "in": "path",
            "name": "path_id",
            "required": true,
            "type": "integer"
          },
          {
            "description": "Offset for pagination.",
            "in": "query",
            "name": "offset",
            "type": "integer"
          },
          {
            "default": "20",
            "description": "Maximum number of pages to get.",
            "in": "query",
            "maximum": 100,
            "minimum": 1,
            "name": "limit",
            "type": "integer"
          }
        ],
        "produces": [
          "application/json"
        ],
        "responses": {
          "200": {
            "description": "200 OK",
            "schema": {
              "$ref": "#/definitions/handlers.apiRefsResponse"
            }
          },
          "400": {
            "description": "400 Bad Request",
            "schema": {
              "$ref": "#/definitions/handlers.apiError"
            }
          },
          "401": {
            "description": "401 Unauthorized",
            "schema": {
              "$ref": "#/definitions/handlers.authError"
            }
          },
          "403": {
            "description": "403 Forbidden",
            "schema": {
              "$ref": "#/definitions/handlers.authError"
            }
          }
        },
        "summary": "Get an overview of referral information for a path.",
        "tags": [
          "stats"
        ]
      }
    },
    "/api/v0/stats/total": {
      "get": {
        "description": "This is mostly useful to display things like browser stats as a percentage of\nthe total; the /api/v0/pages endpoint only counts the pageviews until it's\npaginated.",
        "operationId": "GET_api_v0_stats_total",
        "parameters": [
          {
            "default": "one week ago",
            "description": "Start time, should be rounded to the hour.",
            "format": "date-time",
            "in": "query",
            "name": "start",
            "type": "string"
          },
          {
            "default": "current time",
            "description": "End time, should be rounded to the hour.",
            "format": "date-time",
            "in": "query",
            "name": "end",
            "type": "string"
          },
          {
            "description": "Get values for include_paths and exclude_paths by path name, rather\nthan path ID. This is more convenient in some cases, but also a bit\nslower.",
            "in": "query",
            "name": "path_by_name",
            "type": "boolean"
          },
          {
            "description": "Include only these path IDs; default is to include everything.\n\nIf path_by_name is set, it will look up paths by name instead of ID.",
            "in": "query",
            "items": {
              "type": "string"
            },
            "name": "include_paths",
            "type": "array"
          }
        ],
        "produces": [
          "application/json"
        ],
        "responses": {
          "200": {
            "description": "200 OK",
            "schema": {
              "$ref": "#/definitions/handlers.apiCountTotalResponse"
            }
          },
          "400": {
            "description": "400 Bad Request",
            "schema": {
              "$ref": "#/definitions/handlers.apiError"
            }
          },
          "401": {
            "description": "401 Unauthorized",
            "schema": {
              "$ref": "#/definitions/handlers.authError"
            }
          },
          "403": {
            "description": "403 Forbidden",
            "schema": {
              "$ref": "#/definitions/handlers.authError"
            }
          }
        },
        "summary": "Count total number of pageviews for a date range.",
        "tags": [
          "stats"
        ]
      }
    },
    "/api/v0/stats/{page}": {
      "get": {
        "description": "Page can be: browsers, systems, locations, languages, sizes, campaigns,\ntoprefs.",
        "operationId": "GET_api_v0_stats_{page}",
        "parameters": [
          {
            "default": "one week ago",
            "description": "Start time, should be rounded to the hour.",
            "format": "date-time",
            "in": "query",
            "name": "start",
            "type": "string"
          },
          {
            "in": "path",
            "name": "page",
            "required": true,
            "type": "string"
          },
          {
            "default": "current time",
            "description": "End time, should be rounded to the hour.",
            "format": "date-time",
            "in": "query",
            "name": "end",
            "type": "string"
          },
          {
            "description": "Offset for pagination.",
            "in": "query",
            "name": "offset",
            "type": "integer"
          },
          {
            "default": "20",
            "description": "Maximum number of pages to get.",
            "in": "query",
            "maximum": 100,
            "minimum": 1,
            "name": "limit",
            "type": "integer"
          },
          {
            "description": "Get values for include_paths and exclude_paths by path name, rather\nthan path ID. This is more convenient in some cases, but also a bit\nslower.",
            "in": "query",
            "name": "path_by_name",
            "type": "boolean"
          },
          {
            "description": "Include only these path IDs; default is to include everything.\n\nIf path_by_name is set, it will look up paths by name instead of ID.",
            "in": "query",
            "items": {
              "type": "string"
            },
            "name": "include_paths",
            "type": "array"
          }
        ],
        "produces": [
          "application/json"
        ],
        "responses": {
          "200": {
            "description": "200 OK",
            "schema": {
              "$ref": "#/definitions/handlers.apiStatsResponse"
            }
          },
          "400": {
            "description": "400 Bad Request",
            "schema": {
              "$ref": "#/definitions/handlers.apiError"
            }
          },
          "401": {
            "description": "401 Unauthorized",
            "schema": {
              "$ref": "#/definitions/handlers.authError"
            }
          },
          "403": {
            "description": "403 Forbidden",
            "schema": {
              "$ref": "#/definitions/handlers.authError"
            }
          }
        },
        "summary": "Get browser/system/etc. stats.",
        "tags": [
          "stats"
        ]
      }
    },
    "/api/v0/stats/{page}/{id}": {
      "get": {
        "description": "Page can be: browsers, systems, locations, sizes, campaigns, toprefs.",
        "operationId": "GET_api_v0_stats_{page}_{id}",
        "parameters": [
          {
            "default": "one week ago",
            "description": "Start time, should be rounded to the hour.",
            "format": "date-time",
            "in": "query",
            "name": "start",
            "type": "string"
          },
          {
            "in": "path",
            "name": "page",
            "required": true,
            "type": "string"
          },
          {
            "default": "current time",
            "description": "End time, should be rounded to the hour.",
            "format": "date-time",
            "in": "query",
            "name": "end",
            "type": "string"
          },
          {
            "description": "Offset for pagination.",
            "in": "query",
            "name": "offset",
            "type": "integer"
          },
          {
            "default": "20",
            "description": "Maximum number of pages to get.",
            "in": "query",
            "maximum": 100,
            "minimum": 1,
            "name": "limit",
            "type": "integer"
          },
          {
            "in": "path",
            "name": "id",
            "required": true,
            "type": "integer"
          },
          {
            "description": "Get values for include_paths and exclude_paths by path name, rather\nthan path ID. This is more convenient in some cases, but also a bit\nslower.",
            "in": "query",
            "name": "path_by_name",
            "type": "boolean"
          },
          {
            "description": "Include only these path IDs; default is to include everything.\n\nIf path_by_name is set, it will look up paths by name instead of ID.",
            "in": "query",
            "items": {
              "type": "string"
            },
            "name": "include_paths",
            "type": "array"
          }
        ],
        "produces": [
          "application/json"
        ],
        "responses": {
          "200": {
            "description": "200 OK",
            "schema": {
              "$ref": "#/definitions/handlers.apiStatsResponse"
            }
          },
          "400": {
            "description": "400 Bad Request",
            "schema": {
              "$ref": "#/definitions/handlers.apiError"
            }
          },
          "401": {
            "description": "401 Unauthorized",
            "schema": {
              "$ref": "#/definitions/handlers.authError"
            }
          },
          "403": {
            "description": "403 Forbidden",
            "schema": {
              "$ref": "#/definitions/handlers.authError"
            }
          }
        },
        "summary": "Get detailed stats for an ID.",
        "tags": [
          "stats"
        ]
      }
    }
  },
  "definitions": {
    "goatcounter.APIToken": {
      "title": "APIToken",
      "type": "object",
      "properties": {
        "name": {
          "type": "string"
        },
        "permissions": {
          "type": "integer"
        },
        "sites": {
          "type": "integer",
          "items": {}
        }
      }
    },
    "goatcounter.HitList": {
      "title": "HitList",
      "type": "object",
      "properties": {
        "count": {
          "description": "Number of visitors for the selected date range.",
          "type": "integer"
        },
        "event": {
          "description": "Is this an event?",
          "type": "boolean"
        },
        "max": {
          "description": "Highest visitors per hour or day (depending on daily being set).",
          "type": "integer"
        },
        "path": {
          "description": "Path name (e.g. /hello.html).",
          "type": "string"
        },
        "path_id": {
          "description": "Path ID",
          "type": "integer"
        },
        "ref_scheme": {
          "description": "What kind of referral this is; only set when retrieving referrals .\n\n h HTTP Referal header.\n g Generated; for example are Google domains (google.com, google.nl,\n google.co.nz, etc.) are grouped as the generated referral \"Google\".\n c Campaign (via query parameter)\n o Other",
          "type": "string",
          "enum": [
            "enum:",
            "h",
            "g",
            "c",
            "o"
          ]
        },
        "stats": {
          "description": "Statistics by day and hour.",
          "type": "array",
          "items": {
            "$ref": "#/definitions/goatcounter.HitListStat"
          }
        },
        "title": {
          "description": "Page title.",
          "type": "string"
        }
      }
    },
    "goatcounter.HitListStat": {
      "title": "HitListStat",
      "type": "object",
      "properties": {
        "daily": {
          "description": "Total visitors for this day.",
          "type": "integer"
        },
        "day": {
          "description": "Day these statistics are for.",
          "type": "string",
          "format": "date"
        },
        "hourly": {
          "description": "Visitors per hour.",
          "type": "array",
          "items": {
            "type": "integer"
          }
        },
        "monthly": {
          "description": "Visitors for the month; set on first day of the month. This value will\nnot be set if it's 0.",
          "type": "integer"
        },
        "weekly": {
          "description": "Visitors for the week; set once every 7 days. This value will not be set\nif it's 0.",
          "type": "integer"
        }
      }
    },
    "goatcounter.HitStat": {
      "title": "HitStat",
      "type": "object",
      "properties": {
        "count": {
          "description": "Number of visitors.",
          "type": "integer"
        },
        "id": {
          "description": "ID for selecting more details; not present in the detail view.",
          "type": "string"
        },
        "name": {
          "description": "Display name.",
          "type": "string"
        },
        "ref_scheme": {
          "description": "What kind of referral this is; only set when retrieving referrals .\n\n h HTTP Referal header.\n g Generated; for example are Google domains (google.com, google.nl,\n google.co.nz, etc.) are grouped as the generated referral \"Google\".\n c Campaign (via query parameter)\n o Other",
          "type": "string",
          "enum": [
            "enum:",
            "h",
            "g",
            "c",
            "o"
          ]
        }
      }
    },
    "goatcounter.Path": {
      "title": "Path",
      "type": "object",
      "properties": {
        "event": {
          "description": "Is this an event?",
          "type": "boolean"
        },
        "id": {
          "description": "Path ID",
          "type": "integer"
        },
        "path": {
          "description": "Path name",
          "type": "string"
        },
        "title": {
          "description": "Page title",
          "type": "string"
        }
      }
    },
    "goatcounter.Site": {
      "title": "Site",
      "type": "object",
      "properties": {
        "cname": {
          "description": "Custom domain, e.g. \"stats.example.com\".\n\nWhen self-hosting this is the domain/vhost your site is accessible at.",
          "type": "string"
        },
        "cname_setup_at": {
          "description": "When the CNAME was verified.",
          "type": "string",
          "format": "date-time",
          "readOnly": true
        },
        "code": {
          "description": "Domain code (e.g. \"arp242\", which makes arp242.goatcounter.com). Only\nused for goatcounter.com and not when self-hosting.",
          "type": "string"
        },
        "created_at": {
          "type": "string",
          "format": "date-time"
        },
        "first_hit_at": {
          "type": "string",
          "format": "date-time"
        },
        "id": {
          "type": "integer",
          "readOnly": true
        },
        "link_domain": {
          "description": "Site domain for linking (www.arp242.net). Note this can be a full URL and\nis a bit misnamed.",
          "type": "string"
        },
        "parent": {
          "type": "integer",
          "readOnly": true
        },
        "received_data": {
          "description": "Whether this site has received any data; will be true after the first\npageview.",
          "type": "boolean"
        },
        "setttings": {
          "$ref": "#/definitions/goatcounter.SiteSettings"
        },
        "state": {
          "type": "string"
        },
        "updated_at": {
          "type": "string",
          "format": "date-time"
        },
        "user_defaults": {
          "$ref": "#/definitions/goatcounter.UserSettings"
        }
      }
    },
    "goatcounter.SiteSettings": {
      "title": "SiteSettings",
      "description": "SiteSettings contains all the user-configurable settings for a site, with\nthe exception of the domain settings.\n\nThis is stored as JSON in the database.",
      "type": "object",
      "properties": {
        "allow_bosmang": {
          "type": "boolean"
        },
        "allow_counter": {
          "type": "boolean"
        },
        "allow_embed": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "collect": {
          "type": "integer"
        },
        "collect_regions": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "data_retention": {
          "type": "integer"
        },
        "ignore_ips": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "public": {
          "type": "string"
        },
        "secret": {
          "type": "string"
        }
      }
    },
    "goatcounter.User": {
      "title": "User",
      "type": "object",
      "properties": {
        "access": {
          "type": "object",
          "readOnly": true,
          "additionalProperties": {
            "$ref": "#/definitions/goatcounter.UserAccess"
          }
        },
        "created_at": {
          "type": "string",
          "format": "date-time",
          "readOnly": true
        },
        "email": {
          "type": "string"
        },
        "email_verified": {
          "type": "boolean",
          "readOnly": true
        },
        "id": {
          "type": "integer",
          "readOnly": true
        },
        "last_report_at": {
          "description": "Keep track when the last email report was sent, so we don't double-send them.",
          "type": "string",
          "format": "date-time"
        },
        "login_at": {
          "type": "string",
          "format": "date-time",
          "readOnly": true
        },
        "open_at": {
          "type": "string",
          "format": "date-time",
          "readOnly": true
        },
        "reset_at": {
          "type": "string",
          "format": "date-time",
          "readOnly": true
        },
        "settings": {
          "$ref": "#/definitions/goatcounter.UserSettings"
        },
        "site": {
          "type": "integer",
          "readOnly": true
        },
        "totp_enabled": {
          "type": "boolean",
          "readOnly": true
        },
        "updated_at": {
          "type": "string",
          "format": "date-time",
          "readOnly": true
        }
      }
    },
    "goatcounter.UserAccess": {
      "title": "UserAccess",
      "type": "object"
    },
    "goatcounter.UserSettings": {
      "title": "UserSettings",
      "description": "UserSettings are all user preferences.",
      "type": "object",
      "properties": {
        "date_format": {
          "type": "string"
        },
        "datepicker": {
          "type": "boolean"
        },
        "email_reports": {
          "type": "integer"
        },
        "fewer_numbers": {
          "type": "boolean"
        },
        "fewer_numbers_lock_until": {
          "type": "string",
          "format": "date-time"
        },
        "language": {
          "type": "string"
        },
        "number_format": {
          "type": "string"
        },
        "sunday_starts_week": {
          "type": "boolean"
        },
        "theme": {
          "type": "string"
        },
        "timezone": {
          "$ref": "#/definitions/tz.Zone"
        },
        "twenty_four_hours": {
          "type": "boolean"
        },
        "views": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/goatcounter.View"
          }
        },
        "widgets": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/goatcounter.Widget"
          }
        }
      }
    },
    "goatcounter.View": {
      "title": "View",
      "description": "Views for the dashboard; these settings apply to all widget and are\nconfigurable in the yellow box at the top.",
      "type": "object",
      "properties": {
        "filter": {
          "type": "string"
        },
        "group": {
          "type": "integer"
        },
        "name": {
          "type": "string"
        },
        "period": {
          "description": "\"week\", \"month\", etc., or n days: \"8\"",
          "type": "string"
        }
      }
    },
    "goatcounter.Widget": {
      "title": "Widget",
      "description": "Widgets is a list of widgets to be printed, in order.",
      "type": "object"
    },
    "handlers.APICountRequest": {
      "title": "APICountRequest",
      "type": "object",
      "properties": {
        "filter": {
          "description": "Filter pageviews; accepted values:\n\n ip Ignore requests coming from IP addresses listed in \"Settings → Ignore IP\". Requires the IP field to be set.\n\n[\"ip\"] is used if this field isn't sent; send an empty array ([]) to not\nfilter anything.\n\nThe X-Goatcounter-Filter header will be set to a list of indexes if any\npageviews are filtered; for example:\n\n X-Goatcounter-Filter: 5, 10\n\nThis header will be omitted if nothing is filtered.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "hits": {
          "description": "Hits is the list of pageviews.",
          "type": "array",
          "items": {
            "$ref": "#/definitions/handlers.APICountRequestHit"
          }
        },
        "no_sessions": {
          "description": "By default it's an error to send pageviews that don't have either a\nSession or UserAgent and IP set. This avoids accidental errors.\n\nWhen this is set it will just continue without recording sessions for\npageviews that don't have these parameters set.",
          "type": "boolean"
        }
      }
    },
    "handlers.APICountRequestHit": {
      "title": "APICountRequestHit",
      "type": "object",
      "required": [
        "path"
      ],
      "properties": {
        "bot": {
          "description": "Hint if this should be considered a bot; should be one of the JSBot*`\nconstants from isbot; note the backend may override this if it\ndetects a bot using another method.\nhttps://github.com/zgoat/isbot/blob/master/isbot.go#L28",
          "type": "integer"
        },
        "created_at": {
          "description": "Time this pageview should be recorded at; this can be in the past,\nbut not in the future.",
          "type": "string",
          "format": "date-time"
        },
        "event": {
          "description": "Is this an event?",
          "type": "boolean"
        },
        "ip": {
          "description": "IP to get location from; not used if location is set. Also used for\nsession generation.",
          "type": "string"
        },
        "location": {
          "description": "Location as ISO-3166-1 alpha2 string (e.g. NL, ID, etc.)",
          "type": "string"
        },
        "path": {
          "description": "Path of the pageview, or the event name.",
          "type": "string"
        },
        "query": {
          "description": "Query parameters for this pageview, used to get campaign parameters.",
          "type": "string"
        },
        "ref": {
          "description": "Referrer value, can be an URL (i.e. the Referal: header) or any\nstring.",
          "type": "string"
        },
        "session": {
          "description": "Normally a session is based on hash(User-Agent+IP+salt), but if you don't\nsend the IP address then we can't determine the session.\n\nIn those cases, you can store your own session identifiers and send them\nalong. Note these will not be stored in the database as the sessionID\n(just as the hashes aren't), they're just used as a unique grouping\nidentifier.",
          "type": "string"
        },
        "size": {
          "description": "Screen width.\n\nFor compatibility it also accepts the size as \"width,height,scaling\", but\nthe height and scaling are not used and this format is deprecated.",
          "type": "array",
          "items": {
            "type": "number"
          }
        },
        "title": {
          "description": "Page title, or some descriptive event title.",
          "type": "string"
        },
        "user_agent": {
          "description": "User-Agent header.",
          "type": "string"
        }
      }
    },
    "handlers.apiCountTotalResponse": {
      "title": "apiCountTotalResponse",
      "type": "object",
      "properties": {
        "stats": {
          "description": "Total overview per day and hour.",
          "type": "array",
          "items": {
            "$ref": "#/definitions/goatcounter.HitListStat"
          }
        },
        "total": {
          "description": "Total number of visitors (including events).",
          "type": "integer"
        },
        "total_events": {
          "description": "Total number of visitors for events.",
          "type": "integer"
        },
        "total_utc": {
          "description": "Total number of visitors in UTC. The browser, system, etc, stats are\nalways in UTC.",
          "type": "integer"
        }
      }
    },
    "handlers.apiError": {
      "title": "apiError",
      "description": "Generic API error. An error will have either the \"error\" or \"errors\"\nfield set, but not both.",
      "type": "object",
      "properties": {
        "error": {
          "type": "string"
        },
        "errors": {
          "type": "object"
        }
      }
    },
    "handlers.apiExportRequest": {
      "title": "apiExportRequest",
      "type": "object",
      "properties": {
        "start_from_hit_id": {
          "description": "Pagination cursor; only export hits with an ID greater than this.",
          "type": "integer"
        }
      }
    },
    "handlers.apiHitsResponse": {
      "title": "apiHitsResponse",
      "type": "object",
      "properties": {
        "hits": {
          "description": "Sorted list of paths with their visitor and pageview count.",
          "type": "array",
          "items": {
            "$ref": "#/definitions/goatcounter.HitList"
          }
        },
        "more": {
          "description": "More hits after this?",
          "type": "boolean"
        },
        "total": {
          "description": "Total number of visitors in the returned result.",
          "type": "integer"
        }
      }
    },
    "handlers.apiPathsResponse": {
      "title": "apiPathsResponse",
      "type": "object",
      "properties": {
        "more": {
          "description": "True if there are more paths.",
          "type": "boolean"
        },
        "paths": {
          "description": "List of paths, sorted by ID.",
          "type": "array",
          "items": {
            "$ref": "#/definitions/goatcounter.Path"
          }
        }
      }
    },
    "handlers.apiRefsResponse": {
      "title": "apiRefsResponse",
      "type": "object",
      "properties": {
        "more": {
          "type": "boolean"
        },
        "refs": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/goatcounter.HitStat"
          }
        }
      }
    },
    "handlers.apiSiteUpdateRequest": {
      "title": "apiSiteUpdateRequest",
      "type": "object",
      "properties": {
        "cname": {
          "type": "string"
        },
        "link_domain": {
          "type": "string"
        },
        "settings": {
          "$ref": "#/definitions/goatcounter.SiteSettings"
        }
      }
    },
    "handlers.apiSitesResponse": {
      "title": "apiSitesResponse",
      "type": "object",
      "properties": {
        "sites": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/goatcounter.Site"
          }
        }
      }
    },
    "handlers.apiStatsResponse": {
      "title": "apiStatsResponse",
      "type": "object",
      "properties": {
        "more": {
          "type": "boolean"
        },
        "stats": {
          "description": "Sorted list of paths with their visitor and pageview count.",
          "type": "array",
          "items": {
            "$ref": "#/definitions/goatcounter.HitStat"
          }
        }
      }
    },
    "handlers.authError": {
      "title": "authError",
      "description": "Authentication error: the API key was not provided or incorrect.",
      "type": "object",
      "properties": {
        "Error": {
          "type": "string"
        }
      }
    },
    "handlers.meResponse": {
      "title": "meResponse",
      "type": "object",
      "properties": {
        "token": {
          "$ref": "#/definitions/goatcounter.APIToken"
        },
        "user": {
          "$ref": "#/definitions/goatcounter.User"
        }
      }
    },
    "tz.Zone": {
      "title": "Zone",
      "description": "Zone represents a time zone.",
      "type": "object",
      "properties": {
        "Abbr": {
          "description": "WITA – the correct abbreviation may change depending on the time of year (i.e. CET and CEST, depending on DST).",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "Comments": {
          "description": "Borneo (east, south); Sulawesi/Celebes, Bali, Nusa Tengarra; Timor (west)",
          "type": "string"
        },
        "CountryCode": {
          "description": "ID",
          "type": "string"
        },
        "CountryName": {
          "description": "Indonesia",
          "type": "string"
        },
        "Zone": {
          "description": "Asia/Makassar",
          "type": "string"
        }
      }
    },
    "v2.Export": {
      "title": "Export",
      "type": "object",
      "properties": {
        "created_at": {
          "type": "string",
          "format": "date-time",
          "readOnly": true
        },
        "error": {
          "description": "Any errors that may have occured.",
          "type": "string",
          "readOnly": true
        },
        "finished_at": {
          "type": "string",
          "format": "date-time",
          "readOnly": true
        },
        "hash": {
          "description": "SHA256 hash.",
          "type": "string",
          "readOnly": true
        },
        "id": {
          "type": "integer",
          "readOnly": true
        },
        "last_hit_id": {
          "description": "Last hit ID that was exported; can be used as start_from_hit_id.",
          "type": "integer",
          "readOnly": true
        },
        "num_rows": {
          "type": "integer",
          "readOnly": true
        },
        "site_id": {
          "type": "integer",
          "readOnly": true
        },
        "size": {
          "description": "File size in MB.",
          "type": "string",
          "readOnly": true
        },
        "start_from_hit_id": {
          "description": "The hit ID this export was started from.",
          "type": "integer"
        }
      }
    }
  }
}
