API Reference
Gäld exposes a REST API under /api/v1 for external integrations: ERP connectors, reporting dashboards, automation scripts, etc.
A full interactive API reference with try-it-out is available at /docs on your Gäld instance (e.g. https://app.gaeld.ch/docs). You can also download the OpenAPI 3.0 specification at /docs/openapi.yaml for use with Postman, Insomnia, or code generators.
API access requires the api_access feature flag enabled on your organisation (available on Starter and Business plans, or on any self-hosted instance).
Authentication
Gäld uses Laravel Sanctum bearer tokens. Two token types are available:
| Type | Created by | Scope | Survives user removal? |
|---|---|---|---|
| Personal token | Any user | Tied to user + org | No |
| Organisation token | Admin / Owner | Org-level | Yes |
Create a personal token
curl -X POST https://app.gaeld.ch/api/v1/tokens \
-H "Authorization: Bearer $EXISTING_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "My Integration", "abilities": ["invoicing.view", "contacts.view"]}'
Response:
{
"token": "1|abc123...",
"abilities": ["invoicing.view", "contacts.view"]
}
Use the returned token in subsequent requests:
curl https://app.gaeld.ch/api/v1/invoices \
-H "Authorization: Bearer 1|abc123..."
Create an organisation token
Organisation tokens support a wildcard ability ("*") that grants full access:
curl -X POST https://app.gaeld.ch/api/v1/org-tokens \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "ERP Sync", "abilities": ["*"]}'
Token fields
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Display name (max 255 chars) |
abilities | string[] | No | Permission scopes (see below). Defaults to all. |
expires_in_days | integer | No | Token lifetime in days (1–365). Null = no expiry. |
Token abilities
Token abilities mirror the application permission system. Use GET /api/v1/meta/abilities to retrieve the full list at runtime.
| Scope | Abilities |
|---|---|
| Accounting | accounting.view, accounting.create, accounting.edit, accounting.delete, accounting.close-year, accounting.reopen-year |
| Banking | banking.view, banking.create, banking.edit, banking.delete, banking.import, banking.reconcile |
| Contacts | contacts.view, contacts.create, contacts.edit, contacts.delete |
| Expenses | expenses.view, expenses.create, expenses.edit, expenses.delete, expenses.approve |
| Invoicing | invoicing.view, invoicing.create, invoicing.edit, invoicing.delete, invoicing.finalize, invoicing.record-payment |
| Organisation | organization.view, organization.edit, organization.manage-users, organization.delete, organization.view-audit-log |
| Reporting | reporting.view |
| Payroll | payroll.view, payroll.create, payroll.edit, payroll.delete |
| Migration | migration.import |
Organisation tokens also accept "*" (wildcard) as an ability, which grants all permissions.
Rate Limiting
All API endpoints are rate-limited to 60 requests per minute, keyed by authenticated user ID (or IP address for unauthenticated requests). Exceeding the limit returns 429 Too Many Requests.
Pagination
List endpoints return paginated results using Laravel's LengthAwarePaginator format:
{
"data": [ ... ],
"links": {
"first": "https://app.gaeld.ch/api/v1/invoices?page=1",
"last": "https://app.gaeld.ch/api/v1/invoices?page=5",
"prev": null,
"next": "https://app.gaeld.ch/api/v1/invoices?page=2"
},
"meta": {
"current_page": 1,
"from": 1,
"last_page": 5,
"per_page": 20,
"to": 20,
"total": 97
}
}
Default page size is 20 items. Use ?page=N to navigate pages.
Filtering and sorting
List endpoints support query parameters for filtering and sorting:
# Sort by due_date ascending
GET /api/v1/invoices?sort=due_date
# Sort descending (prefix with -)
GET /api/v1/invoices?sort=-total
# Filter by status
GET /api/v1/invoices?filter[status]=sent
# Full-text search
GET /api/v1/invoices?search=ACME
Available options vary by resource — see each endpoint section below.
Endpoints
Tokens
| Method | Endpoint | Description |
|---|---|---|
GET | /api/v1/tokens | List personal tokens |
POST | /api/v1/tokens | Create a personal token |
DELETE | /api/v1/tokens/{id} | Revoke a personal token |
GET | /api/v1/org-tokens | List organisation tokens |
POST | /api/v1/org-tokens | Create an organisation token |
DELETE | /api/v1/org-tokens/{id} | Revoke an organisation token |
Meta
| Method | Endpoint | Description |
|---|---|---|
GET | /api/v1/meta/abilities | List available token abilities |
GET | /api/v1/meta/webhook-events | List available webhook event types |
Customers
| Method | Endpoint | Description |
|---|---|---|
GET | /api/v1/customers | List customers (paginated) |
GET | /api/v1/customers/{id} | Show customer (includes contact persons) |
POST | /api/v1/customers | Create customer |
PUT | /api/v1/customers/{id} | Update customer |
DELETE | /api/v1/customers/{id} | Delete customer |
Create/Update fields:
| Field | Type | Create | Update | Description |
|---|---|---|---|---|
name | string | Required | Optional | Customer name (max 255) |
email | string | Optional | Optional | Email address |
phone | string | Optional | Optional | Phone number (max 50) |
address | string | Optional | Optional | Street address (max 500) |
city | string | Optional | Optional | City (max 100) |
postal_code | string | Optional | Optional | Postal code (max 10) |
country | string | Optional | Optional | ISO 3166-1 alpha-2 code (e.g. CH) |
vat_number | string | Optional | Optional | VAT identification number (max 50) |
currency | string | Optional | Optional | ISO 4217 code (e.g. CHF) |
payment_terms | string | Optional | Optional | Payment terms text (max 255) |
Response fields:
{
"id": "uuid",
"type": "company",
"name": "ACME GmbH",
"email": "info@acme.ch",
"phone": "+41 44 123 45 67",
"address": "Bahnhofstrasse 1",
"city": "Zürich",
"postal_code": "8001",
"country": "CH",
"vat_number": "CHE-123.456.789",
"currency": "CHF",
"payment_terms": "30 days net",
"contact_persons": [
{
"id": "uuid",
"first_name": "Hans",
"last_name": "Müller",
"full_name": "Hans Müller",
"email": "hans@acme.ch",
"phone": "+41 79 000 00 00",
"position": "CEO",
"is_primary": true,
"notes": null,
"created_at": "2025-01-15T10:00:00.000000Z",
"updated_at": "2025-01-15T10:00:00.000000Z"
}
],
"created_at": "2025-01-15T10:00:00.000000Z",
"updated_at": "2025-01-15T10:00:00.000000Z"
}
Invoices
| Method | Endpoint | Description |
|---|---|---|
GET | /api/v1/invoices | List invoices (paginated) |
GET | /api/v1/invoices/{id} | Show invoice (includes lines, payments, customer) |
POST | /api/v1/invoices | Create invoice |
PUT | /api/v1/invoices/{id} | Update invoice |
DELETE | /api/v1/invoices/{id} | Delete invoice |
Sorting: issue_date (default, desc), due_date, total, number, status
Filters: filter[status], filter[type]
Search: ?search= searches by invoice number and customer name.
Create/Update fields:
| Field | Type | Create | Update | Description |
|---|---|---|---|---|
customer_id | UUID | Required | Optional | Must belong to your organisation |
number | string | Optional | Optional | Invoice number (auto-generated if omitted) |
issue_date | date | Required | Optional | YYYY-MM-DD |
due_date | date | Optional | Optional | Must be ≥ issue_date |
currency | string | Optional | Optional | ISO 4217 (default: org currency) |
notes | string | Optional | Optional | Notes shown on invoice |
payment_terms | string | Optional | Optional | Payment terms text |
lines | array | Required | Optional | At least 1 line item |
lines[].description | string | Required | Required | Line description |
lines[].quantity | decimal | Required | Required | Minimum 0.01 |
lines[].unit_price | decimal | Required | Required | Minimum 0 |
lines[].vat_rate_id | UUID | Optional | Optional | Must belong to your organisation |
Response fields:
{
"id": "uuid",
"number": "INV-2025-001",
"status": "sent",
"type": "invoice",
"related_invoice_id": null,
"customer": { "...": "CustomerResource" },
"issue_date": "2025-01-15",
"due_date": "2025-02-14",
"subtotal": "1000.00",
"vat_amount": "81.00",
"total": "1081.00",
"currency": "CHF",
"notes": null,
"payment_terms": "30 days net",
"amount_paid": "0.00",
"amount_due": "1081.00",
"lines": [
{
"id": "uuid",
"description": "Consulting services",
"quantity": "10.00",
"unit_price": "100.00",
"amount": "1000.00",
"vat_rate_id": "uuid",
"vat_amount": "81.00",
"sort_order": 1
}
],
"payments": [
{
"id": "uuid",
"amount": "500.00",
"payment_date": "2025-02-01",
"payment_method": "bank_transfer",
"reference": "TX-123",
"created_at": "2025-02-01T08:00:00.000000Z"
}
],
"created_at": "2025-01-15T10:00:00.000000Z",
"updated_at": "2025-01-15T10:00:00.000000Z"
}
Expenses
| Method | Endpoint | Description |
|---|---|---|
GET | /api/v1/expenses | List expenses (paginated) |
GET | /api/v1/expenses/{id} | Show expense |
POST | /api/v1/expenses | Create expense |
PUT | /api/v1/expenses/{id} | Update expense |
DELETE | /api/v1/expenses/{id} | Delete expense |
Create/Update fields:
| Field | Type | Create | Update | Description |
|---|---|---|---|---|
category | string | Required | Optional | Category name (max 100) |
amount | decimal | Required | Optional | Minimum 0.01 |
date | date | Required | Optional | YYYY-MM-DD |
description | string | Optional | Optional | Expense description |
vat_amount | decimal | Optional | Optional | VAT amount (min 0) |
vat_rate_id | UUID | Optional | Optional | Must belong to your organisation |
vendor | string | Optional | Optional | Vendor name (max 255) |
currency | string | Optional | Optional | ISO 4217 (default: org currency) |
Response fields:
{
"id": "uuid",
"category": "Office Supplies",
"description": "Printer paper",
"amount": "45.90",
"vat_amount": "3.52",
"date": "2025-01-20",
"vendor": "Büro AG",
"status": "approved",
"currency": "CHF",
"supplier_id": "uuid",
"created_at": "2025-01-20T14:30:00.000000Z",
"updated_at": "2025-01-20T14:30:00.000000Z"
}
Accounts (read-only)
| Method | Endpoint | Description |
|---|---|---|
GET | /api/v1/accounts | List chart of accounts |
GET | /api/v1/accounts/{id} | Show account details |
Response fields:
{
"id": "uuid",
"code": "1020",
"name": "Bank",
"type": "asset",
"parent_id": null,
"is_active": true,
"description": "Main bank account"
}
Bank accounts (read-only)
| Method | Endpoint | Description |
|---|---|---|
GET | /api/v1/bank-accounts | List bank accounts |
GET | /api/v1/bank-accounts/{id} | Show bank account |
Response fields:
{
"id": "uuid",
"name": "PostFinance CHF",
"iban": "CH93 0076 2011 6238 5295 7",
"bank_name": "PostFinance",
"currency": "CHF",
"balance": "12500.00",
"is_active": true,
"account_id": "uuid",
"created_at": "2025-01-01T00:00:00.000000Z",
"updated_at": "2025-03-15T09:00:00.000000Z"
}
Webhooks
| Method | Endpoint | Description |
|---|---|---|
GET | /api/v1/webhooks | List webhooks |
GET | /api/v1/webhooks/{id} | Show webhook |
POST | /api/v1/webhooks | Create webhook |
PUT | /api/v1/webhooks/{id} | Update webhook |
DELETE | /api/v1/webhooks/{id} | Delete webhook |
POST | /api/v1/webhooks/{id}/regenerate-secret | Regenerate signing secret |
Create/Update fields:
| Field | Type | Create | Update | Description |
|---|---|---|---|---|
url | string | Required | Optional | HTTPS endpoint URL (max 2048) |
events | string[] | Required | Optional | At least 1 event (see Webhooks) |
is_active | boolean | Optional | Optional | Default: true |
For webhook events, payload format, delivery, and signature verification, see the dedicated Webhooks page.
Error format
All error responses follow a consistent JSON format:
{
"message": "The given data was invalid.",
"errors": {
"name": ["The name field is required."]
}
}
| HTTP Code | Meaning |
|---|---|
401 | Unauthenticated — invalid or missing token |
403 | Forbidden — token lacks required ability, or feature not enabled |
404 | Resource not found (or belongs to another organisation) |
422 | Validation error — check errors object for field details |
429 | Rate limit exceeded — wait and retry |