Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/soker90/finper/llms.txt

Use this file to discover all available pages before exploring further.

The Subscriptions API tracks recurring charges such as streaming services, SaaS tools, and utilities. A subscription’s nextPaymentDate is always calculated from the most recently linked transaction — it is never manually set. When a new transaction is created elsewhere in the API, a background process automatically checks whether it matches a subscription’s expected payment window and creates a candidate for the user to confirm or discard.

Cycle values

The cycle field is a number — an integer between 1 and 60 representing the number of months between payments. Pass the integer directly; there are no named string values.
Example valueMeaning
1Every 1 month (monthly)
2Every 2 months
3Every 3 months (quarterly)
6Every 6 months
12Every 12 months (annually)
Any integer from 1 to 60 is accepted.

GET /api/subscriptions

List all subscriptions for the authenticated user, ordered by nextPaymentDate ascending. Subscriptions with no linked transactions (where nextPaymentDate is null) appear first.
curl http://localhost:3008/api/subscriptions \
  -H 'token: <your-jwt>'
Response 200
[
  {
    "_id": "64c1a...",
    "name": "Netflix",
    "amount": 15.99,
    "cycle": 1,
    "nextPaymentDate": null,
    "categoryId": { "_id": "...", "name": "Entertainment" },
    "accountId": { "_id": "...", "name": "Current Account", "bank": "MyBank" },
    "logoUrl": "https://example.com/netflix.png",
    "user": "alice"
  }
]

POST /api/subscriptions

Create a new subscription. nextPaymentDate always starts as null and is populated only after transactions are linked.
name
string
required
Display name for the subscription (e.g. "Netflix", "Spotify").
amount
number
required
Expected charge amount per cycle. Must be positive.
cycle
number
required
Number of months between payments. Any integer from 1 to 60. For example: 1 = monthly, 3 = quarterly, 12 = annually.
categoryId
string
required
ID of the expense category. Must exist and belong to the authenticated user.
accountId
string
required
ID of the account the charge comes from. Must exist and belong to the authenticated user.
logoUrl
string
URL of the service’s logo. Must be a valid URI or an empty string.
currency
string
ISO 4217 currency code (e.g. "EUR", "USD"). Optional.
curl -X POST http://localhost:3008/api/subscriptions \
  -H 'token: <your-jwt>' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "Netflix",
    "amount": 15.99,
    "cycle": 1,
    "categoryId": "64a0f...",
    "accountId": "64a0e...",
    "logoUrl": "https://example.com/netflix.png"
  }'
Response 200 — the created subscription object.

PUT /api/subscriptions/:id

Edit a subscription. All fields are optional; only provided fields are updated.
id
string
required
Subscription ID.
name
string
New display name.
amount
number
New charge amount. Must be positive.
cycle
number
New billing cycle in months (1–60).
categoryId
string
New category ID (existence is validated).
accountId
string
New account ID (existence is validated).
logoUrl
string
New logo URL or empty string to clear.
curl -X PUT http://localhost:3008/api/subscriptions/64c1a... \
  -H 'token: <your-jwt>' \
  -H 'Content-Type: application/json' \
  -d '{"amount": 17.99}'
Response 200 — the updated subscription object.

DELETE /api/subscriptions/:id

Delete a subscription. Does not remove linked transactions — they retain their subscriptionId field but the referenced subscription no longer exists.
id
string
required
Subscription ID.
curl -X DELETE http://localhost:3008/api/subscriptions/64c1a... \
  -H 'token: <your-jwt>'
Response 204 — no content.

GET /api/subscriptions/:id/transactions

List all transactions already linked to a subscription, sorted by date descending.
id
string
required
Subscription ID.
curl http://localhost:3008/api/subscriptions/64c1a.../transactions \
  -H 'token: <your-jwt>'
Response 200 — array of transaction objects with populated category and account fields.

GET /api/subscriptions/:id/matching-transactions

Return up to 50 unlinked transactions that share the same category and account as the subscription. Sorted by date descending. Useful for manually identifying historical payments to backfill.
id
string
required
Subscription ID.
curl http://localhost:3008/api/subscriptions/64c1a.../matching-transactions \
  -H 'token: <your-jwt>'
Response 200 — array of up to 50 transaction objects (not yet linked to any subscription).

POST /api/subscriptions/:id/link-transactions

Link one or more transactions to a subscription in a single batch operation. After linking, nextPaymentDate is recalculated from the most recent linked transaction.
id
string
required
Subscription ID.
transactionIds
string[]
required
Non-empty array of transaction IDs to link. Returns HTTP 422 if the array is empty or missing.
curl -X POST http://localhost:3008/api/subscriptions/64c1a.../link-transactions \
  -H 'token: <your-jwt>' \
  -H 'Content-Type: application/json' \
  -d '{"transactionIds": ["64d1b...", "64d2c..."]}'
Response 204 — no content.
Remove the link between a single transaction and a subscription. The transaction itself is not deleted. nextPaymentDate is recalculated after unlinking.
id
string
required
Subscription ID.
transactionId
string
required
ID of the transaction to unlink.
curl -X DELETE \
  http://localhost:3008/api/subscriptions/64c1a.../unlink-transactions/64d1b... \
  -H 'token: <your-jwt>'
Response 204 — no content.

GET /api/subscriptions/candidates

List all subscription candidates for the authenticated user. Candidates are temporary records created automatically when a new transaction’s date falls within ±7 days of a subscription’s nextPaymentDate for the same account and category. Sorted by createdAt descending (newest first).
This route must be declared before the /:id route in the Express router so that the literal path segment candidates is not captured as a dynamic :id parameter.
curl http://localhost:3008/api/subscriptions/candidates \
  -H 'token: <your-jwt>'
Response 200
[
  {
    "_id": "64e1d...",
    "transactionId": {
      "_id": "64d1b...",
      "date": 1704067200000,
      "amount": 15.99,
      "note": "Netflix",
      "category": { "name": "Entertainment" },
      "account": { "name": "Current Account", "bank": "MyBank" }
    },
    "subscriptionIds": [
      {
        "_id": "64c1a...",
        "name": "Netflix",
        "logoUrl": "https://example.com/netflix.png",
        "amount": 15.99,
        "cycle": 1,
        "nextPaymentDate": 1706745600000
      }
    ],
    "createdAt": "2024-01-01T10:00:00.000Z"
  }
]

POST /api/subscriptions/candidates/:id/assign

Confirm a candidate by linking its transaction to the chosen subscription. This:
  1. Sets transaction.subscriptionId = subscriptionId.
  2. Recalculates nextPaymentDate for the subscription.
  3. Deletes the candidate record.
id
string
required
Candidate ID.
subscriptionId
string
required
The subscription to link the transaction to. Must be one of the subscriptionIds on the candidate.
curl -X POST http://localhost:3008/api/subscriptions/candidates/64e1d.../assign \
  -H 'token: <your-jwt>' \
  -H 'Content-Type: application/json' \
  -d '{"subscriptionId": "64c1a..."}'
Response 204 — no content.

POST /api/subscriptions/candidates/:id/dismiss

Discard a candidate without linking anything. The transaction and all subscriptions remain untouched. The candidate record is deleted.
id
string
required
Candidate ID.
curl -X POST http://localhost:3008/api/subscriptions/candidates/64e1d.../dismiss \
  -H 'token: <your-jwt>'
Response 204 — no content.

Route ordering

The /candidates and /candidates/:id/* routes must be registered before the /:id route in subscription.routes.ts. Express evaluates routes in declaration order; if /:id is first, the string "candidates" is captured as the subscription ID and the request 404s.

Build docs developers (and LLMs) love