Files
accounted/packages/connect-contract
Jakob Wennberg 4c6feea64d feat(connect): bank sync through the connector operation, with a per-company canary (#2205)
* feat(connect): bank sync through the connector operation, with a per-company canary

In connector mode the enable-banking sync no longer pages Enable Banking on
the instance: it calls POST /api/connect/bank/sync on the hosted service with
the session id it holds and the account, and receives booked, normalized
rows plus the raw provider pages to archive. Everything downstream is shared
with the direct path (stored external ids computed here from booking_date,
amount and the account scope; ingest; archive; balance refresh), so a company
that moves to the connector produces byte-identical keys. A 410 from the
service maps onto the same SessionExpiredError the direct path throws.

bankConnectorMode(companyId) gains CONNECT_BANK_CANARY_COMPANIES: listed
companies use the connector even while the installation has its own Enable
Banking credentials, which is how hosted Accounted moves its bank sync to
Connect a few companies at a time before dropping its keys. The contract
package gains the bank sync request/response schemas (2026-09-03).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>

* fix(connect): calendar-valid dates, body read inside the timeout, service origin as the default

Review follow-ups on #2205. The contract validates date_from, date_to and
booking_date with z.iso.date() (2026-02-30 and an empty booking date are
refused; the installation derives its stored keys from booking_date). The
connector sync reads the response body inside the timeout window so a
service that stalls the body cannot hold the sync open. DEFAULT_CONNECT_BASE_URL
now names the connector service (connect.accounted.se), which is where the
sync operation exists; the hosted app's copy of the connector routes is
legacy and hosted Accounted itself sets GNUBOK_CONNECT_URL explicitly.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>

---------

Signed-off-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-03 11:06:47 +02:00
..

@accounted/connect-contract

The wire contract between an Accounted ledger installation (the hosted service or a self-hosted instance) and Accounted Connect, the service that operates the provider integrations only Accounted can run: bank feeds through its PSD2 credentials, the Skatteverket API client, the Peppol access point, company lookup, the migration sources.

This package is shape only: constants, Zod schemas and the TypeScript types inferred from them. There is no behaviour and no provider code in it. Both sides of the connection validate with the same schemas so they cannot drift apart, and the package is MIT so that anyone may implement either side.

What is in it:

  • key prefix, header names and the entitlements path an installation uses;
  • the entitlements and sync-report payloads of the hourly key sync;
  • the error envelope and the stable error codes the service answers with;
  • the Peppol operations (/api/connect/peppol/*): request and response schemas plus the operation table (method, path, company header required).

Versioning: CONTRACT_VERSION is a date. Fields are only ever added; a breaking change is a new operation or family, never a changed one.

Use

import { peppolSubmissionSchema, PEPPOL_OPERATIONS, CONTRACT_VERSION } from '@accounted/connect-contract'

const parsed = peppolSubmissionSchema.safeParse(body)
if (!parsed.success) return badRequest(parsed.error)

Inside the Accounted repository the package is consumed from source through a path alias. To publish, build from the repository root with the root dependencies installed:

npx tsc -p packages/connect-contract/tsconfig.json
cd packages/connect-contract && npm publish --access public