# Introduction

## Base URL

The Tailglow API is built on REST principles. We enforce HTTPS in every request to improve data security, integrity, and privacy. The API does not support HTTP.

All requests contain the following base URL:

```md
https://api.tailglow.io
```

## Authentication

To authenticate with the Tailglow API, send your API key in the `Authorization` header using the `Bearer` scheme. An API key looks like `tg_api_` followed by 70 characters.

```md
Authorization: Bearer tg_api_1a2B3c4D5e6F7g8H9i0J...
```

`Authorization: Key <your key>` is also accepted, as is sending the key on its own with no scheme. Keys issued before the `tg_api_` prefix begin with `sk_` and keep working indefinitely.

## Filtering Data

When filtering data, you'll create a query string that will be appended to the URL of your `GET` request. The query string will begin with a `?` and contain key-value pairs separated by `&`.

```
Be careful when using "not" filters. They can result in a lot of data being returned.
```

Tailglow uses a single canonical operator vocabulary across every filter surface. URL syntax is flat: `?<field>=<op>:<value>`. The full list, per-type allowlists, and per-surface availability matrix live in the [Filter Operators enum](/api/enums#filter-operators).

### Strings

- **`equals`** - `?type=equals:expense`
- **`not_equals`** - `?type=not_equals:expense`
- **`starts_with`** - `?type=starts_with:expense`
- **`not_starts_with`** - `?type=not_starts_with:expense`
- **`ends_with`** - `?type=ends_with:expense`
- **`not_ends_with`** - `?type=not_ends_with:expense`
- **`contains`** - `?type=contains:expense`
- **`not_contains`** - `?type=not_contains:expense`
- **`in`** - `?type=in:expense,refund`
- **`not_in`** - `?type=not_in:expense,refund`

String operators compare case-insensitively, so `?type=equals:GET` and `?type=equals:get` include the same rows.

### Numbers

- **`equals`** - `?value=equals:100`
- **`not_equals`** - `?value=not_equals:100`
- **`gt`** (Greater Than) - `?value=gt:100`
- **`gte`** (Greater Than or Equal) - `?value=gte:100`
- **`lt`** (Less Than) - `?value=lt:100`
- **`lte`** (Less Than or Equal) - `?value=lte:100`
- **`between`** - `?value=between:100,200`
- **`in`** / **`not_in`** - `?value=in:100,200`

## Pagination

Most `list` endpoints use cursor-based pagination. The first request omits the cursor; subsequent requests echo back the `next_cursor` (or `prev_cursor`) the previous response handed you.

### Request parameters

- **`limit`** `number`
  Maximum number of items to return per page. Defaults to `25`, max `200`.
- **`after`** `string`
  Opaque cursor from a previous response's `pagination.next_cursor`. Returns the rows after the pivot.
- **`before`** `string`
  Opaque cursor from a previous response's `pagination.prev_cursor`. Returns the rows before the pivot. Mutually exclusive with `after`.
- **`sort`** `"asc" \| "desc"`
  Sort direction. Cursors are minted under one direction; reusing them under a different sort returns a `400`.

### Response shape

The `pagination` object on every list response carries:

- **`limit`** `number`: echoed from the request.
- **`count`** `number`: number of rows in this page (≤ `limit`).
- **`offset`** `number`: 0-indexed position of the first row across the full result set. Use `offset + 1` for "Showing 21" labels.
- **`sort`** `"asc" \| "desc"`: echoed sort direction.
- **`next_cursor`** `string \| null`: cursor for the next page. `null` means there are no more rows.
- **`prev_cursor`** `string \| null`: cursor for the previous page. `null` means you're on the first page.
- **`total`** `number \| null`: exact row count for database-backed lists and records. `null` is returned only when the underlying feed cannot provide a total. For example, collection docs always return `null` because the underlying files aren't enumerated.

Cursors are server-minted opaque tokens. Don't construct them yourself; round-trip the values the API gave you.

## Responses

All of our responses will follow a standard structure, regardless if it's successful or not. Here is an example of the response object.

### Successful Response Example

If you are listing data, the `data` key will be an array of objects and a pagination object will be present.
If you are retrieving, creating, or writing a single object, the `data` key will be an object.
If you are deleting an object, the `data` key will be `null`.

```json
{
  "message": "Data retrieved successfully",
  "data": [],
  "status": 200,
  "error": null,
  "pagination": {
    "object": "pagination",
    "limit": 10,
    "count": 10,
    "offset": 0,
    "sort": "desc",
    "next_cursor": "eyJ2IjoxLCJyZXNvdXJjZSI6...",
    "prev_cursor": null,
    "total": 42
  },
  "endpoint": "/v1/projects"
}
```

### Error Response Example

You'll typically see error when request validations fail. The `error` key will contain an object with a `code` key and a `properties` key that will contain an array of objects with an `id` and `message` key. The `id` corresponds to the field that failed validation and the `message` will contain the error message.

```json
{
  "message": "The request is invalid",
  "data": null,
  "status": 400,
  "error": {
    "code": "request_invalid",
    "properties": [
      {
        "id": "name",
        "message": "Name is required"
      }
    ]
  },
  "pagination": null,
  "endpoint": "/v1/projects"
}
```
