Getting Started
Generate EU-compliant ZUGFeRD/Factur-X invoices with a single API call. Start generating invoices in under 5 minutes.
On This Page
Quick Start
Get your first invoice in 3 steps:
1. Get an API Key
Sign up at thelawin.dev and create an API key in your
dashboard. Sandbox keys (env_sandbox_*) are free
and perfect for testing.
2. Make your first request
curl -X POST https://api.thelawin.dev/v1/generate \
-H "Content-Type: application/json" \
-H "X-API-Key: env_sandbox_YOUR_KEY" \
-d '{
"format": "zugferd",
"template": "minimal",
"locale": "en",
"invoice": {
"number": "2026-001",
"date": "2026-01-15",
"seller": {
"name": "Your Company GmbH",
"street": "Main Street 1",
"city": "Berlin",
"postal_code": "10115",
"country": "DE",
"vat_id": "DE123456789"
},
"buyer": {
"name": "Customer AG",
"city": "Munich",
"country": "DE"
},
"items": [
{
"description": "Consulting",
"quantity": 8,
"unit": "HUR",
"unit_price": 150.00,
"vat_rate": 19
}
]
}
}'
3. Decode the PDF
The response contains the PDF as Base64. Decode it to save your invoice:
{
"pdf_base64": "JVBERi0xLjcK...",
"filename": "rechnung-2026-001.pdf",
"format": {
"format_used": "zugferd",
"profile": "EN16931",
"version": "2.3"
},
"account": {
"remaining": 499,
"plan": "starter"
}
}
# Decode and save
echo "$response" | jq -r '.pdf_base64' | base64 -d > invoice.pdf
What you get
- PDF/A-3 - Archive-compliant PDF format
- ZUGFeRD 2.3 / Factur-X - Embedded XML invoice data
- EN 16931 - European standard compliant
- Instant validation - Every invoice is validated before delivery
- ~50ms response time - Fast generation, no queues
API Keys: Sandbox vs Live
Understanding the difference between sandbox and live keys:
| Feature | Sandbox | Live |
|---|---|---|
| Key Prefix | env_sandbox_* |
env_live_* |
| Cost | Free forever | Paid plan required |
| Quota | Unlimited | Plan-based (500-2000+) |
| Watermark | Yes (bottom of PDF) | No watermark |
| All Formats | ✓ Yes | ✓ Yes |
| Use Case | Development, testing | Production invoicing |
💡 Pro Tip
Use sandbox keys during development and switch to live keys only when deploying to production. Keep your live keys secure and never commit them to version control.
Code Examples
Quick examples in popular languages:
JavaScript / TypeScript
const response = await fetch('https://api.thelawin.dev/v1/generate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.THELAWIN_API_KEY
},
body: JSON.stringify({
format: 'zugferd',
template: 'minimal',
invoice: {
number: '2026-001',
date: '2026-01-15',
seller: { name: 'Acme GmbH', city: 'Berlin', country: 'DE' },
buyer: { name: 'Customer', city: 'Munich', country: 'DE' },
items: [
{ description: 'Service', quantity: 1, unit_price: 100, vat_rate: 19 }
]
}
})
});
if (!response.ok) {
const error = await response.json();
console.error('Error:', error);
throw new Error(`API error: ${response.status}`);
}
const data = await response.json();
const pdfBuffer = Buffer.from(data.pdf_base64, 'base64');
fs.writeFileSync('invoice.pdf', pdfBuffer);
Python
import requests
import base64
import os
response = requests.post(
'https://api.thelawin.dev/v1/generate',
headers={
'Content-Type': 'application/json',
'X-API-Key': os.environ['THELAWIN_API_KEY']
},
json={
'format': 'zugferd',
'template': 'minimal',
'invoice': {
'number': '2026-001',
'date': '2026-01-15',
'seller': {'name': 'Acme GmbH', 'city': 'Berlin', 'country': 'DE'},
'buyer': {'name': 'Customer', 'city': 'Munich', 'country': 'DE'},
'items': [
{'description': 'Service', 'quantity': 1, 'unit_price': 100, 'vat_rate': 19}
]
}
}
)
if response.status_code != 200:
print(f"Error {response.status_code}: {response.json()}")
raise Exception("API request failed")
data = response.json()
pdf_bytes = base64.b64decode(data['pdf_base64'])
with open('invoice.pdf', 'wb') as f:
f.write(pdf_bytes)
Ruby (with SDK)
require 'thelawin'
client = Thelawin::Client.new(api_key: ENV['THELAWIN_API_KEY'])
result = client.invoice
.number('2026-001')
.date('2026-01-15')
.seller(name: 'Acme GmbH', city: 'Berlin', country: 'DE')
.buyer(name: 'Customer', city: 'Munich', country: 'DE')
.add_item(description: 'Service', quantity: 1, unit_price: 100, vat_rate: 19)
.format('zugferd')
.template('minimal')
.generate
if result.success?
result.save_pdf('./invoice.pdf')
puts "Generated: #{result.filename}"
else
result.errors.each { |e| puts "#{e[:path]}: #{e[:message]}" }
end
See SDKs page for more language examples.
Rate Limits & Quota
Understanding API limits and quotas:
Rate Limits (Requests per Second)
| Plan | Rate Limit | Burst |
|---|---|---|
| Sandbox | 5 req/sec | 10 req |
| Starter | 10 req/sec | 20 req |
| Pro / Enterprise | 50 req/sec | 100 req |
Monthly Quota
Monthly credit quotas reset at the start of each month.
- Sandbox: 100 credits/month (watermarked PDFs)
- Starter: 500 credits/month
- Pro: 2,000 credits/month
- Enterprise: Custom limits
1 credit = 1x /generate or 1x /retrieve. /validate is free.
Check your remaining quota in the response:
{
"account": {
"remaining": 487,
"plan": "starter",
"overage_count": 0
}
}
Error Handling
The API uses standard HTTP status codes and returns detailed error messages with JSON-Path references for validation errors.
Common HTTP Status Codes
| Status | Meaning | Action |
|---|---|---|
200 |
Success | PDF generated successfully |
400 |
Bad Request | Fix JSON syntax or missing fields |
401 |
Unauthorized | Check API key in X-API-Key header |
422 |
Validation Error | Check error details (invalid VAT, date, etc.) |
429 |
Rate Limit | Wait and retry with exponential backoff |
500 |
Server Error | Retry request after brief delay |
Validation Error Example
{
"error": "VALIDATION_FAILED",
"message": "Invoice validation failed",
"details": [
{
"path": "$.invoice.seller.vat_id",
"code": "INVALID_VAT_FORMAT",
"message": "Invalid VAT ID format for country DE"
},
{
"path": "$.invoice.items[0].unit",
"code": "INVALID_UNIT_CODE",
"message": "Unknown unit code 'HOURS'. Did you mean 'HUR'?"
}
]
}
See Error Reference for complete error code list.
Best Practices
1. Store API Keys Securely
- Use environment variables (
process.env.THELAWIN_API_KEY) - Never commit keys to version control
- Rotate keys periodically
- Use separate keys for dev/staging/production
2. Implement Retry Logic
Handle transient failures with exponential backoff:
async function generateWithRetry(invoiceData, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await fetch('https://api.thelawin.dev/v1/generate', {
method: 'POST',
headers: { 'X-API-Key': API_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify(invoiceData)
});
if (response.status === 429) {
// Rate limit: wait and retry
const waitMs = Math.pow(2, attempt) * 1000;
await new Promise(resolve => setTimeout(resolve, waitMs));
continue;
}
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
if (attempt === maxRetries - 1) throw error;
}
}
}
3. Validate Before Generating
Use the /v1/validate endpoint for pre-flight checks:
# Validate without generating PDF (no quota consumed)
curl -X POST https://api.thelawin.dev/v1/validate \
-H "X-API-Key: $API_KEY" \
-d @invoice.json
4. Monitor Quota Usage
- Check
account.remainingin every response - Set up alerts when quota drops below 10%
- Track usage in your dashboard
5. Cache Generated PDFs
- Store generated PDFs in your system
- Don't regenerate the same invoice multiple times
- Use invoice number as cache key
Common Pitfalls
❌ Missing X-API-Key Header
Always include X-API-Key header (not Authorization). Common mistake: using Bearer token format.
❌ Incorrect Unit Codes
Use UN/ECE codes (HUR) not words (hours). Unit codes are case-sensitive. See unit codes reference.
❌ Invalid Date Format
Use ISO 8601 format YYYY-MM-DD (e.g., 2026-01-15), not 15.01.2026 or 01/15/2026.
❌ Not Handling Validation Errors
Always check for 422 status and parse details[] array for field-level errors with JSON-Path locations.
❌ Regenerating Same Invoice
Each API call consumes quota. Cache generated PDFs and don't regenerate the same invoice number multiple times.
Next Steps
Generate Endpoint
Full API reference with all parameters
Validate Endpoint
Pre-validation without PDF generation
Templates
Choose between minimal, classic, and compact
Unit Codes
UN/ECE Recommendation 20 unit codes
Authentication
API keys, environments, and security
Error Handling
Complete error code reference