API docs

Base URL: https://api.arabdoc.dev. One authenticated endpoint renders a PDF from JSON.

Authentication

Send your key on every render request as X-API-Key. Keys look like ad_live_… once self-serve issuance is live. Until then, keys are issued manually — request one via /signup or email.

X-API-Key: ad_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

POST /v1/render

Request JSON body. Response is application/pdf on success.

{
  "template": "invoice",
  "data": { /* template schema */ },
  "options": {
    "numerals": "western",
    "calendar": "dual",
    "justify": false
  }
}

Options

FieldTypeNotes
numerals "western" | "arabic-indic" Optional. Controls digit shaping in dates and amounts.
calendar "gregorian" | "hijri" | "dual" Optional. Dual prints Hijri + Gregorian (Asia/Riyadh).
justify boolean Optional. Kashida elongation is unsupported (Chromium).

Template schemas

All objects are strict — unknown fields are rejected.

invoice

FieldType
invoiceNumber, issueDate, supplyDate, currencystring
seller, buyer{ name, address, city, vatNumber, email?, crNumber? }
items[]{ description, sku?, quantity, unitPrice, lineTotal } · max 200
subtotal, vatRate, vatAmount, totalnumber
notes?, font?string / font id

contract

FieldType
title, contractNumber, signedDate, city, currencystring
partyA, partyB{ name, address, crNumber, email? }
lineItems[]{ label, description, amount } · max 200
clauses[]{ number, text }
governingLaw?, font?string / font id

certificate

FieldType
title, subtitle, issuerName, recipientName, bodyTextstring
certificateNumber, issueDate, signatoryName, signatoryTitle, citystring
courseCode?, font?string / font id

letter

FieldType
referenceNumber, letterDate, subject, salutation, closingstring
sender{ orgName, department, address, email?, signatoryName, signatoryTitle }
recipient{ name, title?, orgName? }
bodyParagraphs[]string[]
font?font id

report

FieldType
title, subtitle, orgName, periodLabel, reportDate, reportNumber, authorName, executiveSummary string
authorEmail?string
metrics[]{ name, code, value, unit, change }
sections[]{ heading, body }
font?font id

Font ids: amiri, noto-naskh-arabic, ibm-plex-sans-arabic, cairo, tajawal.

Error codes

Errors are JSON: { "error": { "code", "message", "details?" } }.

HTTPcodeWhen
401UNAUTHORIZEDMissing or invalid API key
400VALIDATION_ERRORBad body / template data
400UNKNOWN_TEMPLATETemplate name not in the five
400LIMIT_EXCEEDED>50 pages or line caps
413PAYLOAD_TOO_LARGEBody > 1 MB
429RATE_LIMITEDPer-minute or daily cap · Retry-After
500INTERNAL_ERRORUnexpected failure

Examples

curl -X POST https://api.arabdoc.dev/v1/render \
  -H "X-API-Key: $ARABDOC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "template": "letter",
    "data": {
      "referenceNumber": "LTR-100",
      "letterDate": "2026-08-08",
      "sender": {
        "orgName": "شركة المثال",
        "department": "الموارد البشرية",
        "address": "الرياض",
        "signatoryName": "فهد العتيبي",
        "signatoryTitle": "مدير"
      },
      "recipient": { "name": "نورة الشمري" },
      "subject": "تأكيد موعد",
      "salutation": "تحية طيبة،",
      "bodyParagraphs": ["نود تأكيد موعد الاجتماع يوم الأحد."],
      "closing": "وتفضلوا بقبول فائق الاحترام"
    },
    "options": { "numerals": "western", "calendar": "dual" }
  }' \
  -o letter.pdf
const res = await fetch("https://api.arabdoc.dev/v1/render", {
  method: "POST",
  headers: {
    "X-API-Key": process.env.ARABDOC_API_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    template: "invoice",
    data: invoicePayload,
    options: { numerals: "western", calendar: "dual" },
  }),
});
if (!res.ok) throw new Error(await res.text());
const pdf = Buffer.from(await res.arrayBuffer());
await fs.writeFile("invoice.pdf", pdf);
import os, requests

r = requests.post(
    "https://api.arabdoc.dev/v1/render",
    headers={
        "X-API-Key": os.environ["ARABDOC_API_KEY"],
        "Content-Type": "application/json",
    },
    json={
        "template": "report",
        "data": report_payload,
        "options": {"numerals": "western", "calendar": "dual"},
    },
    timeout=60,
)
r.raise_for_status()
open("report.pdf", "wb").write(r.content)

Limits