Developers Use cases

AI agent invoicing integration

Let an AI agent prepare Saudi tax invoices safely: drafts through the API or the MCP server, and issuing only with a person's approval.

The rule: agents draft, people issue

A tax invoice cannot be withdrawn once issued. An agent that misreads a request should cost you a draft to delete, not a credit note. WonderFatoora is built around that rule.

Today: the REST API with a drafting key

  1. Create a key with invoices:write and invoices:read, without invoices:issue.
  2. Give your agent one tool that calls POST /invoices with "issue": false.
  3. A person reviews the draft in the dashboard and issues it.
curl -X POST https://fatoora.tw.sa/api/v1/invoices \
  -H "Authorization: Bearer wbk_test_xxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
  "issue": false,
  "issue_date": "2026-11-03",
  "customer": {
    "name": "Example Trading Co.",
    "vat_number": "300000000000003",
    "address_line1": "King Fahd Rd",
    "city": "Riyadh",
    "postal_code": "12345",
    "country": "SA"
  },
  "items": [
    { "description": "Consulting", "quantity": 2, "unit_price": 500, "vat_rate": 15 }
  ]
}'
<?php
$response = $client->post('invoices', [
    'json' => [
        'issue'      => false,          // a draft: no legal number, nothing sent to ZATCA
        'issue_date' => '2026-11-03',
        'customer'   => [
            'name'          => 'Example Trading Co.',
            'vat_number'    => '300000000000003',
            'address_line1' => 'King Fahd Rd',
            'city'          => 'Riyadh',
            'postal_code'   => '12345',
            'country'       => 'SA',
        ],
        'items' => [
            ['description' => 'Consulting', 'quantity' => 2, 'unit_price' => 500, 'vat_rate' => 15],
        ],
    ],
    'http_errors' => false,
]);

$body = json_decode((string) $response->getBody(), true);
if ($body['ok']) {
    echo $body['data']['invoice']['number']; // DRAFT-… until it is issued
} else {
    echo $body['error']['code'];
}
const res = await fetch("https://fatoora.tw.sa/api/v1/invoices", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.WONDERFATOORA_API_KEY}`,
    "Content-Type": "application/json",
    Accept: "application/json",
  },
  body: JSON.stringify({
    issue: false, // a draft: no legal number, nothing sent to ZATCA
    issue_date: "2026-11-03",
    customer: {
      name: "Example Trading Co.",
      vat_number: "300000000000003",
      address_line1: "King Fahd Rd",
      city: "Riyadh",
      postal_code: "12345",
      country: "SA",
    },
    items: [{ description: "Consulting", quantity: 2, unit_price: 500, vat_rate: 15 }],
  }),
});

const body = await res.json();
if (!body.ok) throw new Error(`${body.error.code}: ${body.error.message}`);
console.log(body.data.invoice.id, body.data.invoice.number);
res = requests.post(
    "https://fatoora.tw.sa/api/v1/invoices",
    headers={
        "Authorization": f"Bearer {os.environ['WONDERFATOORA_API_KEY']}",
        "Accept": "application/json",
    },
    json={
        "issue": False,  # a draft: no legal number, nothing sent to ZATCA
        "issue_date": "2026-11-03",
        "customer": {
            "name": "Example Trading Co.",
            "vat_number": "300000000000003",
            "address_line1": "King Fahd Rd",
            "city": "Riyadh",
            "postal_code": "12345",
            "country": "SA",
        },
        "items": [
            {"description": "Consulting", "quantity": 2, "unit_price": 500, "vat_rate": 15}
        ],
    },
    timeout=30,
)
body = res.json()
print(res.status_code, body["data"]["invoice"]["number"] if body["ok"] else body["error"])

Even if the agent sends "issue": true, a key without invoices:issue is refused with 403 insufficient_scope, so the guarantee does not depend on the prompt.

With MCP: Wonder Fatoora MCP

The MCP server gives agents read and drafting tools out of the box, with issuing off by default and every issue approved by a person inside WonderFatoora.

Good practice

  • Give the agent the narrowest key you can, separate from your other systems.
  • Treat text from invoices and customers as data; never let it change the agent's instructions.
  • Log what the agent sent and the id it got back.
  • Use external_reference so a repeated tool call returns the same draft.

Ready to build?

API access is part of the Business and Enterprise plans. Create a key in Settings → API keys and make your first call in minutes.