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 · machine-readable: /v0/openapi.json
- For scripts and AI agents: the machine-readable spec /v0/openapi.json (complete, no key needed) · this guide as plain text: /llms.txt, /docs/guide.md
1. Quickstart
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 <token>. 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_<env>_<prefix>_<secret> |
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:
{ "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:
- Each PDF becomes one record with
status: setupandintake.channel: email. - The document is read automatically:
intake.statusgoesqueued → processing → completed(usually within a minute) orfailed. Until it is matched,counterparty.namemay benull. - The record then follows the project's approval workflow like any other invoice.
To follow up on what you sent:
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:
{ "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):
{ "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/<type> 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.
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:
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 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:
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.
Changelog
| Date | Version | Change |
|---|---|---|
| 2026-10-01 | 0.1.0 | First release: projects, budgets (structure with rolled-up amounts), records, contractors. |