Projects
Projects group documents (invoices, offers, letters, reminders, order confirmations, incoming invoices) under a single named container. Use them to track work-in-progress for a customer, attach metadata like place and ETA, and surface aggregated status downstream.
Each project can hold at most one document; each document can belong to at most one project.
Endpoints
All project endpoints require
apiKeyAuth.
| Endpoint | Method | Description |
|---|---|---|
/projects | GET | Paginated list of projects. |
/project | POST | Create a new project. |
/project/{id} | GET | Get a single project by id. |
/project/{id} | PATCH | Partially update a project. |
/project/{id} | DELETE | Delete a project. Returns { "succeed": true }. |
/project/{id}/document | POST | Attach a document (invoice, offer, …) to the project. |
/project/{id}/document | DELETE | Detach the document from the project. Returns { "succeed": true }. |
{id} is the project UUID.
Project object
| Field | Type | Description |
|---|---|---|
id | uuid | Project ID. |
name | string (≤ 255 chars) | Project display name. Required on create. |
description | string | null | Free-text description. |
place | string (≤ 255) | null | Free-text location. |
eta | ISO 8601 date-time | null | Estimated time of arrival / completion. |
estMinutes | integer ≥ 0 | null | Estimated effort in minutes. |
currency | string | null | ISO 4217 currency code (e.g. EUR). |
status | enum | null | active, paused, or closed. |
customerId | uuid | null | Linked customer. |
customer | object | null | Embedded { id, name } when customerId is set. |
document | object | null | Embedded document reference (see below) when a document is attached. |
createdAt | ISO 8601 date-time | Creation timestamp. |
updatedAt | ISO 8601 date-time | Last update timestamp. |
The embedded document object carries one populated id (the rest are null):
{
"id": "<assignment uuid>",
"invoiceId": null,
"invoiceIncomingId": null,
"offerId": null,
"letterId": null,
"reminderId": null,
"orderConfirmationId": null
}
Create a project
Only name is required. Any other field can be omitted or sent as null.
Example: Minimal create
curl -X POST \
-H "X-API-KEY: your-api-token" \
-H "Content-Type: application/json" \
-d '{ "name": "Berlin warehouse rollout" }' \
https://api.faktoora.com/api/v1/project
Response (200 OK)
{
"id": "6716007f-26fc-430d-bba6-3aea11d271b7",
"name": "Berlin warehouse rollout",
"description": null,
"place": null,
"eta": null,
"estMinutes": null,
"currency": null,
"status": null,
"customerId": null,
"customer": null,
"document": null,
"createdAt": "2026-05-26T13:41:55.073Z",
"updatedAt": "2026-05-26T13:41:55.073Z"
}
Example: Full create
curl -X POST \
-H "X-API-KEY: your-api-token" \
-H "Content-Type: application/json" \
-d '{
"name": "Berlin warehouse rollout",
"description": "Q3 fit-out, 8 racks",
"place": "Berlin",
"estMinutes": 120,
"currency": "EUR",
"status": "active",
"customerId": "8c5a3f0c-1234-4abc-9def-0123456789ab"
}' \
https://api.faktoora.com/api/v1/project
List projects
GET /projects returns a paginated envelope.
Query parameters (all optional):
| Param | Type | Description |
|---|---|---|
page | integer (≥ 1) | Page number. |
perPage | integer (1–100) | Items per page (default 25). |
keyword | string | Free-text match against name / description. |
status | repeated string | Filter by one or more status values. Repeat the param for multiple values. |
customerId | repeated uuid | Filter by one or more customer IDs. |
sort | enum | name, createdAt, or updatedAt. |
order | enum | asc or desc. |
Example: List active projects for a customer
curl -H "X-API-KEY: your-api-token" \
"https://api.faktoora.com/api/v1/projects?status=active&customerId=8c5a3f0c-1234-4abc-9def-0123456789ab&sort=createdAt&order=desc"
Response (200 OK)
{
"data": [ /* Project objects */ ],
"meta": { "page": 1, "perPage": 25, "totalItems": 1, "totalPages": 1 }
}
Update a project
PATCH /project/{id} accepts the same fields as create, all optional. Only the fields you send are updated.
Example: Close a project
curl -X PATCH \
-H "X-API-KEY: your-api-token" \
-H "Content-Type: application/json" \
-d '{ "status": "closed" }' \
https://api.faktoora.com/api/v1/project/6716007f-26fc-430d-bba6-3aea11d271b7
The response is the updated Project object.
Delete a project
DELETE /project/{id} permanently removes the project. The attached document (if any) is left untouched but is automatically detached.
curl -X DELETE \
-H "X-API-KEY: your-api-token" \
https://api.faktoora.com/api/v1/project/6716007f-26fc-430d-bba6-3aea11d271b7
Response (200 OK)
{ "succeed": true }
Attach / detach a document
Use POST /project/{id}/document to link a document to a project, and DELETE /project/{id}/document to unlink it.
Both endpoints take the same body:
| Field | Type | Description |
|---|---|---|
documentId | uuid | The document's UUID. |
documentType | enum | One of invoice, invoiceincoming, letter, offer, reminder, orderconfirmation. |
The project must be empty before a new document can be attached (one document per project).
Example: Attach an invoice
curl -X POST \
-H "X-API-KEY: your-api-token" \
-H "Content-Type: application/json" \
-d '{
"documentId": "550e8400-e29b-41d4-a716-446655440000",
"documentType": "invoice"
}' \
https://api.faktoora.com/api/v1/project/6716007f-26fc-430d-bba6-3aea11d271b7/document
Response (200 OK)
{
"id": "ddc7c64b-7c11-4a6a-9d40-7a08cba2c5e1",
"projectId": "6716007f-26fc-430d-bba6-3aea11d271b7",
"documentId": "550e8400-e29b-41d4-a716-446655440000",
"documentType": "invoice"
}
Example: Detach a document
curl -X DELETE \
-H "X-API-KEY: your-api-token" \
-H "Content-Type: application/json" \
-d '{
"documentId": "550e8400-e29b-41d4-a716-446655440000",
"documentType": "invoice"
}' \
https://api.faktoora.com/api/v1/project/6716007f-26fc-430d-bba6-3aea11d271b7/document
Response (200 OK)
{ "succeed": true }
Errors
| Status | When |
|---|---|
400 | Validation error — e.g. missing name, invalid status, malformed UUID. |
401 | Missing or invalid X-API-KEY. |
404 | Project ID not found, or referenced customerId / documentId does not exist. |
Error body:
{
"code": "E_VALIDATION",
"statusCode": 400,
"message": "body must have required property 'name'"
}