# Expense tracker API

> The Traackly REST API: base URL, API keys and authentication, endpoints, the expense object, pagination, rate limits and errors. Included on Scale.

Source: https://traackly.com/docs/api

The Traackly API is a REST API that returns JSON. Use it to read, add, update and delete your expenses, and to read your categories and account. It is included on the Scale plan. The current version is `v1`.

**Base URL:** `https://traackly.com/api/v1`

## Authentication

1. Sign in to Traackly on the Scale plan.
2. Open **Settings**, then **API Keys**, and click **Create Key**.
3. Choose **Read Only** or **Read & Write**, and copy the key. It is shown only once.

Keys start with `et_sk_`. Send the key as a Bearer token on every request:

```bash
curl https://traackly.com/api/v1/me \
  -H "Authorization: Bearer et_sk_your_api_key"
```

A key only reaches the workspace it was created in. You can have up to 3 active keys and revoke any of them at any time. Keep keys on your server, never in browser code or a public repository.

## Endpoints

| Method | Path | Description |
| --- | --- | --- |
| GET | `/me` | Your account: name, email, plan |
| GET | `/expenses` | List expenses, newest first, with pagination |
| POST | `/expenses` | Create an expense. Returns `201` |
| GET | `/expenses/{id}` | Get one expense |
| PATCH | `/expenses/{id}` | Update an expense. Send only the fields to change. An unchanged stored amount may be sent back as read |
| DELETE | `/expenses/{id}` | Delete an expense. Returns `204` |
| GET | `/categories` | List your categories, built-in and custom |

All paths are relative to the base URL. Read Only keys can call the GET endpoints only.

## The expense object

| Field | Type | Description |
| --- | --- | --- |
| `id` | uuid | Unique ID |
| `amount` | number | Positive. New amounts use at most the currency's decimals (0, 2 or 3, per ISO 4217, e.g. 3 for KWD). A scanned receipt keeps the decimals printed on it, up to 3 |
| `currency` | string | 3-letter ISO 4217 code, such as `USD` |
| `merchant` | string | Where the money was spent |
| `date` | string | `YYYY-MM-DD` |
| `description` | string | Optional note. Empty when not set (may be null on older expenses) |
| `category_id` | uuid | A category ID from `GET /categories` |
| `source` | string | `manual`, `scan`, `quick_add`, `voice`, `email`, `recurring` or `api` |
| `created_at` | string | ISO 8601 timestamp |
| `updated_at` | string | ISO 8601 timestamp |

## Create an expense

Required fields: `merchant`, `amount` (a JSON number), `currency`, `date` and `category_id`. Optional: `description`. Send the body as JSON with `Content-Type: application/json`.

```bash
curl -X POST https://traackly.com/api/v1/expenses \
  -H "Authorization: Bearer et_sk_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "merchant": "Cloud Hosting",
    "amount": 18.40,
    "currency": "USD",
    "date": "2026-10-08",
    "category_id": "a category id from GET /categories"
  }'
```

The response is `201 Created` with the new expense in `data`, and its `source` is `api`.

## Pagination

`GET /expenses` returns up to 50 expenses per page. Set `limit` (1 to 100) to change that. The response has a `meta` object:

```json
{
  "data": [],
  "meta": { "has_more": true, "next_cursor": "eyJkYXRl...", "api_version": "v1" }
}
```

While `has_more` is `true`, request the next page with `?cursor=` set to `next_cursor`.

## Rate limits

60 requests a minute per key, and 180 a minute across all your keys. Over the limit, the API answers `429` with a `Retry-After` header in seconds.

## Errors

Every error has the same shape:

```json
{
  "error": {
    "code": "validation_error",
    "status": 422,
    "message": "Invalid request parameters",
    "request_id": "550e8400-e29b-41d4-a716-446655440000",
    "fields": [{ "field": "amount", "message": "Must be positive" }]
  }
}
```

| Status | Meaning |
| --- | --- |
| `400` | Malformed request, such as invalid JSON or a missing `Content-Type` |
| `401` | Missing or invalid API key (also after you leave Scale, because your keys are revoked) |
| `402` | Your subscription payment is outstanding, so API access is paused |
| `403` | Your plan does not include the API, or a Read Only key tried to write |
| `404` | The expense or path does not exist |
| `422` | Invalid fields, listed in `fields` |
| `429` | Rate limit reached. Wait for `Retry-After` seconds |

Quote the `request_id` when you contact support.

## Full reference

Scale accounts get the complete API reference in the app, under **Settings**, **API Keys**, **View API Documentation**: every filter and sort option, idempotency keys for safe retries, and all error codes.
