Invoice creation example
Detailed Example
This section walks you through two common scenarios using the Wafeq API — creating an invoice and creating a cash invoice — with complete request and response examples in JavaScript, Python, and Go.
Each example shows how to:
- Create or retrieve the necessary data (contacts, accounts)
- Create the invoice or cash invoice
- Record a payment (where applicable)
These examples assume you already have your API key and workspace configured.
If not, start with the Authentication guide first.
Create a contact
Create a customer contact you can bill later.
Endpoint: POST https://api.wafeq.com/v1/contacts/
Auth: Authorization: Api-Key <YOUR_API_KEY>
See the Contacts reference for the complete schema and additional fields.
Request body (minimal example)
{
"name": "Acme Trading LLC",
"email": "[email protected]",
"phone": "+971500000000"
} Tip: Add any extra fields you track (e.g., address data, tax registration number) per the reference.
Request
const res = await fetch("https://api.wafeq.com/v1/contacts/", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Api-Key " + process.env.WAFEQ_API_KEY
},
body: JSON.stringify({
name: "Acme Trading LLC",
email: "[email protected]",
phone: "+971500000000"
})
});
if (!res.ok) {
const err = await res.text();
throw new Error(`Create contact failed: ${res.status} ${err}`);
}
const contact = await res.json();
console.log(contact);import os, requests
resp = requests.post(
"https://api.wafeq.com/v1/contacts/",
headers={
"Authorization": f"Api-Key {os.environ['WAFEQ_API_KEY']}",
"Content-Type": "application/json",
},
json={
"name": "Acme Trading LLC",
"email": "[email protected]",
"phone": "+971500000000",
},
)
resp.raise_for_status()
contact = resp.json()
print(contact)package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
func main() {
body := map[string]any{
"name": "Acme Trading LLC",
"email": "[email protected]",
"phone": "+971500000000",
}
b, _ := json.Marshal(body)
req, _ := http.NewRequest("POST", "https://api.wafeq.com/v1/contacts/", bytes.NewReader(b))
req.Header.Set("Authorization", "Api-Key "+os.Getenv("WAFEQ_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
if resp.StatusCode >= 300 {
msg, _ := io.ReadAll(resp.Body)
panic(fmt.Errorf("create contact failed: %s %s", resp.Status, string(msg)))
}
var contact map[string]any
if err := json.NewDecoder(resp.Body).Decode(&contact); err != nil {
panic(err)
}
fmt.Println(contact)
}Retrieve the account to book the sales to
Choose the revenue account you’ll post the invoice line(s) to (e.g., “Sales”).
If you already know the account_id, you can skip this step and use it directly when creating the invoice line item.
Endpoint: GET https://api.wafeq.com/v1/accounts/
Auth: Authorization: Api-Key <YOUR_API_KEY>
Tip: In your own workspace, prefer filtering by a known code (e.g.,
4000) or an exact name (e.g., “Sales”) to avoid ambiguity.
const res = await fetch("https://api.wafeq.com/v1/accounts/", {
headers: {
"Authorization": "Api-Key " + process.env.WAFEQ_API_KEY
}
});
if (!res.ok) {
const err = await res.text();
throw new Error(`List accounts failed: ${res.status} ${err}`);
}
const { results } = await res.json();
const revenueAccount =
results.find(a => a.code === "4000") ||
results.find(a => a.name?.toLowerCase() === "sales") ||
results.find(a => a.classification === "revenue" || a.type === "revenue");
if (!revenueAccount) {
throw new Error("No suitable revenue account found. Consider using a known code or name.");
}
console.log(revenueAccount);import os, requests
resp = requests.get(
"https://api.wafeq.com/v1/accounts/",
headers={"Authorization": f"Api-Key {os.environ['WAFEQ_API_KEY']}"},
)
resp.raise_for_status()
payload = resp.json()
results = payload.get("results", payload if isinstance(payload, list) else [])
revenue_account = next(
(
a for a in results
if a.get("code") == "4000"
or (a.get("name") or "").lower() == "sales"
or a.get("classification") == "revenue"
or a.get("type") == "revenue"
),
None
)
if not revenue_account:
raise RuntimeError("No suitable revenue account found. Use a known code or exact name.")
print(revenue_account)
package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
)
type Account struct {
ID string `json:"id"`
Name string `json:"name"`
Code string `json:"code"`
Type string `json:"type"`
Classification string `json:"classification"`
}
func main() {
req, _ := http.NewRequest("GET", "https://api.wafeq.com/v1/accounts/", nil)
req.Header.Set("Authorization", "Api-Key "+os.Getenv("WAFEQ_API_KEY"))
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
if resp.StatusCode >= 300 {
panic(fmt.Errorf("list accounts failed: %s", resp.Status))
}
// Support either {"results":[...]} or bare [] shape
var payload struct {
Results []Account `json:"results"`
}
var list []Account
dec := json.NewDecoder(resp.Body)
if err := dec.Decode(&payload); err == nil && len(payload.Results) > 0 {
list = payload.Results
} else {
// Try decode as a bare array (ignore prior decode error)
req2, _ := http.NewRequest("GET", "https://api.wafeq.com/v1/accounts/", nil)
req2.Header = req.Header
resp2, err2 := http.DefaultClient.Do(req2)
if err2 != nil {
panic(err2)
}
defer resp2.Body.Close()
if err := json.NewDecoder(resp2.Body).Decode(&list); err != nil {
panic(err)
}
}
var revenue *Account
for i := range list {
a := &list[i]
if a.Code == "4000" ||
strings.EqualFold(a.Name, "Sales") ||
a.Classification == "revenue" ||
a.Type == "revenue" {
revenue = a
break
}
}
if revenue == nil {
panic("no suitable revenue account found; use a known code or exact name")
}
fmt.Printf("%+v\n", *revenue)
}
Example Response
{
"account_code": "11",
"account_type": "",
"classification": "ASSET",
"created_ts": "2025-10-01T06:50:48.985071Z",
"id": "acc_7mWREbbgavfh6JCGsQCSkz",
"is_locked": true,
"is_payment_enabled": false,
"is_posting": false,
"is_system": false,
"modified_ts": "2025-10-01T06:50:48.985145Z",
"name_ar": "الأصول المتداولة",
"name_en": "Current Assets",
"parent": "acc_FSD8TuroF3ywNFky7zZ5e9",
"sub_classification": "CURRENT_ASSET"
},Create the invoice
Create an empty invoice “header”, then add line items.
Endpoints:
POST https://api.wafeq.com/v1/invoices/(create invoice) — see Invoices.POST https://api.wafeq.com/v1/invoices/{invoice_id}/line-items/(add line) — see Invoice Line Items.
Auth: Authorization: Api-Key <YOUR_API_KEY>
Idempotency (recommended for POSTs): X-Wafeq-Idempotency-Key: <uuid-v4> — see Idempotency.
You’ll use the contact you created earlier and the revenue account you retrieved in the previous step.
Request body (invoice header — minimal)
{
"contact": "cnt_7c2f2a1f-1f7e-4c2f-9f8a-1c2a0b9e3d11",
"currency": "AED",
"invoice_date": "2025-10-30",
"invoice_due_date": "2025-11-14",
"invoice_number": "INV-1001",
"notes": "Thanks for your business!",
"line_items": [
{
"account": "acc_9a1c7f3b-2d4e-4a53-9a3b-b5f7e2c4e9d0",
"description": "Consulting services",
"quantity": 10,
"unit_amount": 250,
"tax_rate": null
}
]
}Request
const API = "https://api.wafeq.com/v1";
const headers = {
"Content-Type": "application/json",
"Authorization": "Api-Key " + process.env.WAFEQ_API_KEY,
"X-Wafeq-Idempotency-Key": crypto.randomUUID()
};
const payload = {
contact: process.env.WAFEQ_CONTACT_ID,
currency: "AED",
invoice_date: "2025-10-30",
invoice_due_date: "2025-11-14",
invoice_number: "INV-1001",
notes: "Thanks for your business!",
line_items: [
{
account: process.env.WAFEQ_REVENUE_ACCOUNT_ID, // e.g., Sales
description: "Consulting services",
quantity: 10,
unit_amount: 250,
tax_rate: null
}
]
};
const invRes = await fetch(`${API}/invoices/`, {
method: "POST",
headers,
body: JSON.stringify(payload)
});
if (!invRes.ok) {
const err = await invRes.text();
throw new Error(`Create invoice failed: ${invRes.status} ${err}`);
}
const invoice = await invRes.json();
console.log(invoice);
import os, uuid, requests
API = "https://api.wafeq.com/v1"
headers = {
"Authorization": f"Api-Key {os.environ['WAFEQ_API_KEY']}",
"Content-Type": "application/json",
"X-Wafeq-Idempotency-Key": str(uuid.uuid4()),
}
payload = {
"contact": os.environ["WAFEQ_CONTACT_ID"],
"currency": "AED",
"invoice_date": "2025-10-30",
"invoice_due_date": "2025-11-14",
"invoice_number": "INV-1001",
"notes": "Thanks for your business!",
"line_items": [
{
"account": os.environ["WAFEQ_REVENUE_ACCOUNT_ID"],
"description": "Consulting services",
"quantity": 10,
"unit_amount": 250,
"tax_rate": None,
}
],
}
resp = requests.post(f"{API}/invoices/", headers=headers, json=payload)
resp.raise_for_status()
invoice = resp.json()
print(invoice)
import os, uuid, requests
API = "https://api.wafeq.com/v1"
headers = {
"Authorization": f"Api-Key {os.environ['WAFEQ_API_KEY']}",
"Content-Type": "application/json",
"X-Wafeq-Idempotency-Key": str(uuid.uuid4()),
}
payload = {
"contact": os.environ["WAFEQ_CONTACT_ID"],
"currency": "AED",
"invoice_date": "2025-10-30",
"invoice_due_date": "2025-11-14",
"invoice_number": "INV-1001",
"notes": "Thanks for your business!",
"line_items": [
{
"account": os.environ["WAFEQ_REVENUE_ACCOUNT_ID"],
"description": "Consulting services",
"quantity": 10,
"unit_amount": 250,
"tax_rate": None,
}
],
}
resp = requests.post(f"{API}/invoices/", headers=headers, json=payload)
resp.raise_for_status()
invoice = resp.json()
print(invoice)
Record a payment on the invoice
Create a customer payment and link it to the invoice you just created.
Endpoint: POST https://api.wafeq.com/v1/payments/
Auth: Authorization: Api-Key <YOUR_API_KEY>
Idempotency (recommended): X-Wafeq-Idempotency-Key: <uuid-v4>
You’ll need:
- The invoice ID (from the previous step)
- The contact ID
- The paid-through account ID (your bank or cash account)
Request body (example)
{
"amount": 2500.0,
"contact": "cnt_7c2f2a1f-1f7e-4c2f-9f8a-1c2a0b9e3d11",
"currency": "AED",
"date": "2025-10-31",
"paid_through_account": "acc_bank_123",
"reference": "Bank transfer FT-92831",
"invoice_payments": [
{
"invoice": "inv_2af9a6e1-2c10-4b2a-b111-0d7b2f2f0e9a",
"amount": 2500.0,
"amount_to_pcy": 2500.0
}
]
}Request
const res = await fetch("https://api.wafeq.com/v1/payments/", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Api-Key " + process.env.WAFEQ_API_KEY,
"X-Wafeq-Idempotency-Key": crypto.randomUUID()
},
body: JSON.stringify({
amount: 2500.0,
contact: process.env.WAFEQ_CONTACT_ID,
currency: "AED",
date: "2025-10-31",
paid_through_account: process.env.WAFEQ_BANK_ACCOUNT_ID,
reference: "Bank transfer FT-92831",
invoice_payments: [
{
invoice: process.env.WAFEQ_INVOICE_ID,
amount: 2500.0,
amount_to_pcy: 2500.0
}
]
})
});
if (!res.ok) {
const err = await res.text();
throw new Error(`Create payment failed: ${res.status} ${err}`);
}
const payment = await res.json();
console.log(payment);
import os, uuid, requests
resp = requests.post(
"https://api.wafeq.com/v1/payments/",
headers={
"Authorization": f"Api-Key {os.environ['WAFEQ_API_KEY']}",
"Content-Type": "application/json",
"X-Wafeq-Idempotency-Key": str(uuid.uuid4()),
},
json={
"amount": 2500.0,
"contact": os.environ["WAFEQ_CONTACT_ID"],
"currency": "AED",
"date": "2025-10-31",
"paid_through_account": os.environ["WAFEQ_BANK_ACCOUNT_ID"],
"reference": "Bank transfer FT-92831",
"invoice_payments": [
{
"invoice": os.environ["WAFEQ_INVOICE_ID"],
"amount": 2500.0,
"amount_to_pcy": 2500.0,
}
],
},
)
resp.raise_for_status()
print(resp.json())
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
"github.com/google/uuid"
)
func main() {
payload := map[string]any{
"amount": 2500.0,
"contact": os.Getenv("WAFEQ_CONTACT_ID"),
"currency": "AED",
"date": "2025-10-31",
"paid_through_account": os.Getenv("WAFEQ_BANK_ACCOUNT_ID"),
"reference": "Bank transfer FT-92831",
"invoice_payments": []map[string]any{
{
"invoice": os.Getenv("WAFEQ_INVOICE_ID"),
"amount": 2500.0,
"amount_to_pcy": 2500.0,
},
},
}
b, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", "https://api.wafeq.com/v1/payments/", bytes.NewReader(b))
req.Header.Set("Authorization", "Api-Key "+os.Getenv("WAFEQ_API_KEY"))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Wafeq-Idempotency-Key", uuid.NewString())
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
if resp.StatusCode >= 300 {
panic(fmt.Errorf("create payment failed: %s", resp.Status))
}
fmt.Println("Payment recorded successfully")
}
Updated about 9 hours ago