Tutorial: Issue a bill from a USD transaction
When a corporate card transaction is in USD (or any non-local currency), most Chilean buyers also need to issue a "factura de compra" (buy-invoice) at the SII — both for VAT recovery and for FX-cost accounting. This tutorial shows how to do that programmatically from a card transaction.
The shape:
- Find the source transaction (
card_transactionsindex). - Read the transaction's exchange rate snapshot (
conversion_rates). - Create a
billreferencing the transaction. - Pre-issue, sign, and emit the bill.
1. Find the source transaction
curl 'https://api.cardda.com/v1/card_transactions?currency[$ne]=CLP&_order=desc&_field=created_at&_end=20' \
-H "Authorization: Bearer $API_KEY" -H "company-id: $COMPANY_ID"Pick a transaction that does not already have an associated bill (fiscal_invoice_id is null):
{
"id": "tx-uuid",
"amount": 9900,
"currency": "USD",
"amount_in_clp": 9270000,
"merchant": { "name": "GitHub", "category": "software" },
"fiscal_invoice_id": null,
"occurred_at": "2026-04-15T10:00:00Z"
}2. Get the exchange-rate snapshot
The bill must use the same USD→CLP rate that Cardda used to settle the transaction (otherwise the SII will flag a discrepancy at the next audit).
curl 'https://api.cardda.com/v1/exchange_rates/USD?at=2026-04-15' \
-H "Authorization: Bearer $API_KEY" -H "company-id: $COMPANY_ID"{ "currency": "USD", "rate": 936.36, "effective_at": "2026-04-15T00:00:00Z" }3. Create the bill
curl -X POST 'https://api.cardda.com/v1/bills' \
-H "Authorization: Bearer $API_KEY" -H "company-id: $COMPANY_ID" \
-H "Content-Type: application/json" \
-d '{
"card_transaction_id": "tx-uuid",
"kind": "buy_invoice",
"supplier": {
"tax_id": "59001520-9",
"name": "GitHub Inc",
"country": "US"
},
"items": [
{
"description": "GitHub Enterprise — annual subscription",
"quantity": 1,
"unit_price_usd": 9900,
"tax_rate": 0
}
],
"exchange_rate": 936.36,
"issue_date": "2026-04-15"
}'{
"id": "bill-uuid",
"status": "draft",
"kind": "buy_invoice",
"folio": null,
"total_clp": 9270000,
...
}4. Pre-issue → sign → issue
The SII flow is three calls:
# (a) Pre-issue: validates the bill against SII rules locally
curl -X POST 'https://api.cardda.com/v1/bills/BILL_ID/preissue' \
-H "Authorization: Bearer $API_KEY" -H "company-id: $COMPANY_ID"
# (b) Sign with the company's electronic certificate (must be uploaded once via the dashboard)
curl -X POST 'https://api.cardda.com/v1/bills/BILL_ID/sign' \
-H "Authorization: Bearer $API_KEY" -H "company-id: $COMPANY_ID"
# (c) Submit to the SII
curl -X POST 'https://api.cardda.com/v1/bills/BILL_ID/issue' \
-H "Authorization: Bearer $API_KEY" -H "company-id: $COMPANY_ID"After (c), the bill's status becomes accepted (or rejected with details in sii_response). A folio is assigned by the SII and returned in the response body.
End-to-end snippet (Node.js)
import fetch from "node-fetch";
const api = "https://api.cardda.com/v1";
const H = {
Authorization: `Bearer ${process.env.API_KEY}`,
"company-id": process.env.COMPANY_ID,
"Content-Type": "application/json",
};
async function call(method, path, body) {
const res = await fetch(api + path, { method, headers: H, body: body && JSON.stringify(body) });
if (!res.ok) throw new Error(`${method} ${path} → ${res.status} ${await res.text()}`);
return res.json();
}
// 1. Find a USD transaction without a bill
const [tx] = await call("GET", "/card_transactions?currency=USD&fiscal_invoice_id=null&_end=1");
// 2. Get the rate
const rate = await call("GET", `/exchange_rates/USD?at=${tx.occurred_at.slice(0, 10)}`);
// 3. Create the bill
const bill = await call("POST", "/bills", {
card_transaction_id: tx.id,
kind: "buy_invoice",
supplier: { tax_id: tx.merchant.tax_id, name: tx.merchant.name, country: "US" },
items: [{
description: tx.merchant.name,
quantity: 1,
unit_price_usd: tx.amount,
tax_rate: 0,
}],
exchange_rate: rate.rate,
issue_date: tx.occurred_at.slice(0, 10),
});
// 4. Pre-issue → sign → issue
await call("POST", `/bills/${bill.id}/preissue`);
await call("POST", `/bills/${bill.id}/sign`);
const final = await call("POST", `/bills/${bill.id}/issue`);
console.log({ id: final.id, folio: final.folio, status: final.status });Bulk path
For month-end reconciliation, use:
curl -X POST 'https://api.cardda.com/v1/bills/issue_all' \
-H "Authorization: Bearer $API_KEY" -H "company-id: $COMPANY_ID" \
-H "Content-Type: application/json" \
-d '{ "bill_ids": ["...", "...", "..."] }'Cardda processes each bill sequentially and returns a per-id status map. Failures don't roll back the successful ones.
Common pitfalls
- Mismatched exchange rate. If your rate differs from the one Cardda used at settlement, the SII may accept the bill but month-end recon will flag a delta. Always pull from
/v1/exchange_rates/USD?at=<settlement_date>. - Missing electronic certificate. Step (b) fails with
422 missing_certificateif the company has not uploaded its SII signing certificate. Upload via dashboard → Settings → Tax → SII keys. - Re-issuing. Once a bill has
status: accepted, you cannot re-issue. To "fix" an issued bill, emit a credit note that points to the original folio.
Related
Updated 1 day ago
