Skip to Content
API OverviewPaymentsSigning Baskets & Drafts

Signing Baskets & Drafts

Payment Type Roadmap

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

Payment TypeStatusDetails
Domestic Credit TransferAvailableFully supported
SE Bank GiroAvailableFully supported
SE Plus GiroAvailableFully supported
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 — name is always required. Domestic credit transfer drafts also require a client-provided draftId; SE Giro drafts may omit draftId (the server generates one).
  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.

PATCH only updates fields that are present in the request body with non-empty values. Omitted fields are left unchanged. Sending an empty string does not clear an existing value — supply a new non-empty value to change a field.

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
draftIdstringVariesClient-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. Required for domestic credit transfer drafts; optional for SE Giro drafts (server generates one if omitted). Conflicts return 409 DRAFT_ALREADY_EXISTS.
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
codestringMachine-readable validation error code (returned by domestic credit transfer drafts)
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" } ] }

Use code for programmatic branching. Use message for user-facing display — it is localized via the X-Language header and the exact wording may change. Domestic credit transfer drafts return code; other draft types may omit it for now.

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
409DRAFT_ALREADY_EXISTSA draft with the supplied draftId already exists (including after delete — the ID remains reserved)
409INVALID_STATEDraft is not in the expected state for update, delete, or other lifecycle operations
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.

Sender Account Access

When senderAccountId is supplied on create (POST) or update (PATCH) for any draft type that supports it (domestic credit transfer and SE Giro), 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.


Domestic Credit Transfer Drafts

Domestic credit transfer drafts use /payments/drafts/domestic-credit-transfer. They follow the same draft lifecycle and are approved through signing baskets.

In addition to the common fields, domestic credit transfer drafts support:

FieldTypeDescription
receiverAccountBbanstringReceiver account number (BBAN). 11–14 characters.
receiverNamestringName of the receiver. Required for instant transfers in Denmark (all Lunar DK transfers are instant).
amountstringPayment amount as decimal string (e.g. "100.00"). Must be positive.
currencystringCurrency code: DKK, SEK, or NOK.
senderAccountIdstringSender’s account UUID.
executionDatestringExecution date (ISO-8601 date, YYYY-MM-DD). Defaults to earliest possible when omitted on approval.
senderMessagestringTransaction title for the sender (visible on the sender’s account). Max length: 140 characters (DK, SE, NO).
shortReceiverMessagestringMessage to the recipient. Max length by currency: DK 140 characters (instant; all Lunar DK transfers are instant), SE 12 characters, NO 70 characters. Danish non–banking-services accounts may be limited to 12 characters at validation. Same limits as message on direct initiation.

All payment fields except draftId and name are optional at creation time and can be added later via PATCH. See Sender Account Access when setting senderAccountId.

Migrating from POST /payments/domestic-credit-transfer? See Migration from direct DCT to drafts for the field mapping table and before/after flow.

Validation Requirements

Before a domestic credit transfer draft can be approved, it must pass validation. Every create, update, and get response includes isValid — use it to decide whether the draft is ready for POST /payments/drafts/approve.

When isValid is false, read validationErrors[0].code for programmatic handling and validationErrors[0].message for display. Pass X-Language (en, sv, da, nb) to localize the message; defaults to en. See Draft Response for the full response shape.

Minimum fields — all four must be populated before Lunar runs full domestic credit transfer validation (same rules as direct initiation):

FieldRequirement
receiverAccountBbanMust be non-empty
amountMust be a positive decimal string
currencyMust be DKK, SEK, or NOK
senderAccountIdMust be a valid account UUID

If any minimum field is missing, isValid is false and validationErrors describes that required fields are still missing.

Full DCT validation runs once all minimum fields are present. The same rules as direct initiation apply, including:

FieldRequirement
receiverNameRequired for instant transfers in Denmark (all Lunar DK transfers are instant)
senderMessage, shortReceiverMessage, executionDateOptional at creation, but validated when supplied

Example — minimum fields not yet populated:

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

Example — full validation ran but a value is invalid:

{ "draftId": "1f4a9c3e-2b7d-4e8a-9f10-6c8b3d5e7f01", "status": "CREATED", "isValid": false, "name": "Rent May", "receiverAccountBban": "11112222333344", "amount": "100.00", "currency": "DKK", "senderAccountId": "acc-123", "validationErrors": [ { "field": "draft", "code": "INVALID_RECEIVER_ACCOUNT", "message": "The receiver account is invalid" } ] }

Validation error codes — domestic credit transfer drafts return a single aggregated entry with field: "draft". The code identifies the reason. Only codes the TPP can resolve by updating draft fields are listed:

CodeWhen returned
MISSING_REQUIRED_FIELDSOne or more of receiverAccountBban, amount, currency, senderAccountId is missing
INVALID_AMOUNTAmount is not a valid positive number
INVALID_CURRENCYCurrency is not DKK, SEK, or NOK
INVALID_RECEIVER_ACCOUNTReceiver BBAN is invalid or not found
INVALID_SENDER_ACCOUNTSender account is invalid, closed, or inaccessible
SAME_SENDER_AND_RECEIVERSender and receiver accounts are the same
INVALID_SENDER_MESSAGESender message contains invalid characters
INVALID_RECEIVER_MESSAGEReceiver message contains invalid characters
INVALID_CREDITOR_NAMEReceiver name is invalid
INVALID_PAYMENT_DATEExecution date is invalid
AMOUNT_EXCEEDS_MAXIMUMAmount exceeds allowed maximum
AMOUNT_BELOW_MINIMUMAmount is below allowed minimum
SENDER_MESSAGE_TOO_LONGSender message exceeds max length
RECEIVER_MESSAGE_TOO_LONGReceiver message exceeds max length
GENERIC_ERRORValidation service error
UNKNOWN_VALIDATION_ERRORUnmapped validation failure

Norwegian KID payments can fail backend validation with KID_REQUIRED or INVALID_KID, but the Open Banking draft API does not expose a KID field — those errors are not actionable through this API. Use a different receiver account or payment type.

Use code for programmatic handling; use message for localized display.

Payment failure and cancellation

When a previous approval or payment attempt fails, the draft returns to CREATED and GET /payments/drafts/domestic-credit-transfer/{id} includes previousFailureReason — a localized string describing the failure (pass X-Language; supported values: en, sv, da, nb). The same failure is surfaced as localized paymentError on GET /payments/drafts.

Pass X-Language on GET (and on create/patch responses) to localize validationErrors[].message and previousFailureReason on domestic credit transfer drafts. Use validationErrors[].code for programmatic handling — it is language-independent. Defaults to en if omitted or unrecognized.

To cancel an in-flight payment linked to a draft:

DELETE /payments/drafts/domestic-credit-transfer/{id}/payment

Returns 204 No Content when the in-flight payment is cancelled. The operation is idempotent when the payment is already cancelled. Returns 409 Conflict when the draft has no in-flight payment to cancel (for example, the draft is still in CREATED with no prior approval).

Example: Domestic Credit Transfer Draft


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.

See Sender Account Access when setting senderAccountId.

Example: SE Bank Giro Draft


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.

When mixing payment types in one basket, your application must track which payment type each draftId belongs to — neither GET /payments/drafts nor the approval status response includes payment type (see Get Approval Status).

Signing Basket Flow

List Drafts

GET /payments/drafts

Returns draft summaries 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.

Use this endpoint for discovery — showing the user which drafts can be selected for approval, and surfacing paymentError after a failed payment. Use GET /payments/drafts/{payment-type}/{id} when you need the full draft payload, field-level validation (validationErrors including code on domestic credit transfer drafts), or previousFailureReason on a specific draft.

EndpointBest for
GET /payments/draftsList approvable drafts; invalidReason; paymentError
GET /payments/drafts/{payment-type}/{id}Full fields; validationErrors[].code (DCT); previousFailureReason (DCT)

The list response does not include payment type — record the payment type when you create each draft. It exposes invalidReason when isValid is false, not the machine-readable validationErrors[].code available on the per-type GET response.

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. May differ from the requested draftIds if some drafts were already approved or not in an approvable state.
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/domestic-credit-transfer/ {paymentId} or 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 aligned 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