Skip to Content
API OverviewPaymentsSigning Baskets & Drafts (Beta)

Signing Baskets & Drafts

This API is in beta and may change based on feedback. The endpoints, request/response shapes, and behavior described here are subject to revision in future releases. We welcome your input — please open an issue  on GitHub to share feedback or report problems.

0.6.0-beta introduced a breaking rename of the draft and signing-basket status enums to align them with the underlying payment status vocabulary — see the changelog for the full before/after mapping. Signing basket behavior is unchanged in 0.7.0-beta.

Payment Type Roadmap

Draft support is being rolled out per payment type. The table below shows the current status and planned timeline.

Payment TypeStatusDetails
SE Bank GiroAvailableFully supported
SE Plus GiroAvailableFully supported
Domestic Credit TransferPlannedExpected in the coming months
DK Payment Slip (GIRO/FI)PlannedFurther in the future

Drafts

Drafts provide a staged payment preparation workflow. Instead of submitting a fully populated payment in a single request, you can:

  1. Create a draft with only a name
  2. Update the draft incrementally as payment details become available
  3. Approve the draft through the POST /payments/drafts/approve endpoint, which initiates the actual payment

Drafts do not have individual approve endpoints — all approval goes through the general Signing Baskets batch approval flow, even when approving a single draft. This is useful when payment details are collected across multiple steps or screens in your application.

Draft Lifecycle

Draft Endpoints

Each supported payment type has its own set of draft endpoints under /payments/drafts/{payment-type}/. The operations are the same across payment types, but the required fields and validation rules differ depending on the payment type (see the payment-type-specific sections below).

OperationMethodPathDescription
Create draftPOST/payments/drafts/{payment-type}Create a new draft
Get draftGET/payments/drafts/{payment-type}/{id}Get draft details and validation status
Update draftPATCH/payments/drafts/{payment-type}/{id}Partially update a draft
Delete draftDELETE/payments/drafts/{payment-type}/{id}Delete a draft in CREATED status

Drafts are approved through the Signing Baskets batch approval flow.

All draft endpoints require the PSP_PI (Payment Initiation) scope.

Common Fields

Every draft has the following fields regardless of payment type:

FieldTypeRequired on CreateDescription
draftIdstringNoClient-provided draft identifier. Must be a valid UUID and must not already be used. Once used, a draftId cannot be reused — even after the draft has been deleted or executed. If omitted, the server generates one. Conflicts return 409 INVALID_STATE.
namestringYesDescriptive name for the draft (e.g. bill filename, receiver)

The redirectUrl for Strong Customer Authentication is supplied on the batch approve request, not on the draft itself.

Additional fields are payment-type-specific — see the sections below.

Draft Response

All draft endpoints return a response with this common structure:

FieldTypeAlways PresentDescription
draftIdstringYesUnique draft identifier
statusstringYesCurrent draft status (see Draft Statuses)
isValidbooleanYesWhether the draft has all required fields for approval
namestringYesDescriptive name for the draft
validationErrorsarrayNoPresent when isValid is false; describes why the draft isn’t approvable

Payment-type-specific fields (e.g. giroNumber, amount) are included when set.

The validationErrors array currently contains a single aggregated entry — not a per-field breakdown:

FieldTypeDescription
fieldstringAlways "draft" in the current API version
messagestringLocalized, human-readable reason the draft isn’t yet approvable (honors the X-Language header)

Example — single missing field (a SE Giro draft with no giroNumber):

{ "draftId": "1f4a9c3e-2b7d-4e8a-9f10-6c8b3d5e7f01", "status": "CREATED", "isValid": false, "name": "Invoice 123", "validationErrors": [ { "field": "draft", "message": "Giro number is missing" } ] }

Example — several fields missing (message is aggregated, not one entry per field):

{ "draftId": "1f4a9c3e-2b7d-4e8a-9f10-6c8b3d5e7f01", "status": "CREATED", "isValid": false, "name": "Invoice 123", "validationErrors": [ { "field": "draft", "message": "Several required fields are missing" } ] }

The exact message wording is subject to change and is localized via the X-Language header. Treat it as display text — do not parse it programmatically.

Draft Statuses

StatusDescription
CREATEDDraft is being prepared and can be updated or deleted. The draft also stays in CREATED while a basket approval is awaiting Strong Customer Authentication, and returns here when a previous payment attempt failed — paymentError is populated and the draft can be retried.
PENDINGApproval has succeeded and the resulting payment is in progress — covers awaiting clearing and awaiting settlement for future-dated payments.
COMPLETEDPayment has settled successfully.
DELETEDDraft has been deleted.

A draft can only be updated or deleted while in CREATED status. If approval or execution fails, the draft returns to CREATED status so it can be corrected and re-approved.

When a payment fails after approval, the draft returns to an approvable state and reappears in GET /payments/drafts with the paymentError field populated. This field contains a localized error message describing why the payment failed (honoring the X-Language header). The TPP can display this error to the user and allow them to re-approve the draft.

Error Handling

All draft endpoints return errors in a consistent format:

FieldTypeDescription
codestringMachine-readable error code
messagestringHuman-readable error message
errorsarrayList of validation errors

Common error codes:

StatusCodeDescription
400INVALID_DRAFT_IDThe provided draftId is not a valid UUID
400INVALID_EXECUTION_DATEdate is not in the expected format (YYYY-MM-DD or RFC3339)
403ACCOUNT_ACCESS_DENIEDThe caller does not have access to the senderAccountId supplied on create or update (see Sender Account Access)
404NOT_FOUNDDraft not found
409INVALID_STATEDraft is not in the expected state for this operation, or the supplied draftId is already in use on create
422Validation error in request body
500Internal server error

Errors specific to the batch approve flow (ALREADY_USED, MISSING_DRAFT_IDS, MISSING_REDIRECT_URL, TRANSFER_DENIED) are documented on the Approve Drafts endpoint.


SE Bank Giro & SE Plus Giro Drafts

SE Bank Giro and SE Plus Giro share the same draft fields and validation rules. The only difference is the endpoint path: /payments/drafts/se-bank-giro vs /payments/drafts/se-plus-giro.

Payment-Type Fields

In addition to the common fields, SE Giro drafts support:

FieldTypeDescription
giroNumberstringThe giro number of the recipient
ocrReferencestringOCR payment reference
amountstringPayment amount as decimal string (e.g. "100.00")
currencystringCurrency code (SEK)
senderAccountIdstringSender’s account UUID
messageToSenderstringNote to the debtor, visible on the final transaction
datestringExecution date (ISO-8601 date, YYYY-MM-DD)

All of these fields are optional at creation time and can be added later via PATCH.

Validation Requirements

A SE Giro draft must have the following fields populated before it can be approved (isValid: true):

FieldRequirement
giroNumberMust be non-empty
amountMust be a positive value
currencyMust be SEK
senderAccountIdMust be a valid account UUID

If any of these fields are missing or invalid, the draft response will include isValid: false and a validationErrors array describing what needs to be fixed.

Sender Account Access

When senderAccountId is supplied on create (POST) or update (PATCH), Lunar verifies that the caller has access to that account. Access is granted when any of the following holds:

  • The caller is the direct owner of the account (private accounts).
  • The caller is a party on the organisation that owns the account (organisation/business accounts).
  • The caller has been granted explicit shared access to the account by its owner.

If none of these apply, the request is rejected with 403 ACCOUNT_ACCESS_DENIED and no change is made to the draft.

Automatic clearing of senderAccountId

A draft’s senderAccountId can become unusable after the draft is created — for example when the account is closed, ownership changes, or a shared-access grant is revoked. When this happens, Lunar automatically clears senderAccountId on affected drafts that are still in CREATED status. The draft reverts to isValid: false with a validation error indicating the sender account is missing, and will continue to appear in GET /payments/drafts so the TPP can prompt the user to pick a new sender account before re-approving.

EventEffect on drafts
Shared account access revokedsenderAccountId cleared on drafts owned by the user whose access was revoked
Account ownership changedsenderAccountId cleared on drafts owned by the former owner
Account closedsenderAccountId cleared on all drafts referencing the closed account

Drafts that have already left CREATED status (PENDING, COMPLETED) are not modified by these events — only drafts still being prepared are cleared.

Example: SE Bank Giro Draft

Future payment types (e.g. Domestic Credit Transfer) will have their own fields and validation requirements. The draft lifecycle and endpoints follow the same pattern, but the specific fields will differ.


Signing Baskets (Batch Approval)

A signing basket lets you group multiple drafts — potentially across different payment types — and approve them all at once through a single authorization flow. This is particularly valuable when a user needs to pay several invoices in one go: each invoice is a separate draft, and the batch approval triggers a single authentication challenge for the entire set.

Signing Basket Flow

List Drafts

GET /payments/drafts

Returns all drafts for the current user that are still approvable, across all payment types that support drafts (see the Payment Type Roadmap). Drafts in CREATED are returned — including drafts that are not yet valid (isValid: false) and drafts whose previous payment failed and are awaiting retry (paymentError set). Drafts in PENDING, COMPLETED, or DELETED are excluded.

Pass the X-Language header (supported values: en, sv, da, nb) to receive localized values for invalidReason and paymentError, and for the fallback name when a draft was created without one. TPP-supplied name values are returned as-is. Defaults to en if omitted or unrecognized.

Response:

FieldTypeDescription
draftsarrayList of draft summaries
totalCountintegerNumber of drafts returned in this response. The endpoint returns all approvable drafts for the current user in one response — there is no pagination

Each draft in the list contains:

FieldTypeDescription
draftIdstringUnique draft identifier
isValidbooleanWhether the draft is ready for approval
invalidReasonstringLocalized reason why the draft is invalid (present when isValid is false)
namestringDescriptive name
accountIdstringAssociated account ID (if set)
paymentErrorstringLocalized error from a failed payment attempt (present when a previous approval failed and the draft is re-approvable)

Approve Drafts (Batch)

POST /payments/drafts/approve

Approves and initiates execution of one or more drafts. A unique requestId is required to track the approval — it can be used to poll for status afterward.

FieldTypeRequiredDescription
requestIdstringYesUnique UUID for this approval request
draftIdsstring[]YesArray of draft IDs to approve
redirectUrlstringYesURL the PSU is redirected to after completing Strong Customer Authentication

The request is validated before any approval is initiated. If any draft ID in draftIds does not exist, the entire request is rejected with a 404. No drafts are approved in this case — ensure all draft IDs are valid before submitting the batch.

Response:

FieldTypeDescription
requestIdstringThe request ID
approvedDraftIdsstring[]Draft IDs that were actually approved
statusstringOverall approval status (see Approval Status)
authUrlstringAuthorization URL the PSU should be redirected to (present when the approval requires an SCA challenge)

When authUrl is present, the TPP can redirect the PSU to it to complete Strong Customer Authentication. After the challenge, the PSU is redirected back to the redirectUrl supplied on the approve request (see Post-SCA Redirect).

StatusCodeDescription
400MISSING_DRAFT_IDSNo draft IDs provided
400MISSING_REDIRECT_URLredirectUrl is missing from the request body
400TRANSFER_DENIEDApproval was denied (e.g. account restrictions)
404NOT_FOUNDOne or more drafts not found — entire request rejected, no drafts approved
409INVALID_STATENone of the drafts are in an approvable state — e.g. already approved/executing, or not yet valid for approval (isValid: false)
409ALREADY_USEDThe requestId has already been used for a previous approval request

The INVALID_STATE and ALREADY_USED errors are both returned as 409 Conflict but with different error codes. ALREADY_USED includes a field: "requestId" in the validation errors array to indicate the source of the conflict.

Post-SCA Redirect

The batch approval flow follows the same authorization pattern as single-payment endpoints: the TPP redirects the PSU to an authorization URL, the PSU completes Strong Customer Authentication, and the PSU is then redirected back to a TPP-owned URL.

The redirectUrl is scoped to the approval request — each batch approve supplies its own single URL that the PSU is sent back to after completing SCA for that batch.

authUrl is only present in the response when an SCA challenge is required. If approval completes without a challenge, no redirect is needed and the authUrl field is omitted.

Get Approval Status

GET /payments/drafts/approve/{requestId}/status

Returns the current status of a batch approval request, including per-draft progress.

Response:

FieldTypeDescription
statusstringOverall approval status (see Approval Status)
draftsarrayPer-draft status details

Each entry in drafts contains:

FieldTypeDescription
draftIdstringDraft identifier
statusstringOne of CREATED, PENDING, COMPLETED, FAILED (see Per-Draft Status)
errorMessagestringLocalized error message (present when status is FAILED; honors X-Language)
paymentIdstringResulting payment ID (present when status is PENDING or COMPLETED)
expectedExecutionDatestringDate the executing service expects to post the payment, in ISO 8601 YYYY-MM-DD form. Present when status is PENDING and an execution date was supplied; omitted otherwise.

Errors:

StatusCodeDescription
404NOT_FOUNDNo approval request exists for the given requestId

The paymentId is first surfaced when a draft reaches PENDING and remains on the response through COMPLETED. It can be used to look up the payment via its corresponding payment endpoint (e.g. GET /payments/se-bank-giro/ {paymentId}). The approval status response does not include the payment type — your application must track which payment type each draft belongs to in order to query the correct endpoint.

Per-Draft Status

StatusDescription
CREATEDDraft is queued for approval — either the PSU has not yet completed SCA, or a previous payment attempt failed (errorMessage populated) and the draft is awaiting retry.
PENDINGPayment has been accepted for execution by the underlying payment service. paymentId is set; expectedExecutionDate may be set when the payment is scheduled for a future date.
COMPLETEDPayment has settled successfully.
FAILEDPayment could not be completed; errorMessage describes the reason and the draft returns to CREATED for retry.

Approval Status

The overall approval status reflects the state of the authorization challenge:

StatusDescription
AWAITING_APPROVALChallenge has been created; waiting for user authorization
APPROVEDAll drafts have been approved and payments initiated
EXPIREDThe authorization challenge expired before completion
DECLINEDThe user declined the authorization challenge

Cross-layer Status Mapping

0.6.0-beta aligns the three status vocabularies that show up across a single signing-basket flow so a TPP only needs to learn one. All three layers use the same uppercase values, with controlled collapsing where the layers don’t have a 1:1 correspondence.

  • Draft (DraftResponse.status) — the lifecycle of an individual draft (GET /payments/drafts/{type}/{id}). Values: CREATED, PENDING, COMPLETED, DELETED. The draft stays in CREATED while the basket approval is awaiting SCA and only transitions to PENDING once approval has succeeded — PENDING therefore covers awaiting clearing and awaiting settlement for future-dated payments, never awaiting SCA.
  • Per-draft basket (DraftApprovalStatus.status) — the per-draft entry inside a basket approval response (GET /payments/drafts/approve/{requestId}/status). Values: CREATED, PENDING, COMPLETED, FAILED. Mirrors the draft layer, with FAILED as a side path when the underlying payment couldn’t be completed (the draft itself recycles back to CREATED so it can be retried).
  • Basket overall (ApprovalStatus) — the overall approval/SCA challenge status returned by both POST /payments/drafts/approve and the status endpoint. Values: AWAITING_APPROVAL, APPROVED, EXPIRED, DECLINED. Models the SCA challenge itself, not the resulting payments.
  • Underlying payment (e.g. SeBankGiroResponse.status) — the eventual payment’s lifecycle, looked up via the payment-type-specific endpoint. Values: AWAITING_APPROVAL, PENDING, APPROVED, COMPLETED, FAILED, DECLINED, CANCELLED. The fully detailed view — drafts only surface the post-approval lifecycle (the underlying payment’s PENDING and COMPLETED); AWAITING_APPROVAL and APPROVED on the payment never appear at the draft layer because the draft is only handed off for execution after the basket has already been approved. Same-day vs future-dated transitions and explicit cancellation surface only on the payment endpoint.
Last updated on