Posted Payments

Posted payments represent completed payment transactions in the system. They include details about the payment source and the actual transfer of funds.

A posted payment can also be a draft: a proposal (typically from Snapbooks automation for a bank transaction) that stores the transfer, account entries and reconciliation entries but has not been booked. A draft creates no journal entry, no reconciliation and no document status change, and its transfer stays at payment_status NONE until it is booked with PATCH /payments/{id}.

Endpoints

Method Endpoint Description
GET /payments List posted payments
GET /payments/{id} Get a specific posted payment
POST /payments Create a posted payment (or a draft with "is_draft": true)
PATCH /payments/{id} Book a draft: body is exactly {"is_draft": false}
DELETE /payments/{id} Discard a draft (booked payments cannot be deleted; reverse their journal entry instead)

Query Parameters

The GET /payments endpoint supports the following query parameters:

Parameter Type Required Description
client_account_id integer Yes Filter by client account ID. Must be one of the user’s eligible client accounts
source_type string No Filter by a single source type. The values Snapbooks sets are PAYMT (the payment posts a bank transaction) and PAYRQ (it books what a payment request paid). Comma-separated lists are not split — send one value
source_id integer No Filter by source ID — the bank transaction or payment request ID, matching source_type
is_draft boolean No true returns drafts only, false booked payments only; omitted returns both
with string No Comma-separated nested relations to expand on the transfer. Supported: debtor.bank_account, debtor.business_partner, creditor.bank_account, creditor.business_partner. Note the paths have no transfer. prefix

This endpoint does not paginate on request: it always answers with the first 100 payments in the data/meta envelope, and page and per_page are ignored. Narrow the result with source_type/source_id instead — a payment of a bank transaction is found with source_type=PAYMT&source_id=<transaction id>.

Create Posted Payment

POST /api/v2/payments

Creates a posted payment (or, with "is_draft": true, a draft proposal — see the note on drafts above).

Request Body

Field Type Required Description
client_account_id integer Yes The client account the payment belongs to
source_type string No The type of source that generated this payment — PAYMT for a bank transaction, PAYRQ for a payment request
source_id integer No The ID of the source
source_line_id integer No The ID of the source line item
memo string No Description or memo for the payment
is_draft boolean No Create as an unbooked draft instead of posting immediately (default: false)
transfer object Yes The transfer to create. Accepts the same fields as Payment Transfer, plus debtor.bank_account_id, creditor.bank_account_id, reconciliation_entries, and account_entries

Any other field in the body is ignored rather than rejected.

Example Request

{
  "client_account_id": 1,
  "source_type": "PAYMT",
  "source_id": 4821,
  "memo": "Invoice payment",
  "transfer": {
    "payment_type": "CREDIT_TRANSFER",
    "amount": "1000.00",
    "currency_code": "EUR",
    "requested_transfer_date": "2023-05-11",
    "debtor": { "bank_account_id": 1 },
    "creditor": { "bank_account_id": 2 },
    "reconciliation_entries": [
      {
        "relation_type": "ARINV",
        "relation_id": 123,
        "amount": "1000.00",
        "currency_code": "EUR"
      }
    ]
  }
}

Validation

Every payment — draft or booked — is checked for a consistent graph:

  • account_entries are either all numbered with line_number or none are, and the numbers must be unique.
  • When they are numbered, a tax_for_line must name a different entry’s line_number, and that entry must carry a tax_code_id. The tax entry inherits that tax code and is marked tax_line.

Booking a payment of a bank transaction (source_type PAYMT) — a POST without is_draft, or a PATCH that books a draft — is additionally checked against the current books:

  • The transfer amount must fit what is left to post of the bank transaction, so partial payments are allowed but overposting is not. A transaction that is already fully posted is rejected.
  • Every document named by a reconciliation_entries entry must still be live and open. A deleted document, or one already settled, is rejected.

Payments with source_type PAYRQ are exempt from the last two checks — they book what the payment request already paid.

Response

Returns the created posted payment with status 201 Created. See Attributes below.

Attributes

Attribute Type Description
id integer The unique identifier of the posted payment
client_account_id integer The ID of the client account
source_type string The type of source that generated this payment
source_id integer The ID of the source
source_line_id integer The ID of the source line item
memo string Description or memo for the payment
is_draft boolean true while the payment is an unbooked proposal
created_by_id integer The user who created the payment; 2 is Snapbooks automation
updated_by_id integer The user who last changed the payment, for example the one who booked a draft
human_touched_at datetime When a human last created or edited the payment; null if only automation has touched it
transfer PaymentTransfer The transfer details

Payment Transfer

Each posted payment includes a transfer with the following attributes:

Attribute Type Description
id integer The unique identifier of the transfer
created_at datetime When the transfer was created
created_by_id integer The ID of the user who created the transfer
requested_transfer_date date The requested date for the transfer
payment_type string The type of transfer (CREDIT_TRANSFER, SALARY, TAX, INTERCOMPANY, MIXED)
payment_status string The current status of the transfer
rejection_code string Code indicating reason for rejection, if applicable
rejection_message string Detailed message about rejection, if applicable
booking_date date The date the transfer was booked
booking_reference string Reference number for the booking
amount decimal The transfer amount
currency_code string The currency code of the transfer
exchange_rate decimal The exchange rate used
creditor_reference string Reference for the creditor

The transfer also always carries debtor and creditor objects, and the reconciliation_entries and account_entries arrays below — none of them need with.

Payment Reconciliation Entry

What the transfer settles: one entry per document (or document line) the payment pays. transfer.reconciliation_entries is always present, empty when the payment settles nothing.

Attribute Type Description
id integer The unique identifier of the entry
created_at datetime When the entry was created
created_by_id integer The ID of the user who created the entry
relation_type string Object type of what is settled, e.g. ARINV, APINV, ARCRN, APCRN
relation_id integer ID of the settled object
relation_line_id integer ID of the settled line on that object, when the payment settles a single line
amount decimal The amount applied to that object
currency_code string Currency of amount
payment_status string Status of this entry; see Payment Status Values
rejection_code string Code indicating why the entry was rejected, if applicable
rejection_message string Detailed rejection message, if applicable

Payment Account Entry

How the transfer is posted: the general-ledger side of the payment, used when the payment is not a plain settlement of a document (a fee withheld, an interest charge, a tax line). transfer.account_entries is always present, empty when the payment needs no extra ledger lines.

Attribute Type Description
id integer The unique identifier of the entry
created_at datetime When the entry was created
created_by_id integer The ID of the user who created the entry
transfer_id integer The transfer the entry belongs to
line_number integer Caller-assigned line number used by tax_for_line. Number every entry of a transfer or none
account_code string The GL account to post to
debit decimal Debit amount
credit decimal Credit amount
currency_code string Currency of debit/credit
tax_code_id integer The tax code applied to the entry
tax_base_amount decimal Net amount the tax is computed from
tax_amount decimal The tax amount
tax_line boolean true when the entry is the tax line of another entry
tax_for_line integer The line_number of the entry this tax line belongs to
memo string Description for the posting
dimensions PaymentAccountEntryDimension Dimensions to post the entry against

Payment Account Entry Dimension

Attribute Type Description
dimension_id integer The dimension to post against
relation_type string Object type of the dimension value: project, department, businesspartner
relation_id integer ID of the dimension value — the project, department or business partner

Relationships

These are only present when requested with with (no transfer. prefix — with=debtor.bank_account).

Relationship Type Description
transfer.debtor.bank_account BankAccount The debtor’s bank account
transfer.debtor.business_partner BusinessPartner The business partner owning the debtor bank account
transfer.creditor.bank_account BankAccount The creditor’s bank account
transfer.creditor.business_partner BusinessPartner The business partner owning the creditor bank account

Example Response

Requested with ?with=debtor.bank_account,debtor.business_partner,creditor.bank_account,creditor.business_partner — without with, debtor and creditor hold only the two *_id fields.

{
    "id": 1,
    "client_account_id": 1,
    "source_type": "PAYMT",
    "source_id": 4821,
    "source_line_id": null,
    "memo": "Invoice payment",
    "is_draft": false,
    "created_by_id": 1,
    "updated_by_id": null,
    "human_touched_at": "2023-05-11T09:14:00+00:00",
    "transfer": {
        "id": 1,
        "created_at": "2023-05-10T12:00:00+00:00",
        "created_by_id": 1,
        "requested_transfer_date": "2023-05-11",
        "payment_type": "CREDIT_TRANSFER",
        "payment_status": "COMPLETED",
        "rejection_code": null,
        "rejection_message": null,
        "booking_date": "2023-05-11",
        "booking_reference": "REF123",
        "amount": 1000.0,
        "currency_code": "EUR",
        "exchange_rate": 1.0,
        "creditor_reference": "INV123",
        "debtor": {
            "bank_account_id": 1,
            "business_partner_id": 1,
            "bank_account": {
                "id": 1,
                "account_number": "DE89370400440532013000"
            },
            "business_partner": {
                "id": 1,
                "name": "ACME Corp"
            }
        },
        "creditor": {
            "bank_account_id": 2,
            "business_partner_id": 2,
            "bank_account": {
                "id": 2,
                "account_number": "FR7630006000011234567890189"
            },
            "business_partner": {
                "id": 2,
                "name": "Supplier Inc"
            }
        },
        "reconciliation_entries": [
            {
                "id": 11,
                "created_at": "2023-05-10T12:00:00+00:00",
                "created_by_id": 1,
                "relation_type": "APINV",
                "relation_id": 123,
                "relation_line_id": null,
                "amount": 1000.0,
                "currency_code": "EUR",
                "payment_status": "COMPLETED",
                "rejection_code": null,
                "rejection_message": null
            }
        ],
        "account_entries": []
    }
}

Booking and discarding drafts

PATCH /payments/{id} accepts only {"is_draft": false}. Booking re-validates the proposal against the current ledger and then posts it exactly like a direct POST: the journal entry, reconciliation, external reconciliation and document status changes are created at that point, and created_by_id is kept, so a draft booked unchanged stays attributed to automation. Responses:

Status Meaning
200 Booked (or already booked; the call is idempotent)
400 Unsupported field, is_draft: true on a booked payment, or the proposal does not validate
409 The proposal is outdated (the bank transaction was settled another way, or a referenced document or bank account changed). The draft has been removed

DELETE /payments/{id} removes a draft and returns 204. It returns 400 for a booked payment.

Undoing a booked payment

A booked payment cannot be deleted — reverse its journal entry instead (see Journal Entries). Reversing it does more than remove the postings: the payment’s transfer and every reconciliation entry move to payment_status CANCELLED, and each document the payment settled is reopened from what is still open of it, so the bank transaction and the documents can be paid again.

Payments sourced from a payment request (source_type PAYRQ) are the exception: the request, not the payment, settled those documents, so a reversal leaves their status alone.

Notes

  • The client_account_id must be one of the eligible client accounts for the authenticated user.
  • When creating a posted payment, the created_by_id is automatically set to the authenticated user’s ID.
  • The source_type and source_id fields track the origin of the payment — the bank transaction or payment request it posts.
  • Bank account and business partner relations are expanded with the with query parameter (with=debtor.bank_account), not with_relations.
  • Amounts are returned as JSON numbers, not strings. Send them as numbers or as decimal strings; both are accepted on write.
  • Dates are ISO 8601 (YYYY-MM-DD); timestamps are ISO 8601 with a UTC offset (2023-05-10T12:00:00+00:00).
  • The source_type filter accepts a single value; it is not a list parameter.