Business Gateway

Customer

Account-scoped customer API (/v1)

GET
/v1/account

The account this credential belongs to, plus the companies it reaches. restricted is true for a credential confined to particular companies; such a credential reads the account's id, name and status only, because the remaining fields describe the account holder's own corporate identity and billing rather than anything about the companies under it.

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Response Body

application/json

application/json

curl -X GET "https://example.com/v1/account"
{}
POST
/v1/account/export

Owner only. Starts building a zip of everything the account holds: companies, participants, document metadata and payloads, and the audit trail. Returns {export: {id, status}}; poll GET .../export/{exportId} until it carries a download link. Large accounts take a while.

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Response Body

application/json

application/json

curl -X POST "https://example.com/v1/account/export"
{}
GET
/v1/account/export/{exportId}

Owner only. {export: {id, status, downloadUrl?, expiresAt?, error?}}. downloadUrl is present only once status is DONE and within its 24h TTL.

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Path Parameters

exportId*string

Response Body

application/json

application/json

curl -X GET "https://example.com/v1/account/export/string"
{}
GET
/v1/api-keys

Owner or company admin; an ordinary member is refused. Each key carries permissions: an array of read/send/receive/manage. A company admin sees only the keys confined to its own companies, never the account-wide ones. ?companyId narrows to one company; a key confined to no company reaches the whole account and stays in view whichever company is asked for.

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Response Body

application/json

application/json

curl -X GET "https://example.com/v1/api-keys"
{}
POST
/v1/api-keys

Owner or company admin. companyId confines the key to one company; leaving it out produces a key that reaches every company under the account, which only an owner may create. permissions is an array of read/send/receive/manage; omitted or empty means all four. Calling a route the key lacks the permission for is 403 PERMISSION_DENIED. The key itself is in the response and is never shown again.

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

curl -X POST "https://example.com/v1/api-keys" \  -H "Content-Type: application/json" \  -d '{}'
{}
DELETE
/v1/api-keys/{id}

Owner or company admin, the latter only for a key confined to one of its own companies. Revoking is immediate and cannot be undone.

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Path Parameters

id*string

Response Body

application/json

application/json

curl -X DELETE "https://example.com/v1/api-keys/string"
{}
POST
/v1/auth/forgot

{email}. Always 204, whether or not the address belongs to a user, so that the response cannot be used to discover who has an account.

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

curl -X POST "https://example.com/v1/auth/forgot" \  -H "Content-Type: application/json" \  -d '{}'
{}
POST
/v1/auth/login

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

curl -X POST "https://example.com/v1/auth/login" \  -H "Content-Type: application/json" \  -d '{}'
{}
POST
/v1/auth/logout

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Response Body

application/json

application/json

curl -X POST "https://example.com/v1/auth/logout"
{}
POST
/v1/auth/oidc

Signs in with an OIDC token from the account's identity provider. 401 SSO_NOT_LINKED — distinct from the generic UNAUTHORIZED — means the token is valid but no user here is linked to it yet; link the identity after signing in with a password.

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

curl -X POST "https://example.com/v1/auth/oidc" \  -H "Content-Type: application/json" \  -d '{}'
{}
POST
/v1/auth/passkey/challenge

Unauthenticated. Returns {publicKey}, the options the browser passes to navigator.credentials.get(), with the challenge and every credential id base64url-encoded. The challenge is single-use and lives five minutes. allowCredentials is empty by design: passkeys here are discoverable, so the authenticator names the account and this route reveals nothing about who has one.

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

curl -X POST "https://example.com/v1/auth/passkey/challenge" \  -H "Content-Type: application/json" \  -d '{}'
{}
POST
/v1/auth/passkey/login

Unauthenticated: {id, authenticatorData, clientDataJSON, signature, userHandle}, all base64url. Returns the same {token, expiresAt, user} a password login does. Every verification failure is one 401 UNAUTHORIZED, except 401 PASSKEY_CLONE_DETECTED — the authenticator's signature counter went backwards, and the passkey should be removed rather than retried.

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

curl -X POST "https://example.com/v1/auth/passkey/login" \  -H "Content-Type: application/json" \  -d '{}'
{}
POST
/v1/auth/reset

{token, password}. A token that is unknown, expired, already used, or whose user can no longer sign in is 400 RESET_TOKEN_INVALID — its own code, so a client can offer a fresh link without reading the message.

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

curl -X POST "https://example.com/v1/auth/reset" \  -H "Content-Type: application/json" \  -d '{}'
{}
POST
/v1/auth/signup

Creates an account (the contract party), its first company (the account's own legal entity unless a company object says otherwise) and the first owner: {account:{name?,legalName,countryCode,registrationNumber?,vatNumber?}, company?:{name?,identifier?,countryCode?,registrationNumber?,vatNumber?}, owner:{name?,email,password}, bootstrapToken?}.

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

curl -X POST "https://example.com/v1/auth/signup" \  -H "Content-Type: application/json" \  -d '{}'
{}
GET
/v1/billing

What the account's companies cost, per month. Answers the owner, a user holding the billing.view grant (PATCH /v1/users/{id}) and a desk session; everyone else, every API key included, gets 403 FORBIDDEN. A user confined to some companies sees only those companies, and every month, line and total is summed over them alone (scope: "companies"). ?companyId narrows the report further to the companies named — repeated (?companyId=a&companyId=b) or comma-separated (?companyId=a,b) — and every month, line and total (yearToDate and sinceStart included) is then summed over those alone, with scope: "companies" and companyIds echoing the selection; an empty ?companyId= is no filter. It only narrows what the caller already sees: a company outside the caller's scope, or one that does not exist, is 404 NOT_FOUND, as on every customer route that takes a companyId. ?period=2026-09 or 2026-Q3, or ?from=2026-01&to=2026-09 (at most 120 months); with none of them, the year to date. months[] has one entry per month with state locked (frozen when the basis was taken out), draft (ended and measured, not yet locked), running (this month, counted live), unmeasured (ended before anything measured it: no numbers) or future, and companyMonths, participantMonths, messages, outboundMessages, inboundMessages, amountMinor, pricedAmountMinor and priceMissing. companies[] gives the same per company and per company and month; lines[] the period's price lines, unit price × quantity = amount. totals has period, yearToDate and sinceStart (from the first measured month), each with its from and to. Amounts are integer minor units of currency. A month without a price keeps its counts and has priceMissing: true with amountMinor null — never 0 — and so does every sum that includes it; pricedAmountMinor is the priced part. outboundMessages and inboundMessages are null where the split is not known (the metering rows behind an old month may have been archived), and always in the yearToDate and sinceStart totals. Sandbox companies are free and appear nowhere. ?format=csv gives one row per month and company: month,state,companyId,company,companyMonths,participantMonths,messages,outboundMessages,inboundMessages,amountMinor,amount,priceMissing,currency.

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Query Parameters

period?string
from?string
to?string
companyId?string
format?string

Response Body

application/json

application/json

curl -X GET "https://example.com/v1/billing"
{}
GET
/v1/companies

?q matches name, identifier, id, registration number and VAT number, case-insensitively. Without ?limit or ?cursor the whole matching list comes back at once; pass either and cursor pagination applies, with nextCursor set while more remains.

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Query Parameters

cursor?string
limit?string
from?string
to?string
direction?string
status?string

Response Body

application/json

application/json

curl -X GET "https://example.com/v1/companies"
{}
POST
/v1/companies

Owner, or an account-wide API key with manage. Adds a legal entity under the account. Empty countryCode inherits the account's; an omitted identifier is derived from the name. When the account is expected to do KYC the response carries onboarding: {url, expiresAt}, a hosted onboarding link to hand the company — see POST /v1/companies/{id}/kyc/onboarding-link. Participants under a company that has not completed KYC wait for operator approval. sandbox: true makes a sandbox company: its participants are local only (never on the Peppol network) and are created READY without operator approval, it gets no KYC onboarding link, and POST /v1/companies/{id}/test-inbound may simulate documents arriving at it; only an operator changes the flag afterwards.

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

curl -X POST "https://example.com/v1/companies" \  -H "Content-Type: application/json" \  -d '{}'
{}
GET
/v1/companies/{id}

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Path Parameters

id*string

Response Body

application/json

application/json

curl -X GET "https://example.com/v1/companies/string"
{}
PATCH
/v1/companies/{id}

Owner, or an account-wide API key with manage. name, identifier (409 IDENTIFIER_TAKEN on collision), countryCode, registrationNumber, vatNumber. A company verified through KYC refuses a new registrationNumber or countryCode with 409 KYC_LOCKED. sandbox cannot be changed here.

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Path Parameters

id*string

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

curl -X PATCH "https://example.com/v1/companies/string" \  -H "Content-Type: application/json" \  -d '{}'
{}
DELETE
/v1/companies/{id}

Owner, or an account-wide API key with manage — the same callers as POST and PATCH. Allowed once every participant of the company is DEACTIVATED (deactivate them with DELETE /v1/participants/{id} first), and always for a company that has none; otherwise 409 COMPANY_NOT_DEREGISTERED. The company is closed (queued outbound dropped, as on close), its DEACTIVATED participants are deleted with it, API keys confined to it are revoked and SFTP logins confined to it disabled, and it frees its identifier for a new company. It then disappears from GET /v1/companies and every other list, and GET /v1/companies/{id} answers 404 — as does a second DELETE. The deletion is soft: documents, usage and billing keep naming the company, and audit records COMPANY_DELETED plus PARTICIPANT_DELETED per participant. 204 No Content.

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Path Parameters

id*string

Response Body

application/json

application/json

curl -X DELETE "https://example.com/v1/companies/string"
{}
GET
/v1/companies/{id}/kyc

The company's latest KYC case: {kyc: {id, status, method, source, orgNumber, legalName, persons, signatoryRule, signatures, requiresReview, companyVerified, ...}}. status is NOT_STARTED, PENDING_SIGNATURES, MANUAL_REVIEW, VERIFIED, REJECTED, EXPIRED or WITHDRAWN; a company that never started has only companyId, status, method, countryCode, required and companyVerified. required is false when the account is exempt from KYC: offer it as optional rather than as a step to finish. Nothing is blocked either way. method is "register" where a register extract is uploaded (Sweden: a Bolagsverket registreringsbevis) and "manual" elsewhere. companyVerified stays true while a newer case is in progress. 501 KYC_NOT_CONFIGURED when the deployment has no KYC.

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Path Parameters

id*string

Response Body

application/json

application/json

curl -X GET "https://example.com/v1/companies/string/kyc"
{}
POST
/v1/companies/{id}/kyc

Owner session only; an API key gets 403 KYC_USE_ONBOARDING_LINK. {objectKey} for a country with a register (method "register"), or {manual: {orgNumber, legalName, address?, signatoryText?}} for any other. A Swedish extract must be the PDF Bolagsverket issued, sealed by Bolagsverket over every byte and sealed within the last 90 days: 400 KYC_DOCUMENT_NOT_SEALED (no seal, or the file was changed after sealing — a scan or a re-saved copy), KYC_DOCUMENT_NOT_FROM_REGISTER, KYC_DOCUMENT_TOO_OLD, and KYC_ORG_NUMBER_MISMATCH when it is for another registration number than the company's. An authentic extract whose firmateckning cannot be read with confidence is accepted with requiresReview true. Starting a case ends any case still open; a verified company stays verified until the new case is. 400 KYC_MANUAL_REQUIRED for objectKey in a country without a register.

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Path Parameters

id*string

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

curl -X POST "https://example.com/v1/companies/string/kyc" \  -H "Content-Type: application/json" \  -d '{}'
{}
GET
/v1/companies/{id}/kyc/documents/{kind}

Owner, or an account-wide API key with manage. kind is agreement (the version the signatories were sent) or signed-agreement (that agreement followed by the signature appendix: who signed, when, from which address). From the case that verified the company, or else its latest case; 404 until there is one. The PDF comes back as an attachment with its SHA-256 in X-Content-SHA256, and the read is audited as KYC_DOCUMENT_READ.

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Path Parameters

id*string
kind*string

Response Body

application/json

application/json

curl -X GET "https://example.com/v1/companies/string/kyc/documents/string"
{}
POST
/v1/companies/{id}/kyc/onboarding-link

Owner, or an account-wide API key with manage. Returns {onboarding: {url, expiresAt}}: a link, valid 30 days, that lets whoever holds it do the company's KYC on our hosted page — upload the registreringsbevis (or enter a foreign company), choose who signs — without an account here. Issuing one ends the previous link for the company. This is how a platform integrating through an API key gets its customers through KYC: the key itself is refused on the KYC routes with 403 KYC_USE_ONBOARDING_LINK.

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Path Parameters

id*string

Response Body

application/json

application/json

curl -X POST "https://example.com/v1/companies/string/kyc/onboarding-link"
{}
POST
/v1/companies/{id}/kyc/signatories

Owner session only (an API key gets 403 KYC_USE_ONBOARDING_LINK), on a case in PENDING_SIGNATURES. With a signatory rule: {alternative, signers: [{personId, email}]}, where alternative indexes signatoryRule.alternatives (clauses naming individuals are listed in signatoryRule.notOffered and cannot be chosen) and the chosen persons must bind the company under it (400 KYC_SIGNATORIES_INSUFFICIENT otherwise). Without one (requiresReview): {signers: [{name, role?, email}]}. Every signer needs their own email address. Each gets a personal link valid 14 days; calling again replaces the earlier choice and ends its unsigned links, and someone who already signed is not asked again. 409 KYC_NO_AGREEMENT when no agreement version is published. When mail cannot be sent, unsentLinks carries the links to pass on another way.

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Path Parameters

id*string

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

curl -X POST "https://example.com/v1/companies/string/kyc/signatories" \  -H "Content-Type: application/json" \  -d '{}'
{}
POST
/v1/companies/{id}/kyc/uploads

Owner session only; an API key gets 403 KYC_USE_ONBOARDING_LINK. {size} in bytes, at most 5 MiB. Returns {objectKey, uploadUrl, expiresAt, headers}: PUT the PDF to uploadUrl with exactly the headers in headers (Content-Type: application/pdf) and a body of exactly size bytes (the URL is signed over both), then pass objectKey to POST /v1/companies/{id}/kyc. An upload not submitted within a day is deleted, and so is one that is refused; an accepted one is filed with the case.

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Path Parameters

id*string

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

curl -X POST "https://example.com/v1/companies/string/kyc/uploads" \  -H "Content-Type: application/json" \  -d '{}'
{}
POST
/v1/companies/{id}/test-inbound

Owner, or an account-wide API key with manage. For a company created with sandbox: true only (409 NOT_SANDBOX otherwise), in production as well. {receiver, sender?, type | documentTypeId+processId, payload, as4MessageId?}: the document runs the real inbound path — validation, storage, webhooks, pull — as a test injection (TEST_INBOUND_INJECTED). receiver must be one of this company's participants; a sandbox company's participants are always local, so a simulated document never shares a mailbox with a real one. Sender, receiver and type may be left out when the payload is an SBD that carries them. Rate limited per account.

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Path Parameters

id*string

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

curl -X POST "https://example.com/v1/companies/string/test-inbound" \  -H "Content-Type: application/json" \  -d '{}'
{}
POST
/v1/desk-sessions/exchange

Unauthenticated: a single-use handoff token is itself the credential. A reused or expired token is 401 UNAUTHORIZED.

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

curl -X POST "https://example.com/v1/desk-sessions/exchange" \  -H "Content-Type: application/json" \  -d '{}'
{}
GET
/v1/files

Every file uploaded to out/ over SFTP for the companies this credential reaches, newest first, and what became of it — including the ones that were refused and so never became a message. Returns {files: [{id, accountId, companyId, companyName?, accountName?, path, fileName, sizeBytes, sha256, status, reason?, detail?, documentId?, attempts, createdAt, updatedAt}], nextCursor?}. status is queued (waiting, possibly after a transient failure that reason names), processing, sent (became a message and was filed under out/sent/; the message's own status says how far it got) or failed (filed under out/failed/). A failed file carries reason and detail, the same two things written to its .error.json; when it became a message that will not be sent — it failed validation — documentId names it too. path is relative to the account's SFTP root and is where the file was uploaded, not where it was moved. Filter with ?status (one of the four; anything else is 400 VALIDATION_FAILED), ?companyId, ?from and ?to (on createdAt); page with ?cursor and ?limit (default 50, at most 100).

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Query Parameters

status?string
companyId?string
from?string
to?string
cursor?string
limit?string

Response Body

application/json

application/json

curl -X GET "https://example.com/v1/files"
{}
GET
/v1/files/{id}

One entry of GET /v1/files. A file of a company this credential does not reach is 404, like an id that does not exist.

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Path Parameters

id*string

Response Body

application/json

application/json

curl -X GET "https://example.com/v1/files/string"
{}
POST
/v1/kyc/onboarding/signatories

Unauthenticated: {token, alternative?, signers}. Same as POST /v1/companies/{id}/kyc/signatories.

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

curl -X POST "https://example.com/v1/kyc/onboarding/signatories" \  -H "Content-Type: application/json" \  -d '{}'
{}
POST
/v1/kyc/onboarding/submit

Unauthenticated: {token, objectKey} or {token, manual: {…}}. Same validation and responses as POST /v1/companies/{id}/kyc.

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

curl -X POST "https://example.com/v1/kyc/onboarding/submit" \  -H "Content-Type: application/json" \  -d '{}'
{}
POST
/v1/kyc/onboarding/uploads

Unauthenticated: {token, size}. Same as POST /v1/companies/{id}/kyc/uploads for the link's company.

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

curl -X POST "https://example.com/v1/kyc/onboarding/uploads" \  -H "Content-Type: application/json" \  -d '{}'
{}
POST
/v1/kyc/onboarding/view

Unauthenticated: {token} from a hosted onboarding link is the credential, for the one company it was issued for. Returns {company: {name, countryCode, registrationNumber}, kyc: …}, the same kyc object as GET /v1/companies/{id}/kyc. An unknown, replaced or expired link is 400 KYC_ONBOARDING_LINK_INVALID.

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

curl -X POST "https://example.com/v1/kyc/onboarding/view" \  -H "Content-Type: application/json" \  -d '{}'
{}
POST
/v1/kyc/signing/decline

Unauthenticated: {token}. The case stays open; the company's owner can choose other signatories. 204.

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

curl -X POST "https://example.com/v1/kyc/signing/decline" \  -H "Content-Type: application/json" \  -d '{}'
{}
POST
/v1/kyc/signing/sign

Unauthenticated: {token, typedName, accept: true}. Records the signature with the time, the client address and user agent, and the agreement's SHA-256, consuming the token. When the signatures on the case bind the company under its firmateckning the case becomes VERIFIED; a case without a readable firmateckning goes to MANUAL_REVIEW once everyone asked has signed. Returns {status}, the case status afterwards. 400 KYC_SIGNING_LINK_INVALID as for view.

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

curl -X POST "https://example.com/v1/kyc/signing/sign" \  -H "Content-Type: application/json" \  -d '{}'
{}
POST
/v1/kyc/signing/view

Unauthenticated: {token} from the mailed link is the credential, sent in the body so it never reaches an access log. Returns the company, the signer's name and role, the agreement version with its SHA-256, and a short-lived download url for the exact PDF being signed. An unknown, used, declined or expired token, or one whose case is no longer waiting for signatures, is 400 KYC_SIGNING_LINK_INVALID.

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

curl -X POST "https://example.com/v1/kyc/signing/view" \  -H "Content-Type: application/json" \  -d '{}'
{}
GET
/v1/me

The signed-in user. session reports {authMethod: "password"|"sso", issuer, provider}, describing how they signed in; it is absent for an API key. Reachable even while user.mustChangePassword is true, when every other route is refused — so a client can tell why it is being refused. canViewBilling says whether GET /v1/billing will answer this caller: true for an owner, a user holding the billing.view grant, and a desk session; false for every API key.

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Response Body

application/json

application/json

curl -X GET "https://example.com/v1/me"
{}
GET
/v1/messages

Documents belonging to the companies this credential reaches; ?companyId narrows to one. ?q matches the sender and receiver participant from the start of the value, case-sensitively, or the document id exactly — never a substring in the middle. ?counts=true without an explicit ?from covers the last 30 days rather than the account's whole history. ?type is an alias from the register (invoice, credit-note, …), which names a document type and process together, or a raw documentTypeId. ?outcome is one of the statistics outcomes and stands for a set of statuses: delivered (AS4_ACCEPTED, CUSTOMER_DELIVERED), failed (SEND_FAILED, DISCOVERY_FAILED, REJECTED, VALIDATION_UNAVAILABLE, VALIDATION_ERROR, CUSTOMER_DELIVERY_FAILED), invalid (VALIDATION_FAILED, BUSINESS_INVALID), pending (every other status) and parked, which matches no document: a parked payload never became one. ?status still applies inside ?outcome. ?source is network or test. ?sandbox is exclude (leave sandbox companies' documents out), only (just theirs) or include (both, the default when it is absent). All four narrow ?counts=true and format=csv as well, so with the same from/to days, scope, type, outcome and source, and sandbox=exclude unless the statistics were asked with includeSandbox=true, its per-status counts add up to the statistics' count. An unknown outcome, source or sandbox value is 400 VALIDATION_FAILED.

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Query Parameters

cursor?string
limit?string
from?string
to?string
direction?string
status?string
companyId?string
q?string
delivery?string
type?string
outcome?string
source?string
sandbox?string
format?string
counts?boolean

When true, the response gets a counts field breaking the active filter down by status/reason/eventType. limit=0 skips the rows and returns only counts.

Response Body

application/json

application/json

curl -X GET "https://example.com/v1/messages"
{}
POST
/v1/messages

202 with status QUEUED_VALIDATION is the usual answer: the document is checked against the Peppol rules after this request returns, not inside it, so the verdict is on a later GET rather than here: VALID moves on to sending; VALIDATION_FAILED raises message.outbound.failed on the account's webhooks and is mailed to the company's notification contacts, with the findings on GET /v1/messages/{id}. A document type with no rules to check, sent under a capability that allows unvalidated documents, skips that step and is 201 with status QUEUED. A replayed Idempotency-Key is 200 with the current status. A payload that is a whole Standard Business Document is accepted: the envelope is taken off and the business document inside is what is stored and sent, under the platform's own SBDH. The envelope's sender, receiver, document type and process must then match the request's, or the request is 400 VALIDATION_FAILED. type is a document type alias from the register (invoice, credit-note, order, …); pint-aunz-invoice and pint-aunz-credit-note are Peppol PINT A-NZ, the billing format for Australian and New Zealand receivers, whose documentTypeId is under the peppol-doctype-wildcard scheme rather than busdox-docid-qns. The business document must be what it is sent as: its root element and namespace, its cbc:CustomizationID (exactly) and, when present, its cbc:ProfileID must be the ones documentTypeId and processId name, or the request is 400 VALIDATION_FAILED before anything is stored — a BIS Billing 3.0 invoice sent as pint-aunz-invoice, a PINT invoice sent as invoice, a credit note sent as invoice. A documentTypeId ending in the wildcard indicator * matches no document (BR-PDW-02: * belongs in SMP registrations only). A document that states no CustomizationID among the root's children (CII, non-UBL) is not checked on that point.

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

curl -X POST "https://example.com/v1/messages" \  -H "Content-Type: application/json" \  -d '{}'
{}
POST
/v1/messages/uploads

Body {bytes, companyId?}. Answers {payloadObjectKey, uploadUrl, expiresAt, headers}. The uploadUrl is signed over Content-Type and Content-Length: PUT the document with exactly the headers in headers (Content-Type: application/xml) and a body of exactly bytes long, or the object store answers 403 SignatureDoesNotMatch. Then send the key with POST /v1/messages or check it with POST /v1/validate.

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

curl -X POST "https://example.com/v1/messages/uploads" \  -H "Content-Type: application/json" \  -d '{}'
{}
GET
/v1/messages/{id}

deliveryUnconfirmed is true on an outbound SEND_FAILED message whose send outcome the platform could not learn: the receiver may already have it, and it is never resent. Show it as unconfirmed rather than failed, and do not resend it either. mlsEvents lists the Peppol Message Level Status trail, oldest first. An entry with direction INBOUND and status RECEIVED is an MLS the receiving Service Provider sent about this outbound message: responseCode is AP (delivered with confirmation), AB (delivered without) or RE (rejected); description is its text; issues is its line responses [{location, reasonCode, description}] with reasonCode SV, BV, BW or FD, and reasons their distinct codes; receivedAt, mlsId and sender (the SP's 0242 participant) identify it. A message's own status does not change on an MLS. Entries with direction OUTBOUND (PENDING/SENT/FAILED/SKIPPED) are history from before 2026-10-08 and carry none of these fields. The message.mls webhook carries mlsEventId, responseCode, reasons and issues for the entry it announces. recipientStatus, on an outbound message once the receiving Service Provider's MLS has arrived, is that status in one word: CONFIRMED (AP, delivered with confirmation), DELIVERED (AB, delivered without) or REJECTED (RE), with recipientStatusReasons the distinct reason codes of a rejection. Outbound documents ask for a status on every send (MLS_TYPE=ALWAYS_SEND), but a receiving Service Provider that does not support MLS sends none, so an absent recipientStatus means no answer, not a failure.

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Path Parameters

id*string

Response Body

application/json

application/json

curl -X GET "https://example.com/v1/messages/string"
{}
POST
/v1/messages/{id}/ack

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Path Parameters

id*string

Response Body

application/json

application/json

curl -X POST "https://example.com/v1/messages/string/ack"
{}
GET
/v1/messages/{id}/payload

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Path Parameters

id*string

Response Body

application/json

application/json

curl -X GET "https://example.com/v1/messages/string/payload"
{}
POST
/v1/network/participants/lookup

{companyId?, ids: ["scheme:value", ...]}, at most 100 ids (400 VALIDATION_FAILED naming the cap above that, or on an empty list). Looked up concurrently, capped at 8 against SMP. Returns {results: [{id, status: OK|INVALID|ERROR, canonicalId?, exists?, smpUrl?, capabilities?, error?}]}, one entry per id in the same order, even when some ids fail: INVALID is a malformed id (no SMP call made), ERROR is a failed SMP call, and OK carries exists (false is a normal, common answer, not an error).

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

curl -X POST "https://example.com/v1/network/participants/lookup" \  -H "Content-Type: application/json" \  -d '{}'
{}
GET
/v1/network/participants/search

?q is either a full Peppol id, looked up exactly (response mode "id"), or free text tried as a national registration number against every scheme we know a format rule for and, where the Peppol Directory is available, as a name or number query against it (mode "search"). Search results are confirmed hits only: a candidate that does not parse, is not registered, or returns no Directory match all give zero rows rather than a not-found entry — unlike "id" mode, whose exists:false is definitive, a search miss only means this route did not find them. directoryConfigured says whether the Directory was consulted, so an empty result can be explained as "they may simply never have published a business card".

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Response Body

application/json

application/json

curl -X GET "https://example.com/v1/network/participants/search"
{}
GET
/v1/network/participants/{peppolId}

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Path Parameters

peppolId*string

Response Body

application/json

application/json

curl -X GET "https://example.com/v1/network/participants/string"
{}
GET
/v1/network/participants/{peppolId}/capabilities

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Path Parameters

peppolId*string

Response Body

application/json

application/json

curl -X GET "https://example.com/v1/network/participants/string/capabilities"
{}
GET
/v1/participants

Every participant under the companies the principal reaches; ?companyId narrows to one of them. ?status and ?q (scheme:value, canonicalId, name, identifier) filter in memory like the admin list. ?counts=true adds a status breakdown for the account (scoped by companyId when given).

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Query Parameters

cursor?string
limit?string
from?string
to?string
direction?string
status?string
counts?boolean

When true, the response gets a counts field breaking the active filter down by status/reason/eventType. limit=0 skips the rows and returns only counts.

Response Body

application/json

application/json

curl -X GET "https://example.com/v1/participants"
{}
POST
/v1/participants

Optional capabilities: an array of {documentTypeId, processId, direction, allowUnvalidated}, the same element shape as POST /v1/participants/{id}/capabilities, document type aliases included ({"type": "invoice"} or {"documentTypeId": "invoice"}). Created in the same transaction as the participant. Omitted = no capabilities, matching pre-existing behaviour. A Peppol id another live participant holds, on any account, is 409 CONFLICT; one whose participant is DEACTIVATED is free to register again (DEACTIVATING still holds it until the SMP entry is gone).

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

curl -X POST "https://example.com/v1/participants" \  -H "Content-Type: application/json" \  -d '{}'
{}
GET
/v1/participants/{id}

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Path Parameters

id*string

Response Body

application/json

application/json

curl -X GET "https://example.com/v1/participants/string"
{}
PATCH
/v1/participants/{id}

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Path Parameters

id*string

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

curl -X PATCH "https://example.com/v1/participants/string" \  -H "Content-Type: application/json" \  -d '{}'
{}
DELETE
/v1/participants/{id}

On any status but DEACTIVATED it deactivates and returns the participant: one with anything in SMP becomes DEACTIVATING and stays published for the unpublish grace, anything else becomes DEACTIVATED. On a participant already DEACTIVATED it deletes it instead and answers 204 No Content: the participant leaves every list and count, GET /v1/participants/{id} answers 404, and its identifier is free again in its company. Calling DELETE until it answers 204 therefore takes a participant all the way off. The deletion is soft — documents, usage and billing keep pointing at it — and is audited as PARTICIPANT_DELETED. Participant responses carry deletedAt, always null on the customer API.

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Path Parameters

id*string

Response Body

application/json

application/json

curl -X DELETE "https://example.com/v1/participants/string"
{}
GET
/v1/participants/{id}/audit

Filter with ?eventType, ?q (substring over actor and metadata), ?from/?to. Each row carries actorName and, where the event has one, targetType/targetId/targetName/targetSub (A8) — no targetHref, since this surface has no admin pages to link a company/account/login target to.

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Path Parameters

id*string

Query Parameters

cursor?string
limit?string
from?string
to?string
direction?string
status?string

Response Body

application/json

application/json

curl -X GET "https://example.com/v1/participants/string/audit"
{}
GET
/v1/participants/{id}/business-card

The business card published to the Peppol Directory for this participant: {participantId, custom, entities: [{names: [{name, language?}], countryCode, geographicalInformation?, identifiers: [{scheme, value}], websites: [url], contacts: [{type?, name?, phoneNumber?, email?}], additionalInformation?, registrationDate?}]}. custom is false when no card has been set and entities is the default built from the company's name and country — what is published until a card is PUT.

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Path Parameters

id*string

Response Body

application/json

application/json

curl -X GET "https://example.com/v1/participants/string/business-card"
{}
DELETE
/v1/participants/{id}/business-card

Drops the stored card; the default built from the company's name and country is republished. Answers that default (custom false). A no-op when no card is stored. Audited as BUSINESS_CARD_DELETED.

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Path Parameters

id*string

Response Body

application/json

application/json

curl -X DELETE "https://example.com/v1/participants/string/business-card"
{}
PUT
/v1/participants/{id}/business-card

Replaces the whole card with {entities: [...]} (participantId and custom are accepted and ignored, so a GET response can be sent back as is) and queues it for republishing. At least one entity, each with at least one name (at most 256 characters); countryCode is ISO 3166-1 alpha-2; language a two- or three-letter ISO 639 code; registrationDate YYYY-MM-DD; websites absolute http(s) URLs; email a bare address; at most 20 entities, 10 names, 20 identifiers, 10 websites and 20 contacts per entity. Anything else is 400 VALIDATION_FAILED naming the field. Answers the stored card, same shape as GET. Audited as BUSINESS_CARD_UPDATED. Published only while the participant is ACTIVE on the network.

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Path Parameters

id*string

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

curl -X PUT "https://example.com/v1/participants/string/business-card" \  -H "Content-Type: application/json" \  -d '{}'
{}
GET
/v1/participants/{id}/capabilities

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Path Parameters

id*string

Response Body

application/json

application/json

curl -X GET "https://example.com/v1/participants/string/capabilities"
{}
POST
/v1/participants/{id}/capabilities

{documentTypeId, processId, direction, allowUnvalidated} with canonical Peppol identifiers, or a document type alias as on POST /v1/messages: {type: "invoice", direction} — or the alias as documentTypeId with no processId. Stored canonical; an unknown alias, or a type that contradicts the ids given with it, is 400 VALIDATION_FAILED.

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Path Parameters

id*string

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

curl -X POST "https://example.com/v1/participants/string/capabilities" \  -H "Content-Type: application/json" \  -d '{}'
{}
DELETE
/v1/participants/{id}/capabilities/{capability}

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Path Parameters

id*string
capability*string

Response Body

application/json

application/json

curl -X DELETE "https://example.com/v1/participants/string/capabilities/string"
{}
POST
/v1/participants/{id}/migration

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Path Parameters

id*string

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

curl -X POST "https://example.com/v1/participants/string/migration" \  -H "Content-Type: application/json" \  -d '{}'
{}
GET
/v1/passkeys

{passkeys:[{id, name, aaguid, transports, createdAt, lastUsedAt}]} for the signed-in user. An API key has no passkeys and gets 403: a passkey belongs to a person, not to an account.

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Response Body

application/json

application/json

curl -X GET "https://example.com/v1/passkeys"
{}
POST
/v1/passkeys

{name, id, attestationObject, clientDataJSON, transports}, the binary members base64url. The challenge must be one issued to this same user, and must not have been used already.

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

curl -X POST "https://example.com/v1/passkeys" \  -H "Content-Type: application/json" \  -d '{}'
{}
POST
/v1/passkeys/challenge

Returns {publicKey}: PublicKeyCredentialCreationOptions for the signed-in user, with their existing credentials in excludeCredentials so one authenticator cannot hold two passkeys for the same account.

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Response Body

application/json

application/json

curl -X POST "https://example.com/v1/passkeys/challenge"
{}
DELETE
/v1/passkeys/{id}

Removing the last one is allowed and needs no confirmation: every user always has a password, so a passkey is an alternative sign-in method rather than the only one.

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Path Parameters

id*string

Response Body

application/json

application/json

curl -X DELETE "https://example.com/v1/passkeys/string"
{}
GET
/v1/sftp/connection

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Response Body

application/json

application/json

curl -X GET "https://example.com/v1/sftp/connection"
{}
GET
/v1/sftp/logins

A credential confined to particular companies sees only the logins belonging to them; a login with no company reaches the whole account and is visible only to credentials that do too. ?companyId narrows to one company; an account-wide login stays in view whichever company is asked for.

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Response Body

application/json

application/json

curl -X GET "https://example.com/v1/sftp/logins"
{}
PATCH
/v1/sftp/logins/{id}

{resetPassword: true} and nothing else: the new password is in the response, once. Changing the authentication method, disabling a login, setting a password directly or moving it between companies is 400 OPERATOR_MANAGED — ask us instead. Owner or company admin, the latter only for a login belonging to one of its own companies.

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Path Parameters

id*string

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

curl -X PATCH "https://example.com/v1/sftp/logins/string" \  -H "Content-Type: application/json" \  -d '{}'
{}
GET
/v1/sso/identities

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Response Body

application/json

application/json

curl -X GET "https://example.com/v1/sso/identities"
{}
POST
/v1/sso/identities

Session principal or API key. For a session, the token's email must match the caller's own account email or the request is refused with 400 SSO_EMAIL_MISMATCH — an API key has no user to match against and keeps the unrestricted automation path.

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

curl -X POST "https://example.com/v1/sso/identities" \  -H "Content-Type: application/json" \  -d '{}'
{}
DELETE
/v1/sso/identities/{id}

An owner may remove any identity in the account; any other signed-in user only one whose email is their own (403 FORBIDDEN otherwise); an API key keeps the account-wide automation path.

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Path Parameters

id*string

Response Body

application/json

application/json

curl -X DELETE "https://example.com/v1/sso/identities/string"
{}
GET
/v1/sso/status

Unauthenticated: {configured, provider, issuer, passkeysEnabled}, so a sign-in page can show or hide the single sign-on button, and label it with the right provider name, before anyone has signed in.

Response Body

application/json

application/json

curl -X GET "https://example.com/v1/sso/status"
{}
GET
/v1/stream

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Response Body

application/json

application/json

curl -X GET "https://example.com/v1/stream"
{}
POST
/v1/stream-tickets

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Response Body

application/json

application/json

curl -X POST "https://example.com/v1/stream-tickets"
{}
GET
/v1/usage

Metered events for the caller's account in [from, to) (default: the last 24 hours): totals per kind and the same per company, for re-billing. ?companyId narrows to one company the principal reaches; format=csv exports accountId,companyId,kind,count.

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Response Body

application/json

application/json

curl -X GET "https://example.com/v1/usage"
{}
GET
/v1/users

Owner or company admin. An owner sees every user under the account; a company admin sees only the users whose companies lie entirely inside its own, plus itself. ?companyId narrows to the users who reach that company; a user with no confinement reaches every company and stays in view. Every user carries status ("active" | "inactive"); a deactivated one also carries disabledAt, and erasableAt once it can be erased. Only active users are listed unless ?include=inactive; any other value is 400 VALIDATION_FAILED. Every user carries grants, the rights stored for them on top of their role (today only "billing.view"); it is always an array, and always empty for an owner, who holds every grant implicitly.

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Response Body

application/json

application/json

curl -X GET "https://example.com/v1/users"
{}
POST
/v1/users

Owner or company admin. role is one of owner, company_admin, member. companyIds confines the user to those companies; from an owner, empty means every company, an owner can never be confined, and a company admin must name at least one. A company admin may create only a member or another company admin, only within its own companies, and an omitted companyIds then means its own companies rather than the whole account. Leave password out: a random one is generated, the user is flagged mustChangePassword, and a one-time set-password link is emailed. If mail is not available, temporaryPassword comes back once instead, for you to hand over another way.

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

curl -X POST "https://example.com/v1/users" \  -H "Content-Type: application/json" \  -d '{}'
{}
PATCH
/v1/users/{id}

Changing your own password requires currentPassword: wrong is 400 CURRENT_PASSWORD_INVALID, missing is 400 CURRENT_PASSWORD_REQUIRED. It is not required when an owner resets somebody else's. Setting your own password while signed in through single sign-on is refused with 403 PASSWORD_CHANGE_NOT_ALLOWED_FOR_SSO_SESSION. On your own id this is one of the two routes still reachable while mustChangePassword is true, and setting a real password clears the flag without a fresh sign-in. role and companyIds need an owner, or a company admin acting on someone else — never on your own row, so nobody widens their own access. companyIds replaces the user's confinement; omitted leaves it alone, and from an owner, empty means every company. Promoting someone to owner always clears it. A change takes effect on the user's next request. resendInvite is the sibling of resetPassword for a user who never set a password: it mails a 48-hour invitation link rather than the one-hour reset link. grants replaces the user's grants — today only "billing.view", the right to read GET /v1/billing; omitted leaves them alone and [] takes them all away. Only an owner gives grants (403 FORBIDDEN for a company admin, a member and every API key); an owner holds them all implicitly, so a non-empty grants on an owner, and an unknown grant, are 400 VALIDATION_FAILED.

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Path Parameters

id*string

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

curl -X PATCH "https://example.com/v1/users/string" \  -H "Content-Type: application/json" \  -d '{}'
{}
DELETE
/v1/users/{id}

Owner or company admin, the latter only for a user inside its own companies. The first call deactivates: sign-in is refused, sessions and passkeys are deleted, and status becomes "inactive". The last active owner cannot be deactivated (409 LAST_OWNER), nor can you deactivate yourself (400 VALIDATION_FAILED). After the user has stayed deactivated for the grace period — 30 days — a second call erases them permanently instead; 409 USER_NOT_DISABLED, ERASURE_TOO_SOON and ERASURE_NOT_CONFIGURED name the cases where it will not. POST /v1/users/{id}/enable reverses a deactivation.

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Path Parameters

id*string

Response Body

application/json

application/json

curl -X DELETE "https://example.com/v1/users/string"
{}
POST
/v1/users/{id}/enable

Owner or company admin, the latter only for a user inside its own companies. The user signs in again with the password they already had and keeps the companies they had; deactivation never touched either. Passkeys do not come back — deactivation deleted them — so a new one has to be registered. API keys are unaffected either way: a key belongs to the account, not to whoever created it, and is revoked on its own. Reactivating an already-active user is not an error.

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Path Parameters

id*string

Response Body

application/json

application/json

curl -X POST "https://example.com/v1/users/string/enable"
{}
POST
/v1/validate

Runs a document through the same Peppol validation an outbound message gets — XML safety, then XSD and Schematron in the validator — and answers every finding, warnings included. Nothing is stored and nothing is sent. Body {type?, documentTypeId?, processId?, payload} or, for a document over the 1 MiB JSON body limit, {companyId?, payloadObjectKey} naming an upload from POST /v1/messages/uploads; the upload is read and left in place, so the same key can be sent next. type is an alias ("invoice", …); with neither type nor ids the identifiers are read off the document (root element, CustomizationID, ProfileID). A stated type the document contradicts (as POST /v1/messages would refuse with 400) is answered here as the first finding — severity fatal, rule DOCUMENT_TYPE_MISMATCH, no layer — followed by the validator's own, with valid false. Answers {valid, documentTypeId, processId, type, findings: [{severity, rule, location, message, layer}]}: valid is false only on a fatal finding; layer "schema" means the document is not valid UBL and Schematron did not run. 400 PEPPOL_DOCUMENT_NOT_SUPPORTED for a type without artefacts, 400 PEPPOL_VALIDATION_FAILED for unsafe XML, 413 PAYLOAD_TOO_LARGE for an upload over PAYLOAD_MAX_BYTES, 404 for a company the credential does not reach, 503 VALIDATION_UNAVAILABLE while the validator is down. Rate limited per account.

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

curl -X POST "https://example.com/v1/validate" \  -H "Content-Type: application/json" \  -d '{}'
{}
GET
/v1/webhooks

A credential confined to particular companies sees only the endpoints belonging to them. An endpoint with no company receives events for the whole account and is visible only to credentials that reach it. ?companyId narrows to one company; an account-wide endpoint stays in view whichever company is asked for.

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Response Body

application/json

application/json

curl -X GET "https://example.com/v1/webhooks"
{}
POST
/v1/webhooks

Owner, company admin, or an API key with manage. companyId confines the endpoint to one company's documents; leaving it out sends every company under the account to it, which only an owner or an account-wide key may create. A key confined to one company manages that company's endpoints only. Payloads carry accountId and companyId, so one endpoint can serve many companies. The signing secret is returned once, here.

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

curl -X POST "https://example.com/v1/webhooks" \  -H "Content-Type: application/json" \  -d '{}'
{}
PUT
/v1/webhooks

Same body and access as POST /v1/webhooks, made idempotent for a platform that registers its endpoint on every deploy. An endpoint with the same URL and companyId is updated in place — enabled, timeoutMs, and the secret only when one is given — and answered 200 with created: false and no secret. Otherwise one is created and answered 201 with created: true and the signing secret, exactly as POST. A URL that changes is a new endpoint; delete the old one.

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

curl -X PUT "https://example.com/v1/webhooks" \  -H "Content-Type: application/json" \  -d '{}'
{}
GET
/v1/webhooks/deliveries

Deliveries for the principal's account (companyFilter applies). Filter with ?status, ?companyId, ?from, ?to; page with ?cursor and ?limit. Returns {deliveries, nextCursor?}.

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Query Parameters

cursor?string
limit?string
from?string
to?string
direction?string
status?string

Response Body

application/json

application/json

curl -X GET "https://example.com/v1/webhooks/deliveries"
{}
PATCH
/v1/webhooks/{id}

Owner, company admin, or an API key with manage — a company admin or a company-confined key only for an endpoint belonging to its own companies. rotateSecret issues a new signing secret and returns it once — deliveries signed with the old one are already gone, so update your verifier at the same time.

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Path Parameters

id*string

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

curl -X PATCH "https://example.com/v1/webhooks/string" \  -H "Content-Type: application/json" \  -d '{}'
{}
DELETE
/v1/webhooks/{id}

Owner, company admin, or an API key with manage, with the same rule about which endpoints it reaches as PATCH.

Authorization

customerAuth
AuthorizationBearer <token>

Customer session (pus_) or API key (pap_). X-API-Key is also accepted.

In: header

Path Parameters

id*string

Response Body

application/json

application/json

curl -X DELETE "https://example.com/v1/webhooks/string"
{}