# Propel API — Guide The Propel API gives read access to your **projects**, their **budgets** and the **records ledger** (the money that moves on a project). It is the same data the Propel app shows, with the same access rules. - **Base URL:** `https://api.propel-industrial.com` (the address you are reading this from) - **Reference (every endpoint and field):** [/docs](https://api.propel-industrial.com/docs) · machine-readable: [/v0/openapi.json](https://api.propel-industrial.com/v0/openapi.json) - **For scripts and AI agents:** the machine-readable spec [/v0/openapi.json](https://api.propel-industrial.com/v0/openapi.json) (complete, no key needed) · this guide as plain text: [/llms.txt](https://api.propel-industrial.com/llms.txt), [/docs/guide.md](https://api.propel-industrial.com/docs/guide.md) --- ## 1. Quickstart ```bash export PROPEL_KEY="pk_test_…" # an API key (see §2) # projects you can access curl -s https://api.propel-industrial.com/v0/projects \ -H "Authorization: Bearer $PROPEL_KEY" # a project's budget: structure + approved / invoiced / paid per node curl -s https://api.propel-industrial.com/v0/projects/{projectId}/budget \ -H "Authorization: Bearer $PROPEL_KEY" # fully approved records (payment pending or paid) of one project, 200 per page curl -s "https://api.propel-industrial.com/v0/records?projectId={projectId}&status=paymentPending,paid&limit=200" \ -H "Authorization: Bearer $PROPEL_KEY" ``` --- ## 2. Authentication Every `/v0` request needs `Authorization: Bearer `. Two kinds of token: | Token | Who | How to get it | |---|---|---| | **Session token** | a signed-in Propel user (e.g. the Propel app itself) | the app's sign-in | | **API key** `pk___` | a script, integration or agent | your Propel administrator creates it — shown **once** | **API keys act on behalf of a user (their owner).** On every request the key gets the owner's **current** access — if the owner loses access to a project, so does the key, immediately. On top of that, a key has its own **ceiling**: - **projects** — optionally limited to a list of projects; - **field groups** — by default `core, amounts, budget, documents` (no bank, tax, payment or notes unless granted explicitly). Keys are bound to one environment (`test` or `live`) and one tenant; a `test` key never works on production. Keys can be revoked instantly; only a hash is stored. Treat keys like passwords — never put them in code, emails or tickets. --- ## 3. Access: projects and field groups You only ever see projects you are entitled to. **A project or record you can't access returns `404`**, exactly like one that doesn't exist — the API never confirms that something exists. Within a project, **field groups** decide which fields you see. Every field in the reference is labelled with its group (`x-field-group`). Fields you may not see are **omitted**, never nulled. | Group | Contains (examples) | admin | user | guest | |---|---|:-:|:-:|:-:| | `core` | ids, names, status, dates of record, counterparty name | ✔ | ✔ | ✔ | | `amounts` | net / tax / gross, budget totals | ✔ | ✔ | – | | `budget` | budget node, structure, codes | ✔ | ✔ | – | | `payment` | due date, payment date | ✔ | ✔ | – | | `documents` | document names, the project's intake address | ✔ | ✔ | – | | `tax` | contractor tax id, registration number | ✔ | ✔ | – | | `bank` | *(not exposed on the API)* | | | | Totals follow the same rule: if you can't see `amounts`, budget totals are not returned either, and **filtering on a field group you can't see is rejected** (`400`) — so nothing can be inferred. --- ## 4. Conventions **Amounts** — always an `Amount` object, never a bare number, never a decimal: ```json { "value": 123456, "currency": "EUR" } // = 1,234.56 EUR ``` `value` is an integer in the currency's smallest unit (cents for EUR). Amounts in different currencies are never added together. **Dates** — calendar dates are `YYYY-MM-DD` in Europe/Vienna (e.g. `recognitionDate`, `cashDate`). Instants, where they appear, are ISO-8601 in UTC (`2026-09-30T14:03:00Z`). **IDs** — opaque strings; don't parse them. Budget nodes also carry a readable `code` (`"07-0007"`) which a company can change — **store the `nodeId`, display the `code`**. **Allocations** — `allocations` says where a record's amount is booked: today one entry (the whole net amount on one budget line). An invoice split across budget lines will carry several entries that add up to `net`; `budgetNode` is a shortcut for the single-line case and is `null` when a record is split. **Records** — the ledger. Each record has `systemKind` (`cost` · `income` · `financing` · `tax`), `direction` (`in` / `out`), `basis` (`actual` / `forecast`) and `source` (`invoice`, …). v0 returns **cost / actual** records from invoices. `status` is the lifecycle (`setup → approvalPending → paymentPending → paid`, or `rejected`); `workflowStep` is the project's own step name (e.g. "Developer Approval"). **Budget structure** — three levels: **budget → sub-budget → cost item**. Each project uses a structure (a company template) and may add project-only nodes or switch template nodes off. `/v0/projects/{id}/budget` returns the project's active nodes, **parents before children**, with `budget` (approved), `invoiced` (all non-rejected invoices, net, *setup included*) and `paid`. **A node's amounts include its own records and all its descendants — total the top level only.** Every `parentId` points at a node in the same response. `?level=budget` returns just the top lines, `?level=subBudget` two levels. Invoiced amounts without a budget node are reported as `unallocated`, approved budget without one as `unallocatedBudget`. All totals are in the project's currency; amounts in any other currency are never added in — they are listed per currency under `excluded`. Budget figures are net; gross is on the record. --- ## 4a. Sending invoices Every project has an **intake address** (`inboundEmail` on the project, needs the `documents` field group). Email invoices there as **PDF attachments** — one email may carry several PDFs: 1. Each PDF becomes **one record** with `status: setup` and `intake.channel: email`. 2. The document is read automatically: `intake.status` goes `queued → processing → completed` (usually within a minute) or `failed`. Until it is matched, `counterparty.name` may be `null`. 3. The record then follows the project's approval workflow like any other invoice. To follow up on what you sent: ```bash curl -s "https://api.propel-industrial.com/v0/records?projectId={projectId}&channel=email&status=setup&limit=200" \ -H "Authorization: Bearer $PROPEL_KEY" ``` Emails without a PDF are ignored. `intake.receivedAt` is when the record was created. --- ## 5. Pagination Lists return: ```json { "data": [ … ], "page": { "nextCursor": "eyJ2Ijox…", "limit": 50 } } ``` **To read a whole list: use `limit=200` and follow `nextCursor` until it is `null`.** `limit` is 1–200 (default 50). A page may hold fewer items than `limit` even when more follow — always rely on `nextCursor`. Records are ordered by `id`, **not by date** — filter by date instead of assuming an order. `GET /v0/projects` always returns all your projects in one page. Cursors are opaque, bound to your credentials and to the same filters — reuse them only with the same query. Pagination is not a frozen snapshot: records added while you page may or may not appear. ## 5a. Filtering records | Parameter | Meaning | |---|---| | `projectId` | one project (`404` if you can't access it, like the project itself). Without it, a call spans all your projects — up to 30; above that, pass `projectId` and query project by project. | | `status` | one or more of `setup, approvalPending, paymentPending, paid, rejected`, comma-separated. The fully approved slice is `status=paymentPending,paid`. | | `invoiceNumber` | exact invoice number | | `contractorId` | one counterparty | | `recognitionDateFrom`, `recognitionDateTo` | invoice date range, inclusive, `YYYY-MM-DD` — e.g. a month or a quarter | | `budgetNodeId` | records on **exactly** that node; add `includeDescendants=true` for the node and everything below it | | `channel` | how the record came in: `email`, `app`, `chat`, `api`, `manual` | **Unknown or empty parameters are rejected with `400 invalid-query`** — a typo never silently returns unfiltered data. The API is read-only: any method other than `GET` returns `405`. --- ## 6. Errors Errors are an `Error` object, sent as `application/problem+json` ([RFC 9457](https://www.rfc-editor.org/rfc/rfc9457)): ```json { "type": "/problems/not-found", "title": "Not found", "status": 404, "requestId": "8f2c…" } ``` | Status | `type` (suffix) | Meaning | |---|---|---| | 400 | `invalid-query`, `invalid-body`, `invalid-cursor`, `filter-not-allowed`, `too-many-projects` | fix the request (unknown/empty parameters included) | | 405 | `method-not-allowed` | data endpoints are read-only; `POST /v0/feedback` is the one write | | 401 | `authentication-required`, `invalid-credentials` | missing, wrong, revoked or expired token | | 404 | `not-found` | doesn't exist **or** you can't access it | | 429 | `too-many-requests` | slow down: at most 300 requests per minute per key or user; wait `Retry-After` seconds | | 500 | `internal-error` | our fault — quote the `requestId` | `type` is a stable, relative URI — branch your code on it; `GET https://api.propel-industrial.com/problems/` explains each one. Every response carries an `x-request-id` header; quote it when reporting a problem. --- ## 7. Endpoints (v0, read-only) | Method | Path | Returns | |---|---|---| | GET | `/v0/projects` | projects you can access | | GET | `/v0/projects/{projectId}` | one project | | GET | `/v0/projects/{projectId}/budget` | budget structure with approved / invoiced / paid per node | | GET | `/v0/records` | records (filters: `projectId`, `status`, `budgetNodeId`; paginated) | | GET | `/v0/records/{recordId}` | one record | | GET | `/v0/contractors` | contractors linked to your projects (core + tax; **no bank data**) | Full request/response schemas: [/docs](https://api.propel-industrial.com/docs). --- ## 7a. Client libraries There are no official client libraries yet. The OpenAPI document is complete and resolvable, so you can generate a typed client in a minute, e.g. for TypeScript: ```bash npx openapi-typescript https://api.propel-industrial.com/v0/openapi.json -o propel.d.ts ``` (Python/others: `openapi-generator-cli generate -i https://api.propel-industrial.com/v0/openapi.json -g python`.) The reference at [/docs](https://api.propel-industrial.com/docs) shows copy-paste examples per endpoint (curl, JavaScript, Node, Python). ## 7b. Feedback Something wrong, missing or unclear? Tell us directly — it reaches the Propel team immediately: ```bash curl -s -X POST "https://api.propel-industrial.com/v0/feedback" \ -H "Authorization: Bearer $PROPEL_KEY" -H "Content-Type: application/json" \ -d '{ "category": "missing", "message": "I need to filter records by contractor name.", "endpoint": "GET /v0/records", "agent": { "name": "my-reporting-agent", "model": "…" } }' ``` `category` is one of `bug`, `missing`, `confusing`, `idea`, `praise`. Add the `requestId` of a call that went wrong. **AI agents:** please send feedback when a field or filter you need is missing, a response surprised you, or these docs were unclear. Never include keys or secrets. Up to 20 messages per day. ## 8. Stability and versioning - The version is in the path (`/v0`). v0 is the first version of the API. - We avoid breaking changes: new endpoints, new optional fields and new enum values are added without notice — treat unknown fields and enum values gracefully. If a breaking change is unavoidable in v0, it is announced in advance in the changelog. - From v1 on, changes within a version are additive only; anything breaking goes to the next version. - Not available in v0: write endpoints (except feedback), webhooks, PDF downloads, bank data. ## Privacy The API returns your organisation's project data to callers you authorise. How Propel handles personal data: [propel-industrial.com/privacy-policy](https://www.propel-industrial.com/privacy-policy). ## Changelog | Date | Version | Change | |---|---|---| | 2026-10-01 | 0.1.0 | First release: projects, budgets (structure with rolled-up amounts), records, contractors. |