temsor API
← Guides

Issuing a Turkish e-Invoice (UBL-TR) from your own software

What a UBL-TR document must contain, why integrators reject valid-looking invoices, and how to produce one from JSON without reading the 400-page spec.

Updated 2026-07-26

If you sell to Turkish businesses, at some point your billing system has to emit a document Türkiye’s tax authority recognises. That document is UBL-TR — a national customisation of UBL 2.1 — and it is stricter than anything you have shipped for an EU invoice.

First, which document do you need?

ProfileWhenBuyer
TEMELFATURAStandard e-Invoice, no formal objection flowRegistered e-Invoice user
TICARIFATURABuyer may formally accept or rejectRegistered e-Invoice user
EARSIVFATURAe-Archive — buyer is not in the e-Invoice systemConsumers and unregistered businesses

Sending the wrong profile is the first rejection most teams hit: an e-Invoice addressed to someone who is not registered simply cannot be delivered. Check the recipient’s registration before choosing.

Why a valid-looking invoice gets rejected

  1. Element order. UBL is XSD-driven and the order of elements is part of the contract. Correct data in the wrong sequence fails validation with an unhelpful message.
  2. Totals that are one kuruş off. Line amounts, per-rate VAT subtotals and the payable amount must reconcile exactly. Rounding each line and then rounding the sum again is the classic way to end up one kuruş adrift.
  3. Invoice number format. Sixteen characters: three letters, the four-digit year, then a nine-digit sequence — ABC2026000000123. The year inside the number has to match the issue date.
  4. Tax identifiers. A ten-digit VKN or, for an individual, an eleven-digit TCKN — each with a checksum. A typo passes your form validation and fails at the integrator hours later.
  5. The amount in words. Turkish invoices carry the payable amount written out (Yalnız #…#). Missing it is not fatal everywhere, but it is expected.
  6. UUID (ETTN). Every document carries a UUID that identifies it for its entire legal life. Generate it once and store it — you cannot reissue it later.
The thing nobody tells you: the sum of line-level VAT is not always the document VAT. Group the lines by rate, then compute the tax on each group’s taxable base. Adding up per-line tax amounts drifts as soon as you have odd quantities.

From JSON to a valid document

curl -X POST https://api.temsor.com/v1/tr/invoice/build \
  -H 'content-type: application/json' \
  -d '{
    "series": "ABC", "sequence": 123, "issueDate": "2026-07-26",
    "supplier": { "name": "Örnek Yazılım A.Ş.", "taxNumber": "4840847211",
                  "taxOffice": "Kadıköy", "city": "İstanbul" },
    "customer": { "name": "Alıcı Ticaret Ltd. Şti.", "taxNumber": "3250566341",
                  "taxOffice": "Beşiktaş", "city": "İstanbul" },
    "lines": [
      { "name": "Yazılım geliştirme hizmeti", "quantity": 10,
        "unitCode": "HUR", "unitPrice": 1500, "vatRate": 20 }
    ]
  }'

What comes back is the XML plus the numbers it computed, so you can reconcile against your own ledger before anything leaves your system:

{
  "id": "ABC2026000000123",
  "uuid": "61f15ddb-…",
  "totals": {
    "lineExtensionAmount": 15000,
    "taxTotalAmount": 3000,
    "payableAmount": 18000
  },
  "taxSubtotals": [{ "rate": 20, "taxableAmount": 15000, "taxAmount": 3000 }],
  "checks": [
    { "check": "invoice number matches issue year", "passed": true },
    { "check": "parties carry a valid tax identifier", "passed": true },
    { "check": "line totals sum to the document total", "passed": true }
  ],
  "warnings": []
}

The totals you send are ignored on purpose. Everything is recomputed from quantities, unit prices, discounts and rates — because when your number and the document’s number disagree, the document is what gets rejected. If your ledger disagrees with the response, you have found a bug before the tax authority did.

What still belongs to you

Two steps are deliberately outside this endpoint, and no API can honestly take them from you:

The document produced here is shaped to enter that step — the signature slot is already referenced in the cac:Signature block.

Reading invoices you receive

The other direction is often the more urgent one: a supplier sends you UBL-TR XML and your system has to book it. tr/invoice/parse turns it into flat JSON — parties, lines, taxes, totals — and flags the case where the line sum and the stated total disagree, which happens more often in the wild than anyone expects.

A quick round trip as a sanity check

import Client from 'temsor-api';
const api = new Client();

const built = await api.trInvoiceBuild(invoiceJson);
const back  = await api.trInvoiceParse({ xml: built.xml });

console.assert(back.totals.payableAmount === built.totals.payableAmount);

Worth doing once in your test suite: it catches an entire class of encoding and escaping problems — an ampersand in a trade name, a Turkish character mangled by the wrong charset — before a real invoice carries them.

Frequently asked

What is the difference between e-Fatura and e-Arşiv?

e-Fatura (TEMELFATURA, TICARIFATURA) is used when the buyer is registered in the e-Invoice system. e-Arşiv (EARSIVFATURA) is used when they are not — typically consumers and smaller businesses.

What format must a Turkish invoice number have?

Sixteen characters: a three-letter series code, the four-digit year, and a nine-digit sequence, e.g. ABC2026000000123. The year in the number must match the issue date.

Does this API sign and send the invoice?

No. Signing requires your financial seal or e-signature certificate, and transmission runs through your integrator. The endpoint produces the document ready for that step.

Can I issue an invoice in a foreign currency?

Yes — set currency and supply exchangeRate as the number of TRY per unit of that currency. The rate is written into the document as required.

Endpoints used here

/v1/tr/invoice/build

Open the reference, run it in the browser, no key required.

/v1/tr/invoice/parse

Open the reference, run it in the browser, no key required.

/v1/tr/validate

Open the reference, run it in the browser, no key required.

/v1/tr/money/to-words

Open the reference, run it in the browser, no key required.