View as Markdown

Report subscriptions

A report subscription renders a pack of your report definitions on a schedule and mails it to your users. Reads need reports:read and the reports module on read in one of your groups; writes need reports:write and an operator seat (owner, admin, advisor) or a group with the reports module on read_write. Every write takes ?dry_run=true and an Idempotency-Key header and is logged in the change history.

The pack is rendered with the access of the user who made the subscription, so only they, a firm admin or Aleta may change it; another operator gets 403. A subscription Aleta set up for you (owner: aleta) is closed to your people, admins included: changing or archiving it is a 403 with the text Aleta set this up for you: only Aleta can change it.

Data model

idstring

Stable identifier for the subscription. A UUID.

namestring

The subscription's name.

statusstring

draft, active or paused. Only an active subscription is sent; it needs a name, at least one entry, a recipient for every entry, a client (the subscription's, every entry's definition's, or every entry scoped to a client), no entry for an archived definition and, unless manual, a schedule that resolves. Otherwise setting active is a 400 that lists what is missing.

ownerstring

aleta (Aleta set it up for you) or tenant (your own).

originstring

v1. Subscriptions are made on the platform; the legacy platform's are not moved.

client_idstring or null

The client the subscription files its reports under.

created_by_user_idstring

The user who made the subscription; the pack is rendered with their access.

cadencestring

manual, daily, weekly, monthly or quarterly.

time_of_day, timezonestring or null, string

When it is sent (HH:MM, 24h; null for manual) in an IANA time zone, e.g. Europe/Copenhagen.

day_of_week, day_of_monthstring or null, integer or null

The weekday (mon … sun) of a weekly subscription and the day (1–31) of a monthly or quarterly one.

business_days_onlyboolean

Skip weekends: a daily subscription is not sent on Saturday or Sunday; any other send that falls on a weekend moves to the Monday.

entriesarray of object

The pack, one line per report: report_definition_id, format (pdf, xlsx or csv), currency (ISO code or null), language (da, en or null), scope ({level: lens, lens_id}, {level: client, client_id} or null for the definition's own; {level: invalid} for a line saved outside v1 with a scope the sender does not know, which fails its run until the line is replaced) and recipients (empty: the subscription's own).

recipientsarray of object

Who receives the pack: your users, each {user_id, email, name}. user_id is null on a stored address none of your users has.

subjectstring or null

The mail's subject. The mail body (body) can be written but is not returned.

next_run_at, last_run_attimestamp or null

The next scheduled send and the last run. RFC 3339, UTC, with microseconds.

created_at, updated_attimestamp

RFC 3339, UTC, with microseconds.

archived_at, archived_by, archive_reasontimestamp, string, string or null

Set once DELETE archived the subscription. Null while it is live.


GET/v1/report-subscriptions

List report subscriptions

Returns the subscriptions you can see by id, as a cursor list. Archived subscriptions are left out unless asked for. An unknown query parameter is a 400.

Query parameters

client_idstring

Only this client's subscriptions.

statusstring

draft, active or paused.

report_definition_idstring

Only subscriptions with an entry for this report definition.

include_archivedboolean

Also archived subscriptions. Default false.

cursor, limitstring, integer

The page: limit defaults to 100, at most 1000.

Request
GET/v1/report-subscriptions
curl https://api.aleta.ai/v1/report-subscriptions \
  -H "Authorization: Bearer {access_token}"
Response
{
  "data": [
    {
      "id": "019f95f0-2d40-7b58-8f6e-9a3c1d7e5b02",
      "name": "Monthly pack",
      "status": "active",
      "owner": "tenant",
      "origin": "v1",
      "client_id": "019dfeb0-0511-77a7-a8be-a911ab988a0a",
      "created_by_user_id": "019dfe9f-77aa-7b10-8c2d-3e4f5a6b7c8d",
      "cadence": "monthly",
      "time_of_day": "07:00",
      "timezone": "Europe/Copenhagen",
      "day_of_week": null,
      "day_of_month": 1,
      "business_days_only": true,
      "entries": [
        {
          "report_definition_id": "019f1a2b-3c4d-7e5f-8a9b-0c1d2e3f4a5b",
          "format": "pdf",
          "currency": "DKK",
          "language": "da",
          "scope": {
            "level": "lens",
            "lens_id": "019e0f1a-2b3c-7d4e-9f5a-6b7c8d9e0f1a"
          },
          "recipients": []
        }
      ],
      "recipients": [
        {
          "user_id": "019dfea0-1122-7344-8556-6778899aabbc",
          "email": "cfo@client.example",
          "name": "Client CFO"
        }
      ],
      "subject": "Your monthly report",
      "next_run_at": "2026-11-02T06:00:00.000000Z",
      "last_run_at": null,
      "created_at": "2026-10-17T06:12:00.481213Z",
      "updated_at": "2026-10-17T06:12:00.481213Z",
      "archived_at": null,
      "archived_by": null,
      "archive_reason": null
    }
  ],
  "next_cursor": null,
  "limit": 100
}

GET/v1/report-subscriptions/{id}

Retrieve a report subscription

Returns one subscription, an archived one too. An unknown id, one you cannot see, or one that is not a UUID is a 404.

Request
GET/v1/report-subscriptions/{id}
curl https://api.aleta.ai/v1/report-subscriptions/019f95f0-2d40-7b58-8f6e-9a3c1d7e5b02 \
  -H "Authorization: Bearer {access_token}"

POST/v1/report-subscriptions

Create a report subscription

Creates a subscription and returns 201 with it, origin: v1 and owner: tenant (aleta when Aleta made it). An entry names one of your own report definitions: copy one of Aleta's first with POST /v1/report-definitions and copy_from. An archived definition, csv for a definition without exactly one data table, an unknown client or lens, or an incomplete schedule is a 400.

Body

name, cadence, timezonestringRequired

As in the data model.

statusstring

draft (default), active or paused.

client_id, time_of_day, day_of_week, day_of_month, business_days_onlyas in the data model

manual takes no time_of_day; daily needs it; weekly also day_of_week; monthly and quarterly also day_of_month.

entriesarray of object

Each {report_definition_id, format?, currency?, language?, lens_id? | client_id?, recipients?}. format defaults to pdf; an entry with both lens_id and client_id is a 400.

recipientsarray of object

Each {email, name?, user_id?}, at most 200 per list. A recipient must be one of your users: a user with access to your firm, not deleted. An address none of your users has, or a user_id whose address is not the one given, is a 400. Each recipient must also see what they are sent, under their own access: the lens or client of every line mailed to them (a line without a scope is for its definition's lens, or else the subscription's or the definition's client); otherwise the write is a 400 naming the recipient. The user who made the subscription must see every line too, since the pack is rendered with their access; a change by an admin to another user's subscription is checked against that user.

subject, bodystring

The mail's subject and body.

Request
POST/v1/report-subscriptions
curl -X POST https://api.aleta.ai/v1/report-subscriptions \
  -H "Authorization: Bearer {access_token}" \
  -H "Idempotency-Key: {uuid}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Monthly pack",
    "status": "active",
    "client_id": "019dfeb0-0511-77a7-a8be-a911ab988a0a",
    "cadence": "monthly",
    "time_of_day": "07:00",
    "timezone": "Europe/Copenhagen",
    "day_of_month": 1,
    "entries": [{"report_definition_id": "019f1a2b-3c4d-7e5f-8a9b-0c1d2e3f4a5b", "format": "pdf"}],
    "recipients": [{"email": "cfo@client.example"}]
  }'

PATCH/v1/report-subscriptions/{id}

Update a report subscription

Changes the fields you send; a field left out keeps its value, and null clears client_id, time_of_day, day_of_week or day_of_month. Returns 200 with the subscription. status: active brings an archived subscription back; any other change to an archived one is a 409 in_use whose next_action is this route with status.

Request
PATCH/v1/report-subscriptions/{id}
curl -X PATCH https://api.aleta.ai/v1/report-subscriptions/019f95f0-2d40-7b58-8f6e-9a3c1d7e5b02 \
  -H "Authorization: Bearer {access_token}" \
  -H "Content-Type: application/json" \
  -d '{"status": "paused"}'

DELETE/v1/report-subscriptions/{id}

Archive a report subscription

Archives the subscription: it is paused, never sent again and left out of the list; its deliveries are kept. Returns 200 with the archived subscription; ?reason= is kept as archive_reason. Any query parameter other than reason and dry_run is a 400. Archiving one already archived changes nothing. PATCH with status: active brings it back.

Request
DELETE/v1/report-subscriptions/{id}
curl -X DELETE "https://api.aleta.ai/v1/report-subscriptions/019f95f0-2d40-7b58-8f6e-9a3c1d7e5b02?reason=replaced" \
  -H "Authorization: Bearer {access_token}"