KWNDevelopersالعربيةOpen KWN

API reference

Bills

Vendor bills: drafted from an order, then posted. Posting is the approval and needs purchase:approve.

List bills

GET/bills

Needs
purchases.read
Scopes
purchase:read

Every vendor bill, newest first, whether pushed, billed from an order or typed in.

Parameters

FieldTypeDescription
statusQuerystringA status, by name.
partnerIdQuerystringThe customer (sales) or the vendor (purchases).
issueFromQuerydateIssued on or after this day.
issueToQuerydateIssued on or before this day.
numberQuerystring
limitQueryinteger1 to 200; 50 by default.
cursorQuerystringThe previous page's next.
updatedSinceQuerydateOnly rows changed at or after this instant.
createdFromQuerydate
createdToQuerydate
externalIdQuerystringOnly the row your system gave this id.

Code samples

curl -sS -X GET "https://app.kwn.tax/api/v1/bills" \
  -H "X-Api-Key: $KWN_API_KEY"

Responses

  • 200OK
    FieldTypeDescription
    dataobject[]The rows of this page.
    nextstringThe cursor of the next page, to send as ?cursor=; null on the last page.
  • 400Refused: the request or a business rule. The code says which (REQUEST_INVALID, RULE_REFUSED, …).
    Example
    {
      "error": "Lines: at least one line is required.",
      "code": "REQUEST_INVALID"
    }
  • 401No key, a key that is not ours, or one revoked (API_KEY_INVALID); a merchant not active (MERCHANT_INACTIVE).
    Example
    {
      "error": "That API key is not valid, or it was revoked.",
      "code": "API_KEY_INVALID"
    }
  • 403The key's scopes do not carry the capability (APP_NOT_GRANTED, ROLE_TOO_LOW), or the plan lacks the app (APP_LOCKED).
    Example
    {
      "error": "This key's scopes do not include sales.approve.",
      "code": "ROLE_TOO_LOW"
    }
  • 404Not found: another type under this route, or another company's (NOT_FOUND).
    Example
    {
      "error": "Not found.",
      "code": "NOT_FOUND"
    }
  • 409A conflict: an externalId taken, or a change that could not be saved (EXTERNAL_ID_TAKEN, CONFLICT).
    Example
    {
      "error": "That externalId is already used by another record of this company.",
      "code": "EXTERNAL_ID_TAKEN"
    }
  • 429Too many requests: wait retryAfter seconds (RATE_LIMITED).
    Example
    {
      "error": "Too many requests. Wait a little and try again.",
      "code": "RATE_LIMITED"
    }

Read a bill

GET/bills/{id}

Needs
purchases.read
Scopes
purchase:read

Any bill of the company, with its lines. A superset of what the legacy push answers.

Parameters

FieldTypeDescription
idPathRequiredstring

Code samples

curl -sS -X GET "https://app.kwn.tax/api/v1/bills/<id>" \
  -H "X-Api-Key: $KWN_API_KEY"

Responses

  • 200OK
    FieldTypeDescription
    idstring
    typeRFQ | ORDER | BILL | REFUND | DEBIT_NOTE
    numberstring
    statusDRAFT | SENT | CONFIRMED | RECEIVED | POSTED | PAID | CANCELLED
    issueDatedate
    dueDatedate
    vendorobject
    vendorRefstring
    buyerIdstring
    paymentTermIdstring
    notesstring
    linesobject[]
    subtotalnumber
    discountTotalnumber
    taxTotalnumber
    totalnumber
    paidTotalnumber
    creditedTotalnumber
    originMANUAL | API | EINVOICE
    sourceobject
    derivedobject[]
    externalIdstring
    duplicatebooleanAlways false here; true only on the legacy push when it answered a bill it already had.
    createdAtdate
    updatedAtdate
  • 400Refused: the request or a business rule. The code says which (REQUEST_INVALID, RULE_REFUSED, …).
    Example
    {
      "error": "Lines: at least one line is required.",
      "code": "REQUEST_INVALID"
    }
  • 401No key, a key that is not ours, or one revoked (API_KEY_INVALID); a merchant not active (MERCHANT_INACTIVE).
    Example
    {
      "error": "That API key is not valid, or it was revoked.",
      "code": "API_KEY_INVALID"
    }
  • 403The key's scopes do not carry the capability (APP_NOT_GRANTED, ROLE_TOO_LOW), or the plan lacks the app (APP_LOCKED).
    Example
    {
      "error": "This key's scopes do not include sales.approve.",
      "code": "ROLE_TOO_LOW"
    }
  • 404Not found: another type under this route, or another company's (NOT_FOUND).
    Example
    {
      "error": "Not found.",
      "code": "NOT_FOUND"
    }
  • 409A conflict: an externalId taken, or a change that could not be saved (EXTERNAL_ID_TAKEN, CONFLICT).
    Example
    {
      "error": "That externalId is already used by another record of this company.",
      "code": "EXTERNAL_ID_TAKEN"
    }
  • 429Too many requests: wait retryAfter seconds (RATE_LIMITED).
    Example
    {
      "error": "Too many requests. Wait a little and try again.",
      "code": "RATE_LIMITED"
    }

Update a draft bill

PUT/bills/{id}

Needs
purchases.write
Scopes
purchase:write

Replaces the header and lines of a draft bill. A bill pushed through the legacy endpoint stays without VAT.

Parameters

FieldTypeDescription
idPathRequiredstring

Request body

20 fields
FieldTypeDescription
linesobject[]
lines[].unitPricenumber
lines[].quantitynumber
lines[].taxRatenumberVAT percent: 5 or 0.
lines[].descriptionstring
lines[].productIdstring
lines[].productCodestringA product's SKU, then its barcode.
lines[].uomstring
lines[].discountnumber
lines[].kindPRODUCT | SECTION | NOTE
vendorIdstringThe vendor contact; needed to confirm.
issueDatedate
dueDatedate
vendorRefstringThe vendor's own reference.
notesstring
orderDeadlinedate
expectedArrivaldate
buyerIdstring
paymentTermIdstring
externalIdstringYour system's id for it, kept for good. Create only.

Code samples

curl -sS -X PUT "https://app.kwn.tax/api/v1/bills/<id>" \
  -H "X-Api-Key: $KWN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "vendorId": "ven_7xp3k8d2",
  "issueDate": "2026-10-15",
  "vendorRef": "Q-2026-118",
  "externalId": "PO-5521",
  "lines": [
    {
      "description": "A4 copier paper, box of 5 reams",
      "quantity": 20,
      "unitPrice": 95,
      "taxRate": 5,
      "productCode": "PAP-A4-5"
    }
  ]
}'

Responses

  • 200OK
    FieldTypeDescription
    idstring
    typeRFQ | ORDER | BILL | REFUND | DEBIT_NOTE
    numberstring
    statusDRAFT | SENT | CONFIRMED | RECEIVED | POSTED | PAID | CANCELLED
    issueDatedate
    dueDatedate
    vendorobject
    vendorRefstring
    buyerIdstring
    paymentTermIdstring
    notesstring
    linesobject[]
    subtotalnumber
    discountTotalnumber
    taxTotalnumber
    totalnumber
    paidTotalnumber
    creditedTotalnumber
    originMANUAL | API | EINVOICE
    sourceobject
    derivedobject[]
    externalIdstring
    duplicatebooleanAlways false here; true only on the legacy push when it answered a bill it already had.
    createdAtdate
    updatedAtdate
  • 400Refused: the request or a business rule. The code says which (REQUEST_INVALID, RULE_REFUSED, …).
    Example
    {
      "error": "Lines: at least one line is required.",
      "code": "REQUEST_INVALID"
    }
  • 401No key, a key that is not ours, or one revoked (API_KEY_INVALID); a merchant not active (MERCHANT_INACTIVE).
    Example
    {
      "error": "That API key is not valid, or it was revoked.",
      "code": "API_KEY_INVALID"
    }
  • 403The key's scopes do not carry the capability (APP_NOT_GRANTED, ROLE_TOO_LOW), or the plan lacks the app (APP_LOCKED).
    Example
    {
      "error": "This key's scopes do not include sales.approve.",
      "code": "ROLE_TOO_LOW"
    }
  • 404Not found: another type under this route, or another company's (NOT_FOUND).
    Example
    {
      "error": "Not found.",
      "code": "NOT_FOUND"
    }
  • 409A conflict: an externalId taken, or a change that could not be saved (EXTERNAL_ID_TAKEN, CONFLICT).
    Example
    {
      "error": "That externalId is already used by another record of this company.",
      "code": "EXTERNAL_ID_TAKEN"
    }
  • 429Too many requests: wait retryAfter seconds (RATE_LIMITED).
    Example
    {
      "error": "Too many requests. Wait a little and try again.",
      "code": "RATE_LIMITED"
    }

Post a bill

POST/bills/{id}/post

Needs
purchases.approve
Scopes
purchase:approve

Validates the draft bill into the books: Dr expenses and input VAT, Cr the vendor's payable. Posted already: alreadyDone.

Parameters

FieldTypeDescription
idPathRequiredstring

Code samples

curl -sS -X POST "https://app.kwn.tax/api/v1/bills/<id>/post" \
  -H "X-Api-Key: $KWN_API_KEY"

Responses

  • 200OK
    FieldTypeDescription
    alreadyDonebooleanTrue when the document was already where the action takes it; nothing changed.
    documentobject
  • 400Refused: the request or a business rule. The code says which (REQUEST_INVALID, RULE_REFUSED, …).
    Example
    {
      "error": "Lines: at least one line is required.",
      "code": "REQUEST_INVALID"
    }
  • 401No key, a key that is not ours, or one revoked (API_KEY_INVALID); a merchant not active (MERCHANT_INACTIVE).
    Example
    {
      "error": "That API key is not valid, or it was revoked.",
      "code": "API_KEY_INVALID"
    }
  • 403The key's scopes do not carry the capability (APP_NOT_GRANTED, ROLE_TOO_LOW), or the plan lacks the app (APP_LOCKED).
    Example
    {
      "error": "This key's scopes do not include sales.approve.",
      "code": "ROLE_TOO_LOW"
    }
  • 404Not found: another type under this route, or another company's (NOT_FOUND).
    Example
    {
      "error": "Not found.",
      "code": "NOT_FOUND"
    }
  • 409A conflict: an externalId taken, or a change that could not be saved (EXTERNAL_ID_TAKEN, CONFLICT).
    Example
    {
      "error": "That externalId is already used by another record of this company.",
      "code": "EXTERNAL_ID_TAKEN"
    }
  • 429Too many requests: wait retryAfter seconds (RATE_LIMITED).
    Example
    {
      "error": "Too many requests. Wait a little and try again.",
      "code": "RATE_LIMITED"
    }

Cancel a draft bill

POST/bills/{id}/cancel

Needs
purchases.write
Scopes
purchase:write

A posted bill is refunded, never cancelled. Cancelled already: alreadyDone.

Parameters

FieldTypeDescription
idPathRequiredstring

Code samples

curl -sS -X POST "https://app.kwn.tax/api/v1/bills/<id>/cancel" \
  -H "X-Api-Key: $KWN_API_KEY"

Responses

  • 200OK
    FieldTypeDescription
    alreadyDonebooleanTrue when the document was already where the action takes it; nothing changed.
    documentobject
  • 400Refused: the request or a business rule. The code says which (REQUEST_INVALID, RULE_REFUSED, …).
    Example
    {
      "error": "Lines: at least one line is required.",
      "code": "REQUEST_INVALID"
    }
  • 401No key, a key that is not ours, or one revoked (API_KEY_INVALID); a merchant not active (MERCHANT_INACTIVE).
    Example
    {
      "error": "That API key is not valid, or it was revoked.",
      "code": "API_KEY_INVALID"
    }
  • 403The key's scopes do not carry the capability (APP_NOT_GRANTED, ROLE_TOO_LOW), or the plan lacks the app (APP_LOCKED).
    Example
    {
      "error": "This key's scopes do not include sales.approve.",
      "code": "ROLE_TOO_LOW"
    }
  • 404Not found: another type under this route, or another company's (NOT_FOUND).
    Example
    {
      "error": "Not found.",
      "code": "NOT_FOUND"
    }
  • 409A conflict: an externalId taken, or a change that could not be saved (EXTERNAL_ID_TAKEN, CONFLICT).
    Example
    {
      "error": "That externalId is already used by another record of this company.",
      "code": "EXTERNAL_ID_TAKEN"
    }
  • 429Too many requests: wait retryAfter seconds (RATE_LIMITED).
    Example
    {
      "error": "Too many requests. Wait a little and try again.",
      "code": "RATE_LIMITED"
    }

Record a supplier refund

POST/bills/{id}/refunds

Needs
purchases.write
Scopes
purchase:write

Send an Idempotency-Key header.

A draft refund against a posted bill. Requires Idempotency-Key.

Parameters

FieldTypeDescription
idPathRequiredstring
Idempotency-KeyHeaderRequiredstring1 to 100 visible ASCII characters, such as a UUID, unique to this request. Sent again with the same request, the first answer is replayed (Idempotent-Replayed: true); with another request, 422 IDEMPOTENCY_KEY_REUSED.

Code samples

curl -sS -X POST "https://app.kwn.tax/api/v1/bills/<id>/refunds" \
  -H "X-Api-Key: $KWN_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"

Responses

  • 201Created
    FieldTypeDescription
    idstring
    typeRFQ | ORDER | BILL | REFUND | DEBIT_NOTE
    numberstring
    statusDRAFT | SENT | CONFIRMED | RECEIVED | POSTED | PAID | CANCELLED
    issueDatedate
    dueDatedate
    vendorobject
    vendorRefstring
    buyerIdstring
    paymentTermIdstring
    notesstring
    orderDeadlinedate
    expectedArrivaldate
    linesobject[]
    subtotalnumber
    discountTotalnumber
    taxTotalnumber
    totalnumber
    paidTotalnumber
    creditedTotalnumber
    originMANUAL | API | EINVOICE
    sourceobject
    derivedobject[]
    externalIdstring
    createdAtdate
    updatedAtdate
  • 400Refused: the request or a business rule. The code says which (REQUEST_INVALID, RULE_REFUSED, …).
    Example
    {
      "error": "Lines: at least one line is required.",
      "code": "REQUEST_INVALID"
    }
  • 401No key, a key that is not ours, or one revoked (API_KEY_INVALID); a merchant not active (MERCHANT_INACTIVE).
    Example
    {
      "error": "That API key is not valid, or it was revoked.",
      "code": "API_KEY_INVALID"
    }
  • 403The key's scopes do not carry the capability (APP_NOT_GRANTED, ROLE_TOO_LOW), or the plan lacks the app (APP_LOCKED).
    Example
    {
      "error": "This key's scopes do not include sales.approve.",
      "code": "ROLE_TOO_LOW"
    }
  • 404Not found: another type under this route, or another company's (NOT_FOUND).
    Example
    {
      "error": "Not found.",
      "code": "NOT_FOUND"
    }
  • 409A conflict: an externalId taken, or a change that could not be saved (EXTERNAL_ID_TAKEN, CONFLICT).
    Example
    {
      "error": "That externalId is already used by another record of this company.",
      "code": "EXTERNAL_ID_TAKEN"
    }
  • 422That Idempotency-Key was used for a different request (IDEMPOTENCY_KEY_REUSED).
    Example
    {
      "error": "That Idempotency-Key was used for a different request.",
      "code": "IDEMPOTENCY_KEY_REUSED"
    }
  • 429Too many requests: wait retryAfter seconds (RATE_LIMITED).
    Example
    {
      "error": "Too many requests. Wait a little and try again.",
      "code": "RATE_LIMITED"
    }

Print

GET/bills/{id}/pdf

Needs
purchases.read
Scopes
purchase:read

The printed document as the app prints it, in English or Arabic (?lang=ar). Counted against the apikey-heavy budget.

Parameters

FieldTypeDescription
idPathRequiredstring
langQuerystring

Code samples

curl -sS -X GET "https://app.kwn.tax/api/v1/bills/<id>/pdf" \
  -H "X-Api-Key: $KWN_API_KEY"

Responses

  • 200OK
  • 400Refused: the request or a business rule. The code says which (REQUEST_INVALID, RULE_REFUSED, …).
    Example
    {
      "error": "Lines: at least one line is required.",
      "code": "REQUEST_INVALID"
    }
  • 401No key, a key that is not ours, or one revoked (API_KEY_INVALID); a merchant not active (MERCHANT_INACTIVE).
    Example
    {
      "error": "That API key is not valid, or it was revoked.",
      "code": "API_KEY_INVALID"
    }
  • 403The key's scopes do not carry the capability (APP_NOT_GRANTED, ROLE_TOO_LOW), or the plan lacks the app (APP_LOCKED).
    Example
    {
      "error": "This key's scopes do not include sales.approve.",
      "code": "ROLE_TOO_LOW"
    }
  • 404Not found: another type under this route, or another company's (NOT_FOUND).
    Example
    {
      "error": "Not found.",
      "code": "NOT_FOUND"
    }
  • 409A conflict: an externalId taken, or a change that could not be saved (EXTERNAL_ID_TAKEN, CONFLICT).
    Example
    {
      "error": "That externalId is already used by another record of this company.",
      "code": "EXTERNAL_ID_TAKEN"
    }
  • 429Too many requests: wait retryAfter seconds (RATE_LIMITED).
    Example
    {
      "error": "Too many requests. Wait a little and try again.",
      "code": "RATE_LIMITED"
    }