Menu

Liquidity forecast

Where the bank balance is heading: 30 days by day and 13 ISO weeks from the bank balance, open customer and supplier invoices, the bank payment queue, salary, taxes through the skattekonto, recurring invoices and forecast rows, each krona counted once and the lowest point flagged. Forecast rows are money expected that the books do not hold yet, upserted by your own key; they are not bookkeeping, so they are written directly under forecast:write.

Endpoints


GET /api/v1/companies/:companyId/cash-forecast

cash-forecast.get · scope reports:read

The 13-week liquidity forecast: where the bank balance is heading, by day and by ISO week.

Projects the bank balance from today: the opening balance (the banks' last reported balance, or the booked bank balance when no bank reports one, labelled), then every expected flow: open customer and supplier invoices on their due dates, the bank payment queue on its dates, salary runs as net pay, taxes through the skattekonto (Skatteverket's own debits, F-skatt, VAT and AGI), recurring invoices not created yet, and forecast rows. Answers 30 days by day and 13 ISO weeks (weeks, at most 26) as opening + in - out = closing, the lowest point flagged, and overdue invoices listed apart. Each flow is tagged known, derived or forecast; estimates say so. Read-only.

Use when: To answer "will we have money for X", find the week the balance is lowest, or plan payments.

Don't use for: The historical cash flow (GET /reports/kassaflodesanalys) or what is booked on the bank accounts (GET /measures/bank_balance).

Pitfalls

  • Each krona counts once: an invoice inside a payment order counts at the order's date; a salary run as net pay, its tax and avgifter through the skattekonto on the AGI's due date; a forecast row linked to an invoice yields to it.
  • Overdue invoices are listed under overdue, not projected: nothing says when they will be paid.
  • complete=false means a source could not be read; its caveat (SOURCE_UNAVAILABLE) names it.
  • Estimates are flagged: VAT for a period not yet declared is the books so far; salary for a month without a run repeats the latest run.

Risk: low · Idempotent: yes · Reversible: no · Dry-run supported: no

Query parameters

NameTypeRequiredDescription
weeksnumbernoISO weeks to project, the current week first. Default 13.

Response fields

NameType
currency"SEK"
todaystring
horizonobject
openingobject
daysobject[]
weeksobject[]
lowestobject
closingnumber
flowsobject[]
overdueobject
completeboolean
caveatsobject[]

Example request

{
  "weeks": 13
}

Example response

{
  "data": {
    "currency": "SEK",
    "today": "2026-10-28",
    "horizon": {
      "start": "2026-10-28",
      "end": "2027-01-24",
      "weeks": 13
    },
    "opening": {
      "amount": 412500,
      "source": "bank",
      "as_of": "2026-10-28T05:02:11Z",
      "age_days": 0,
      "accounts": []
    },
    "days": [
      {
        "date": "2026-10-28",
        "opening": 412500,
        "inflow": 0,
        "outflow": 18750,
        "closing": 393750,
        "lowest": false
      }
    ],
    "weeks": [
      {
        "iso_week": "2026-W44",
        "start": "2026-10-26",
        "end": "2026-11-01",
        "opening": 412500,
        "inflow": 62000,
        "outflow": 18750,
        "closing": 455750,
        "min_closing": 393750,
        "lowest": false
      }
    ],
    "lowest": {
      "date": "2026-11-25",
      "closing": 121300,
      "iso_week": "2026-W48"
    },
    "closing": 286400,
    "flows": [
      {
        "date": "2026-10-28",
        "direction": "out",
        "amount": 18750,
        "kind": "known",
        "source": "payment_order",
        "estimate": false,
        "counterparty": "Städbolaget AB",
        "reference": "F-2026-118",
        "ref": {
          "type": "payment_order",
          "id": "5c2a…"
        },
        "confidence": null
      }
    ],
    "overdue": {
      "receivables": {
        "total": 0,
        "invoices": []
      },
      "payables": {
        "total": 0,
        "invoices": []
      }
    },
    "complete": true,
    "caveats": []
  },
  "meta": {
    "request_id": "req_…",
    "api_version": "2026-05-12"
  }
}

GET /api/v1/companies/:companyId/cash-forecast-rows

cash-forecast-rows.list · scope reports:read

The forecast rows: money expected in or out that the books do not hold yet.

Lists the company's forecast rows by expected date: amount, direction, category, confidence, the outside key (external_source, external_id) and status (open, realised, superseded, cancelled). Filter by status, date range and outside system. Paginated with limit and offset; total is the full count. Read-only.

Use when: To see what has been added to the liquidity forecast, or to find a row to change or cancel.

Don't use for: The projection itself (GET /cash-forecast).

Pitfalls

  • Amounts are SEK and always positive; direction (in or out) says which way. Other currencies are not accepted yet.
  • A row dated before today leaves the projection: cancel it, or move its date if the money is still expected.
  • Link a row to its invoice (invoice_id or supplier_invoice_id) once the invoice exists: the invoice then counts and the row does not, so the money is never counted twice.

Risk: low · Idempotent: yes · Reversible: no · Dry-run supported: no

Query parameters

NameTypeRequiredDescription
status"open" | "realised" | "superseded" | "cancelled"noOnly rows with this status. Default: every status.
fromstringnoFirst expected date, inclusive.
tostringnoLast expected date, inclusive.
external_sourcestringnoOnly rows from this outside system, e.g. "boom".
limitnumbernoRows per page, default 100.
offsetnumberno

Response fields

NameType
rowsobject[]
totalnumber

Example request

{
  "status": "open"
}

Example response

{
  "data": {
    "rows": [
      {
        "cash_forecast_row_id": "7b0e…",
        "expected_date": "2026-11-20",
        "amount": 48000,
        "direction": "in",
        "currency": "SEK",
        "category": "revenue",
        "source": "pms",
        "external_source": "boom",
        "external_id": "booking-48213",
        "confidence": "likely",
        "dimension_value_id": null,
        "invoice_id": null,
        "supplier_invoice_id": null,
        "status": "open",
        "matched_transaction_id": null,
        "note": "Bokning vecka 47",
        "created_at": "2026-10-28T09:12:00Z",
        "updated_at": "2026-10-28T09:12:00Z"
      }
    ],
    "total": 1
  },
  "meta": {
    "request_id": "req_…",
    "api_version": "2026-05-12"
  }
}

POST /api/v1/companies/:companyId/cash-forecast-rows

cash-forecast-rows.upsert-batch · scope forecast:write

Add or update forecast rows by your own key, up to 500 at a time.

Writes rows to the liquidity forecast, each named by your system and its id (external_source, external_id): a key not seen before creates a row, a key seen before updates that row while it is open. A row that is realised, superseded or cancelled is left as it is and listed under skipped. Not bookkeeping: rows never reach the books, so they are written directly (no approval) under forecast:write. Up to 500 rows per call. Dry-runnable.

Use when: A system that knows future money (bookings in a property management system, a loan plan, planned purchases) feeds the forecast, or an agent adds an expected payment.

Don't use for: Booking anything: a forecast row is never a verifikat. Customer and supplier invoices already count on their own.

Pitfalls

  • Amounts are SEK and always positive; direction (in or out) says which way. Other currencies are not accepted yet.
  • A row dated before today leaves the projection: cancel it, or move its date if the money is still expected.
  • Link a row to its invoice (invoice_id or supplier_invoice_id) once the invoice exists: the invoice then counts and the row does not, so the money is never counted twice.
  • Sending the same batch again changes nothing: the key names the row. A cancelled row is not reopened by a later upsert.

Risk: low · Idempotent: yes · Reversible: yes · Dry-run supported: yes

Query parameters

NameTypeRequiredDescription
dry_runstringnotrue (any case) previews the write without committing it, like the X-Dry-Run: true header. Any other value commits.

Request body

NameTypeRequiredDescription
rowsobject[]yes
source"api" | "mcp" | "pms"noWho writes the rows. Default: the door called (api over REST, mcp over MCP). pms: a property management system.

Response fields

NameType
rowsobject[]
creatednumber
updatednumber
skippedobject[]

Example request

{
  "rows": [
    {
      "external_source": "boom",
      "external_id": "booking-48213",
      "expected_date": "2026-11-20",
      "amount": 48000,
      "direction": "in",
      "category": "revenue",
      "note": "Bokning vecka 47"
    }
  ],
  "source": "pms"
}

Example response

{
  "data": {
    "rows": [
      {
        "cash_forecast_row_id": "7b0e…",
        "expected_date": "2026-11-20",
        "amount": 48000,
        "direction": "in",
        "currency": "SEK",
        "category": "revenue",
        "source": "pms",
        "external_source": "boom",
        "external_id": "booking-48213",
        "confidence": "likely",
        "dimension_value_id": null,
        "invoice_id": null,
        "supplier_invoice_id": null,
        "status": "open",
        "matched_transaction_id": null,
        "note": "Bokning vecka 47",
        "created_at": "2026-10-28T09:12:00Z",
        "updated_at": "2026-10-28T09:12:00Z"
      }
    ],
    "created": 1,
    "updated": 0,
    "skipped": []
  },
  "meta": {
    "request_id": "req_…",
    "api_version": "2026-05-12"
  }
}

POST /api/v1/companies/:companyId/cash-forecast-rows/cancel

cash-forecast-rows.cancel · scope forecast:write

Cancel a forecast row: it leaves the forecast and stays on record.

Sets an open row's status to cancelled; rows are never deleted. Name the row by cash_forecast_row_id, or by your own external_source and external_id. Cancelling a cancelled row changes nothing; a realised or superseded row cannot be cancelled. Not bookkeeping: written directly under forecast:write. Dry-runnable.

Use when: Money that was expected will not come or go: a booking cancelled, a purchase dropped.

Don't use for: Changing a row's amount or date (upsert it with the same key).

Pitfalls

  • A cancelled row is not reopened by a later upsert of the same key: write a new key if the money is expected again.

Risk: low · Idempotent: yes · Reversible: no · Dry-run supported: yes

Query parameters

NameTypeRequiredDescription
dry_runstringnotrue (any case) previews the write without committing it, like the X-Dry-Run: true header. Any other value commits.

Request body

NameTypeRequiredDescription
cash_forecast_row_idstringnoThe row to cancel. Or name it by external_source and external_id.
external_sourcestringno
external_idstringno

Response fields

NameType
rowobject

Example request

{
  "external_source": "boom",
  "external_id": "booking-48213"
}

Example response

{
  "data": {
    "row": {
      "cash_forecast_row_id": "7b0e…",
      "expected_date": "2026-11-20",
      "amount": 48000,
      "direction": "in",
      "currency": "SEK",
      "category": "revenue",
      "source": "pms",
      "external_source": "boom",
      "external_id": "booking-48213",
      "confidence": "likely",
      "dimension_value_id": null,
      "invoice_id": null,
      "supplier_invoice_id": null,
      "status": "cancelled",
      "matched_transaction_id": null,
      "note": "Bokning vecka 47",
      "created_at": "2026-10-28T09:12:00Z",
      "updated_at": "2026-10-28T09:12:00Z"
    }
  },
  "meta": {
    "request_id": "req_…",
    "api_version": "2026-05-12"
  }
}