Guides
Corrections for refunds
A refund in your store becomes a correction invoice (KOR) with difference lines, linked to the KSeF number of the original invoice.
How a refund becomes a correction
When you refund goods or shipping, send the refund to POST /v1/invoices/{id}/corrections. The API creates a correction invoice (KOR, TypKorekty 2) with difference lines: returned items have negative quantities, and the correction refers to the original invoice's KSeF number. The queue waits until the original is accepted in KSeF, so you can send the refund right away.
curl -X POST https://app.ksefshop.pl/v1/invoices/inv_4fJ2kP9xQwErTy7uI0oPaSdF/corrections \
-H "Authorization: Bearer $KSEF_API_KEY" \
-H "Idempotency-Key: refund-RF-1" \
-H "Content-Type: application/json" \
-d '{
"refund": {
"external_id": "RF-1",
"reason": "Zwrot towaru",
"lines": [
{
"name": "Kubek ceramiczny 350 ml",
"sku": "KUB-350",
"quantity": 1,
"unit_price": "49.00",
"vat_rate": "23"
}
],
"shipping": {
"price": "15.99"
}
}
}'<?php
$payload = [
'refund' => [
'external_id' => 'RF-1',
'reason' => 'Zwrot towaru',
'lines' => [
[
'name' => 'Kubek ceramiczny 350 ml',
'sku' => 'KUB-350',
'quantity' => 1,
'unit_price' => '49.00',
'vat_rate' => '23',
],
],
'shipping' => ['price' => '15.99'],
],
];
$ch = curl_init('https://app.ksefshop.pl/v1/invoices/inv_4fJ2kP9xQwErTy7uI0oPaSdF/corrections');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('KSEF_API_KEY'),
'Idempotency-Key: refund-RF-1',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE),
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
$data = json_decode($body, true);
if ($status >= 400) {
throw new RuntimeException($data['error']['code'] . ': ' . $data['error']['message']);
}
echo var_export($data['id'], true), ' ', var_export($data['status'], true), PHP_EOL;const res = await fetch("https://app.ksefshop.pl/v1/invoices/inv_4fJ2kP9xQwErTy7uI0oPaSdF/corrections", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.KSEF_API_KEY}`,
"Idempotency-Key": "refund-RF-1",
"Content-Type": "application/json",
},
body: JSON.stringify({
refund: {
external_id: "RF-1",
reason: "Zwrot towaru",
lines: [
{
name: "Kubek ceramiczny 350 ml",
sku: "KUB-350",
quantity: 1,
unit_price: "49.00",
vat_rate: "23",
},
],
shipping: { price: "15.99" },
},
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.error.code}: ${data.error.message}`);
console.log(data.id, data.status);import os
import requests
resp = requests.post(
"https://app.ksefshop.pl/v1/invoices/inv_4fJ2kP9xQwErTy7uI0oPaSdF/corrections",
headers={
"Authorization": f"Bearer {os.environ['KSEF_API_KEY']}",
"Idempotency-Key": "refund-RF-1",
},
json={
"refund": {
"external_id": "RF-1",
"reason": "Zwrot towaru",
"lines": [
{
"name": "Kubek ceramiczny 350 ml",
"sku": "KUB-350",
"quantity": 1,
"unit_price": "49.00",
"vat_rate": "23",
},
],
"shipping": {"price": "15.99"},
},
},
timeout=30,
)
data = resp.json()
if not resp.ok:
raise RuntimeError(f"{data['error']['code']}: {data['error']['message']}")
print(data["id"], data["status"])What to send
refund.external_id: your refund id. One refund, one correction: a repeat returns the existing correction with200.lines: returned lines with a positive quantity, the gross unit price and the VAT rate, as on the invoice.shipping.price: refunded gross shipping; the VAT rate of the invoice's shipping applies.reason: the reason printed on the correction, e.g. "Returned goods".
At least one of lines and shipping is required.
What the API does not do
| Situation | Response |
|---|---|
| Amount-only refund, no lines or shipping | 422 order_unprocessable |
A correction of a correction (cor_... in the path) |
400 correction_requires_invoice |
| The original was rejected by KSeF | correction skipped, original_rejected |
| An order without an invoice | correction skipped, original_not_invoiced |
Correction status
A correction has the same statuses as an invoice. Follow it through correction.accepted, correction.rejected and correction.skipped events, with GET /v1/invoices/cor_..., or in the original invoice's corrections field.
WooCommerce
Instead of refund, the WooCommerce plugin sends woocommerce_order plus woocommerce_refund (a WooCommerce REST API v3 refund). We derive the difference lines from _refunded_item_id.