Lydira API
TR

API reference

Reports

4 endpoints

Read-only export of the same reports the browser runs. Two families share one result shape: preset reports off the built-in catalogue (GET /api/v1/reports) and the agency's own saved reports from the report builder (GET /api/v1/saved_reports).

Both are plan-gated per report. GET /api/v1/reports lists only what the account's plan actually includes, so an empty catalogue is an answer about the plan rather than an error. Running a report the plan excludes answers 403 plan_gated.

Filters travel as filters[key]=value. The catalogue publishes each preset's accepted filters and columns, so a client can build the query without hardcoding them.

Preset catalogue

GET/api/v1/reports

Needs a key with the full scope.

Every built-in report the account's plan includes, with the filters it accepts and the columns it returns. Read this first and build your query from it rather than hardcoding keys: the catalogue is the contract, and it shrinks or grows with the plan.

A fixed set of definitions rather than records, so there is no page.

Response · 200

  • dataobject[]required
    Show fields
    • keystring

      What you pass as {id} to run it.

    • plan_featurestring

      The plan flag this report needs.

    • filtersobject[]

      The filters this preset accepts, as definitions rather than values.

    • columnsobject[]

      The columns it returns, in order, with their labels and types.

Responses

  • 401Missing or invalid Bearer token
  • 403Policy denied or account has no owner
  • 429Rate limit exceeded
curl -X GET 'https://YOUR-CELL.lydira.com/api/v1/reports' \
  -H 'Authorization: Bearer sek_YOUR_API_KEY'
Response
200
{
  "data": [
    {
      "key": "ar_aging",
      "plan_feature": "reports_advanced",
      "filters": [
        {
          "key": "currency",
          "type": "string"
        },
        {
          "key": "overdue_min",
          "type": "integer"
        },
        {
          "key": "overdue_max",
          "type": "integer"
        }
      ],
      "columns": [
        {
          "key": "number",
          "label_key": "reports.cols.invoice_number",
          "type": "string"
        },
        {
          "key": "bill_to_name",
          "label_key": "reports.cols.customer",
          "type": "string"
        },
        {
          "key": "issued_on",
          "label_key": "reports.cols.issued_on",
          "type": "date"
        },
        {
          "key": "due_on",
          "label_key": "reports.cols.due_on",
          "type": "date"
        },
        {
          "key": "days_overdue",
          "label_key": "reports.cols.days_overdue",
          "type": "number"
        },
        {
          "key": "bucket",
          "label_key": "reports.cols.bucket",
          "type": "badge"
        },
        {
          "key": "currency",
          "label_key": "reports.cols.currency",
          "type": "string"
        },
        {
          "key": "total",
          "label_key": "reports.cols.total",
          "type": "money"
        },
        {
          "key": "paid",
          "label_key": "reports.cols.collected",
          "type": "money"
        },
        {
          "key": "balance",
          "label_key": "reports.cols.balance_due",
          "type": "money"
        }
      ]
    }
  ]
}

Run a preset report

GET/api/v1/reports/{id}

Needs a key with the full scope.

id is a preset key from the catalogue. Filters go in as filters[key]=value, for example ?filters[from]=2026-01-01&filters[to]=2026-03-31.

The result is unwrapped: rows, columns and the totals sit at the top level, not under data. Money in rows follows the key's role, so a masked column comes back empty rather than the row being withheld.

A report the plan does not include answers 403 plan_gated. A report built from cost AND sell (the P&L presets) goes whole or not at all: a key whose role cannot see both answers 403 forbidden. An unrecognised key answers 404 not_found.

Parameters

Path

  • idstringrequired

    A preset key.

Query

  • filtersobject

    Per-preset; the catalogue publishes which keys each one takes.

Response · 200

  • keystring
  • columnsobject[]

    Each column's key, type and label: a label_key (a translation key) for a built-in column, a plain label for one of the account's own fields (keyed cf:<key>, or cf:t.<key> for a traveller's). A datetime value is an ISO 8601 instant, unless its column says wall_clock: true: then it is local time where it happens (a car's pickup, a flight's arrival), and its trailing Z is not an offset.

  • rowsobject[]
  • summaryobject

    Headline figures for the whole result.

  • filtersobject

    The filters actually applied, after defaults and clamping.

  • generated_atdate-timenullable
  • paginationobjectnullable

    Present only on presets that page. null means the result is whole.

  • group_bystringnullable

    The grouping column, when the preset groups.

  • groupsobject[]nullable

    Ordered group headers with their per-group totals.

  • totalsobjectnullable

    Grand totals, including a per-currency breakdown where the rows are not in one currency.

Responses

  • 401Missing or invalid Bearer token
  • 403The plan does not include this report, or the key's role cannot see its cost and sell
  • 404Resource not found
  • 429Rate limit exceeded
curl -X GET 'https://YOUR-CELL.lydira.com/api/v1/reports/ar_aging' \
  -H 'Authorization: Bearer sek_YOUR_API_KEY'
Response
200
{
  "key": "ar_aging",
  "columns": [
    {
      "key": "number",
      "label_key": "reports.cols.invoice_number",
      "type": "string"
    },
    {
      "key": "bill_to_name",
      "label_key": "reports.cols.customer",
      "type": "string"
    },
    {
      "key": "issued_on",
      "label_key": "reports.cols.issued_on",
      "type": "date"
    },
    {
      "key": "due_on",
      "label_key": "reports.cols.due_on",
      "type": "date"
    },
    {
      "key": "days_overdue",
      "label_key": "reports.cols.days_overdue",
      "type": "number"
    },
    {
      "key": "bucket",
      "label_key": "reports.cols.bucket",
      "type": "badge"
    },
    {
      "key": "currency",
      "label_key": "reports.cols.currency",
      "type": "string"
    },
    {
      "key": "total",
      "label_key": "reports.cols.total",
      "type": "money"
    },
    {
      "key": "paid",
      "label_key": "reports.cols.collected",
      "type": "money"
    },
    {
      "key": "balance",
      "label_key": "reports.cols.balance_due",
      "type": "money"
    }
  ],
  "rows": [
    {
      "id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18",
      "number": 98,
      "bill_to_name": "Yılmaz Turizm A.Ş.",
      "issued_on": "2026-02-10",
      "due_on": "2026-02-24",
      "days_overdue": 18,
      "bucket": "d1_30",
      "currency": "EUR",
      "total": "3480.0",
      "paid": "1000.0",
      "balance": "2480.0",
      "entity_url": "/accounting/invoices/9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18"
    },
    {
      "id": "2c0f6b9a-4d18-4a7d-8e51-9d2f7c3a1b4e",
      "number": 117,
      "bill_to_name": "Ayşe Yılmaz",
      "issued_on": "2026-03-14",
      "due_on": "2026-03-28",
      "days_overdue": 0,
      "bucket": "current",
      "currency": "EUR",
      "total": "1250.0",
      "paid": "0.0",
      "balance": "1250.0",
      "entity_url": "/accounting/invoices/2c0f6b9a-4d18-4a7d-8e51-9d2f7c3a1b4e"
    }
  ],
  "summary": {
    "total_rows": 2,
    "total_balance": "3730.0",
    "total_invoiced": "4730.0",
    "total_collected": "1000.0",
    "by_bucket": {
      "current": "1250.0",
      "d1_30": "2480.0",
      "d31_60": 0,
      "d61_90": 0,
      "d90_plus": 0
    }
  },
  "filters": {
    "currency": "EUR"
  },
  "generated_at": "2026-03-14T09:30:00Z",
  "pagination": null,
  "group_by": null,
  "groups": null,
  "totals": null
}

List saved reports

GET/api/v1/saved_reports

Needs a key with the full scope.

The agency's own reports from the report builder, by name. Requires the reports_builder plan feature; without it the whole endpoint answers 403 plan_gated.

Parameters

Query

  • limitintegerdefault 25

    Rows per page.

  • cursorstring

    Opaque; take it from the previous response's page.next_cursor.

Response · 200

  • dataobject[]required
    Show fields
    • iduuid
    • namestring
    • entitystringnullable

      What the report is built over.

    • schedule_frequencystringnullable

      null when it is run on demand only.

    • recipient_countinteger
    • last_run_atdate-timenullable
  • pageobjectrequired

    The cursor for the list beside it. Re-send the same request with ?cursor=<next_cursor> while has_more is true. next_cursor is null on the last page.

    Show fields
    • next_cursorstringrequirednullable
    • has_morebooleanrequired
    • limitintegerrequired

Responses

  • 401Missing or invalid Bearer token
  • 403The plan does not include the report builder
  • 429Rate limit exceeded
curl -X GET 'https://YOUR-CELL.lydira.com/api/v1/saved_reports' \
  -H 'Authorization: Bearer sek_YOUR_API_KEY'
Response
200
{
  "data": [
    {
      "id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18",
      "name": "Ayşe Yılmaz",
      "entity": "hotel_stays",
      "schedule_frequency": "weekly",
      "recipient_count": 3,
      "last_run_at": "2026-03-14T09:30:00Z"
    }
  ],
  "page": {
    "next_cursor": "MjAyNi0wMy0xNFQwOTozMDowMC4wMDAwMDBafDk0MQ",
    "has_more": true,
    "limit": 25
  }
}

Run a saved report

GET/api/v1/saved_reports/{id}

Needs a key with the full scope.

Runs the stored definition and returns the same result shape a preset does, plus saved_report_id and name. The filters are the ones saved with the report; they are not overridable here.

Parameters

Path

  • iduuidrequired

Response · 200

  • keystring
  • columnsobject[]

    Each column's key, type and label: a label_key (a translation key) for a built-in column, a plain label for one of the account's own fields (keyed cf:<key>, or cf:t.<key> for a traveller's). A datetime value is an ISO 8601 instant, unless its column says wall_clock: true: then it is local time where it happens (a car's pickup, a flight's arrival), and its trailing Z is not an offset.

  • rowsobject[]
  • summaryobject

    Headline figures for the whole result.

  • filtersobject

    The filters actually applied, after defaults and clamping.

  • generated_atdate-timenullable
  • paginationobjectnullable

    Present only on presets that page. null means the result is whole.

  • group_bystringnullable

    The grouping column, when the preset groups.

  • groupsobject[]nullable

    Ordered group headers with their per-group totals.

  • totalsobjectnullable

    Grand totals, including a per-currency breakdown where the rows are not in one currency.

  • saved_report_iduuid
  • namestring

Responses

  • 401Missing or invalid Bearer token
  • 403The plan does not include the report builder
  • 404Resource not found
  • 429Rate limit exceeded
curl -X GET 'https://YOUR-CELL.lydira.com/api/v1/saved_reports/9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18' \
  -H 'Authorization: Bearer sek_YOUR_API_KEY'
Response
200
{
  "key": "custom:5a8e2d14-7c3b-4f9a-b06e-1d2c3f4a5b69",
  "columns": [
    {
      "key": "property",
      "label_key": "reports.cols.property",
      "type": "string"
    },
    {
      "key": "check_in_on",
      "label_key": "reports.cols.check_in",
      "type": "date"
    },
    {
      "key": "status",
      "label_key": "reports.cols.status",
      "type": "badge",
      "value_i18n": "stays.status"
    },
    {
      "key": "currency",
      "label_key": "reports.cols.currency",
      "type": "string"
    },
    {
      "key": "price_amount",
      "label_key": "reports.cols.total",
      "type": "money"
    },
    {
      "key": "cost_amount",
      "label_key": "reports.cols.cost",
      "type": "money"
    }
  ],
  "rows": [
    {
      "id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18",
      "property": "Museum Hotel",
      "check_in_on": "2026-03-14",
      "status": "confirmed",
      "currency": "EUR",
      "price_amount": "1250.0",
      "cost_amount": "1100.0",
      "entity_url": "/stays/9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18",
      "__group": "confirmed"
    },
    {
      "id": "2c0f6b9a-4d18-4a7d-8e51-9d2f7c3a1b4e",
      "property": "Argos in Cappadocia",
      "check_in_on": "2026-03-21",
      "status": "option",
      "currency": "EUR",
      "price_amount": "980.0",
      "cost_amount": "860.0",
      "entity_url": "/stays/2c0f6b9a-4d18-4a7d-8e51-9d2f7c3a1b4e",
      "__group": "option"
    }
  ],
  "summary": {
    "total_rows": 2
  },
  "filters": {
    "from": "2026-03-01",
    "to": "2026-03-31",
    "currency": "EUR"
  },
  "generated_at": "2026-03-14T09:30:00.000Z",
  "pagination": null,
  "group_by": "status",
  "groups": [
    {
      "key": "confirmed",
      "label": "confirmed",
      "count": 1,
      "totals": {
        "price_amount": "1250.0",
        "cost_amount": "1100.0"
      }
    },
    {
      "key": "option",
      "label": "option",
      "count": 1,
      "totals": {
        "price_amount": "980.0",
        "cost_amount": "860.0"
      }
    }
  ],
  "totals": {
    "count": 2,
    "columns": {
      "price_amount": "2230.0",
      "cost_amount": "1960.0"
    },
    "by_currency": {
      "EUR": {
        "price_amount": "2230.0",
        "cost_amount": "1960.0"
      }
    }
  },
  "saved_report_id": "5a8e2d14-7c3b-4f9a-b06e-1d2c3f4a5b69",
  "name": "March hotel stays by status"
}