> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sub.jerrylu.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Subscription CRUD

> Create, read, update, and delete subscriptions with task semantics for AI agents.

# Subscription CRUD

Subscription API payloads use camelCase. Server-managed fields are returned in responses but rejected in write requests.

## Operation semantics

| Operation                           | Use when                                                            | Risk        | Confirmation           |
| ----------------------------------- | ------------------------------------------------------------------- | ----------- | ---------------------- |
| `GET /api/v1/subscriptions`         | Search, summarize, analyze, or identify subscriptions               | Low         | Not required           |
| `GET /api/v1/subscriptions/{id}`    | Fetch one known record by UUID                                      | Low         | Not required           |
| `POST /api/v1/subscriptions`        | Record a new subscription                                           | Medium      | Required for AI agents |
| `PATCH /api/v1/subscriptions/{id}`  | Change writable fields, including `status` (cancel/pause/resume)    | Medium–High | Required for AI agents |
| `DELETE /api/v1/subscriptions/{id}` | Permanently remove a tracked record (prefer cancel to keep history) | High        | Required for AI agents |

For AI integrations, list subscriptions before updating or deleting unless the user already supplied a subscription UUID. Write operations require an API key with the `write` scope; read-only keys receive `403 insufficient_scope`.

## Pagination

`GET /api/v1/subscriptions` returns results newest first and accepts two optional query parameters:

| Parameter | Default | Rule                                     |
| --------- | ------- | ---------------------------------------- |
| `limit`   | `50`    | Page size, 1 to 100                      |
| `offset`  | `0`     | Number of records to skip from the start |

The response includes a `pagination` object. Fetch the next page with `offset + limit` while `hasMore` is `true`.

```json theme={null}
{
  "data": [ /* ... */ ],
  "pagination": { "limit": 50, "offset": 0, "hasMore": false },
  "requestId": "request-..."
}
```

```bash theme={null}
curl -H "Authorization: Bearer $SUBSCRIPTION_MANAGER_API_KEY" \
  "https://your-site.example/api/v1/subscriptions?limit=50&offset=50"
```

Out-of-range values return `400 invalid_pagination`.

## Filtering and sorting

`GET /api/v1/subscriptions` accepts optional filters and a sort order alongside pagination:

| Parameter        | Rule                                                                                                                                                           |
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `status`         | One of `active`, `paused`, `cancelled`                                                                                                                         |
| `category`       | Exact category match, e.g. `Streaming`                                                                                                                         |
| `period`         | One of `monthly`, `yearly`, `custom`                                                                                                                           |
| `q`              | Case-insensitive search over the subscription name                                                                                                             |
| `expiringBefore` | `YYYY-MM-DD`; returns records whose `nextPaymentDate` is on or before this date                                                                                |
| `sort`           | One of `createdAt`, `-createdAt`, `nextPaymentDate`, `-nextPaymentDate`, `amount`, `-amount`, `name`, `-name` (default `-createdAt`; `-` prefix is descending) |

Combine `status=active` with `expiringBefore` to answer "what renews soon". Invalid values return `400 invalid_query` with a `field` and `suggestedFix`.

```bash theme={null}
curl -H "Authorization: Bearer $SUBSCRIPTION_MANAGER_API_KEY" \
  "https://your-site.example/api/v1/subscriptions?status=active&expiringBefore=2026-07-01&sort=nextPaymentDate"
```

The response echoes the applied query so an agent can confirm what produced the result:

```json theme={null}
{
  "data": [ /* ... */ ],
  "pagination": { "limit": 50, "offset": 0, "hasMore": false },
  "query": { "sort": "nextPaymentDate", "filters": { "status": "active", "expiringBefore": "2026-07-01" } },
  "requestId": "request-..."
}
```

## Subscription object

```json theme={null}
{
  "id": "33333333-3333-4333-8333-333333333333",
  "name": "Netflix",
  "category": "Streaming",
  "amount": 15.99,
  "currency": "USD",
  "period": "monthly",
  "lastPaymentDate": "2026-06-01",
  "nextPaymentDate": "2026-07-01",
  "notificationEnabled": true,
  "status": "active",
  "createdAt": "2026-06-16T00:00:00.000Z",
  "updatedAt": "2026-06-16T00:00:00.000Z"
}
```

## Writable fields

```json theme={null}
{
  "name": "Netflix",
  "category": "Streaming",
  "amount": 15.99,
  "currency": "USD",
  "period": "monthly",
  "lastPaymentDate": "2026-06-01",
  "customDate": null,
  "notificationEnabled": true,
  "status": "active"
}
```

The server manages:

* `id`
* `nextPaymentDate`
* `createdAt`
* `updatedAt`

Do not send server-managed fields in `POST` or `PATCH` requests.

## Field rules

| Field                 | Rule                                                                                  |
| --------------------- | ------------------------------------------------------------------------------------- |
| `name`                | Required, 1-120 characters                                                            |
| `category`            | Required, 1-80 characters                                                             |
| `amount`              | Required number, 0 to 999999.99, max 2 decimals                                       |
| `currency`            | Required. One of `CNY`, `USD`, `EUR`, `JPY`, `GBP`, `AUD`, `CAD`, `CHF`, `HKD`, `SGD` |
| `period`              | Required. One of `monthly`, `yearly`, `custom`                                        |
| `lastPaymentDate`     | Required date in `YYYY-MM-DD` format                                                  |
| `customDate`          | Required only when `period` is `custom`; positive whole-number string of days         |
| `notificationEnabled` | Optional boolean, defaults to `true`                                                  |
| `status`              | Optional. One of `active`, `paused`, `cancelled`; defaults to `active`                |

`nextPaymentDate` is calculated by the server from `lastPaymentDate`, `period`, and `customDate`. On `PATCH`, it is recalculated only when one of those three fields changes; updating any other field leaves the stored `nextPaymentDate` untouched. Only the fields you send in a `PATCH` are validated, so updating one field never fails because of unrelated stored data.

## Lifecycle: cancel, pause, resume vs delete

A subscription has a `status`: `active` (billing), `paused` (temporarily stopped), or `cancelled` (no longer billing, kept for history). Changing status is a soft operation — the record and its audit history survive and can be reactivated. `DELETE` is permanent and removes the record and its audit history.

Prefer a status change when the user says they stopped using a service but might come back:

```bash theme={null}
# Cancel (keep history)
curl -X PATCH \
  -H "Authorization: Bearer $SUBSCRIPTION_MANAGER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status":"cancelled"}' \
  https://your-site.example/api/v1/subscriptions/33333333-3333-4333-8333-333333333333

# Pause, then later resume
curl -X PATCH ... -d '{"status":"paused"}' .../{id}
curl -X PATCH ... -d '{"status":"active"}'  .../{id}
```

Only `active` subscriptions count toward active spend in the [analytics](/en/api/analytics) endpoints. Use `DELETE` only when the user wants the record gone permanently.

## Natural language examples

### Add a subscription

User intent:

```text theme={null}
Add ChatGPT Plus, 20 USD per month, last paid on 2026-06-17.
```

API call:

```bash theme={null}
curl -X POST \
  -H "Authorization: Bearer $SUBSCRIPTION_MANAGER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"ChatGPT Plus","category":"AI","amount":20,"currency":"USD","period":"monthly","lastPaymentDate":"2026-06-17","notificationEnabled":true}' \
  https://your-site.example/api/v1/subscriptions
```

### Change billing period

User intent:

```text theme={null}
Change Netflix to yearly billing.
```

Agent flow:

1. Call `GET /api/v1/subscriptions` to identify the Netflix record.
2. Ask the user to confirm the exact record and change.
3. Call `PATCH /api/v1/subscriptions/{id}`.

```bash theme={null}
curl -X PATCH \
  -H "Authorization: Bearer $SUBSCRIPTION_MANAGER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"period":"yearly"}' \
  https://your-site.example/api/v1/subscriptions/33333333-3333-4333-8333-333333333333
```

### Remove a duplicate

User intent:

```text theme={null}
Delete the duplicate Netflix subscription.
```

Agent flow:

1. Call `GET /api/v1/subscriptions` and find duplicate candidates.
2. Ask the user to confirm the exact subscription name and id.
3. Call `DELETE /api/v1/subscriptions/{id}`.

```bash theme={null}
curl -X DELETE \
  -H "Authorization: Bearer $SUBSCRIPTION_MANAGER_API_KEY" \
  https://your-site.example/api/v1/subscriptions/33333333-3333-4333-8333-333333333333
```

## Custom billing period

For non-monthly/non-yearly intervals, set `period` to `custom` and send `customDate` as a positive whole-number string of days.

```json theme={null}
{
  "name": "Domain renewal",
  "category": "Infrastructure",
  "amount": 12,
  "currency": "USD",
  "period": "custom",
  "lastPaymentDate": "2026-06-01",
  "customDate": "45"
}
```

When patching from `custom` back to `monthly` or `yearly`, omit `customDate`. The server clears stale custom billing data.

## AI tool schema

Use [ai-tools.json](/api/ai-tools.json) for tool/function definitions, risk levels, and confirmation prompts.
