Planix APIDeveloper guide, version 1
Planix API, version 1

Connect your ERP, Power BI or Excel to Planix

Read productivity, plant and delay data from Planix with a simple REST API, and download month-end journals that your finance team can import into SAP, Oracle, Dynamics, Tally or any other system. Every answer is JSON or CSV, every key reads one company only.

1Create a key

Your company administrator opens API keys, names the key and ticks what it may read. The key is shown once.

2Call the API

Send the key in the Authorization header over HTTPS. Start with /status to check it works.

3Set up finance exports

On the same page, set account codes, cost centres and column names once. Exports then come out ready to import.

Overview

Base address for every call:

https://pace.planixengineering.com/pace-backend/api-v1.php/<endpoint>

If your server or proxy drops the part after .php, use the same endpoint as a parameter: api-v1.php?route=assets&page=2. Both forms behave the same.

AreaEndpointsPermission (scope)
Productivity Intelligenceprojects, activities, daily-reports, manpower, productivity/earned-vs-actual, productivity/monthly-loss, exports/labour-costread:productivity
Asset Intelligenceassets, asset-projects, asset-documents, timesheets, meter-readings, hires, charges, consumable-issues, exports/plant-rechargeread:assets
Root Cause Intelligenceprogrammes, programme-updates, delay-events, delay-windowsread:rootcause
TelematicsPOST meter-readingswrite:meters
Any keystatus, exports/mappingsnone
Two kinds of project id. Productivity Intelligence projects (projects) and Asset Intelligence projects and stores (asset-projects) are separate lists with their own ids. Plant charges and timesheets use asset project ids; reports, BOQ and labour cost use productivity project ids. Map both to your ERP cost centres once on the API keys page.

Authentication

Each company has its own keys. A company administrator (a Planix Access user with the admin role and two-step sign-in turned on) creates and revokes them on API keys. A key looks like this and is shown only once:

plxk_rg4mhrmumq_LRD1xOG6U6W64FxkScuMHG54Kc7XAyXPsOzn3udS

The first part (plxk_rg4mhrmumq) identifies the key in lists and logs. Planix stores only a SHA-256 hash of the full key, so a lost key cannot be recovered: revoke it and create a new one.

Send the key in one of three ways

# 1. Bearer token (recommended for code and ERP connectors)
curl -H "Authorization: Bearer plxk_xxxxxxxxxx_..." https://pace.planixengineering.com/pace-backend/api-v1.php/status

# 2. Custom header
curl -H "X-Planix-Key: plxk_xxxxxxxxxx_..." https://pace.planixengineering.com/pace-backend/api-v1.php/status

# 3. HTTP Basic: any user name (for example api), the key as the password.
#    Use this in Power BI and Excel, which store the password safely.
curl -u "api:plxk_xxxxxxxxxx_..." https://pace.planixengineering.com/pace-backend/api-v1.php/status

Scopes

ScopeAllows
read:productivityProjects, BOQ activities, daily report summaries, manpower, earned and actual man-days, monthly productivity loss, the labour cost export.
read:assetsAsset register, documents and expiry dates, plant timesheets, meter readings, outside hire, monthly charge sheet lines, consumable issues, the plant recharge export.
read:rootcauseProgrammes, programme updates, delay events with responsibility and entitlement, windows analysis.
write:metersPosting hour meter and odometer readings from a telematics system. Nothing else can be written through the API.

Check a key

GET /status

{
  "api": "Planix API",
  "version": "v1",
  "time": "2026-10-10T15:38:09+03:00",
  "company": { "id": 2, "name": "Test Contracting" },
  "key": { "name": "SAP nightly sync", "prefix": "plxk_rg4mhrmumq", "scopes": ["read:assets", "read:productivity"] },
  "rate_limit": { "per_minute": 120 },
  "endpoints": ["projects", "activities", "..."],
  "docs": "https://pace.planixengineering.com/API-GUIDE.html"
}
HTTPS only. Always call https://. The server redirects plain HTTP to HTTPS, but by then the key has already crossed the network unencrypted. Keep keys out of URLs, e-mail and shared spreadsheets.

Data conventions

  • Field names are snake_case and stay the same in JSON and CSV. Every row of an endpoint has the same fields in the same order; unknown values are null.
  • Dates are ISO 8601: 2026-09-30. Date-times carry the offset: 2026-09-30T17:00:00+03:00 (Bahrain time unless your server is set otherwise).
  • Months are YYYY-MM, for example month=2026-09.
  • Money is a plain number next to a currency code (BHD, SAR, AED, QAR, OMR, KWD, INR...). Planix never converts currencies. Productivity uses each project's currency; assets use the company currency set in Asset Intelligence.
  • Booleans are true/false in JSON and in CSV.
  • Lists inside a row (for example driving event ids) are JSON arrays; in CSV they are joined with ;.

JSON envelope

{
  "data": [ { ... }, { ... } ],
  "meta": { "page": 1, "per_page": 100, "total": 523, "total_pages": 6, "has_more": true,
            "generated_at": "2026-10-10T15:38:09+03:00" },
  "links": { "self": "...?page=1", "next": "...?page=2", "prev": null }
}

CSV

Add format=csv, or end the endpoint with .csv (/assets.csv). CSV is UTF-8 with a byte order mark so Excel shows Arabic and other non-English text correctly; add bom=0 to leave it out. The first row holds the field names. A text value that starts with =, +, - or @ is prefixed with ' so a spreadsheet never runs it as a formula.

Pagination

List endpoints return pages. Use page (from 1) and per_page (default 100, maximum 500; larger values are reduced to 500).

GET /activities?project_id=6&page=2&per_page=500
  • meta.total, meta.total_pages and meta.has_more tell you whether to ask for the next page.
  • links.next is the full address of the next page (also sent as an HTTP Link header, rel="next"). It is null on the last page.
  • The X-Total-Count header carries the total as well.
  • A page past the end returns an empty data list, not an error.

Flat feeds for BI tools

Put feed/ in front of any list endpoint to get every row in one answer, without paging (up to 100,000 rows; the header X-Planix-Truncated: 1 warns if there were more). Feeds take the same filters and both formats:

GET /feed/timesheets.csv?from=2026-01-01
GET /feed/delay-events?format=json

Incremental sync

For a nightly ERP or data warehouse load, ask only for what changed since your last run with updated_since. It accepts a date or a date-time with offset:

GET /assets?updated_since=2026-10-09T02:00:00+03:00
GET /delay-events?updated_since=2026-10-09T23:00:00Z
GET /activities?updated_since=2026-10-01

Recommended pattern: store meta.generated_at from the first page of each run and use it, minus five minutes, as updated_since next time. Update rows in your system by their id (or the key listed below).

EndpointWhat counts as changedUnique key
projects, activities, assets, asset-projects, asset-documents, hires, programmes, delay-eventsAny edit to the row (updated_at)id
daily-reports, manpowerAny save, output entry, approval or rejection of the day's reportproject_id + report_date (+ line)
productivity/earned-vs-actual, productivity/monthly-lossWhole months in which any report changed are returned again, fully recalculatedproject_id + month (+ activity_id)
timesheetsEntry or edit of the day sheetid (one per asset per day)
meter-readings, consumable-issues, programme-updatesNew rows (these records are not edited)id
charges, delay-windowsNot supported: always read a whole month or programmemonth + reference / programme_id + seq
Deleted rows do not appear in an incremental answer. Run a full load (without updated_since) once a week, or at month end, and remove rows your system has that Planix no longer returns. For month-based figures, reload the current and previous month after every month-end lock.

Errors

Errors use normal HTTP status codes and a small JSON body. The code is stable; the error text is for people and may change.

HTTP/1.1 403 Forbidden
{ "error": "This key does not have the \"read:assets\" scope. Your administrator can create a key that has it.",
  "code": "missing_scope", "status": 403, "required_scope": "read:assets" }
StatuscodeMeaning and what to do
400invalid_parameter, missing_parameter, invalid_bodyA parameter is missing or in the wrong format (dates, months, numbers). Fix the request.
401missing_key, invalid_key, revoked_keyNo key, a wrong key, or a key that was revoked. The answer carries WWW-Authenticate: Basic so Excel and Power BI ask for credentials.
403missing_scope, company_suspendedThe key may not read this area, or the company account is suspended.
404not_foundUnknown endpoint, or an id (project, asset, programme) that does not belong to your company.
405method_not_allowedUse GET (POST only for meter readings).
413 / 422too_many_readingsMeter readings: more than 1,000 in one call, or none could be accepted.
429rate_limited, too_many_failed_keysSlow down. Wait the number of seconds in Retry-After.
500noneA server fault. The detail is logged for Planix support, not sent to you. Retry later; contact support if it continues.

Rate limits

Each key may make 120 requests per minute. Every answer tells you where you stand:

X-RateLimit-Limit: 120
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1791634920      # Unix time when the minute window resets

Over the limit you get 429 with Retry-After in seconds. Use per_page=500 or a feed/ to need fewer calls. Separate systems should use separate keys; one busy key does not slow the others.

To protect against guessing, an address that sends 20 wrong keys within 15 minutes is refused for 15 minutes (too_many_failed_keys).

Productivity endpoints

All need read:productivity. Figures use the same maths as Productivity Intelligence: man-days are worker-days booked against BOQ activities in the daily reports, earned man-days are output divided by the activity benchmark (output per man-day), and the loss is extra man-days x trade daily wage x (1 + supervision factor).

List projects

GET/projectsread:productivity

Filters: id, status (active, onhold, completed), updated_since.

{ "data": [ {
  "id": 6, "name": "Alpha Tower", "employer": "Alpha Client", "location": "Manama",
  "start_date": "2026-07-01", "end_date": "2027-06-30", "revised_end_date": null, "status": "active",
  "currency": "BHD", "boq_value": 131650, "daily_wage": 9, "supervision_factor": 0.35,
  "created_at": "2026-10-10T15:37:27+03:00", "updated_at": "2026-10-10T15:37:27+03:00" } ], "meta": { ... }, "links": { ... } }

BOQ activities

GET/activitiesread:productivity

BOQ items with codes, quantities, rates and the productivity benchmark. Filters: project_id, discipline (civil, mep), active (1 current BOQ, 0 items retired by a re-import), updated_since.

{ "id": 1, "project_id": 6, "code": "C-100", "discipline": "civil", "description": "Blockwork 200mm",
  "unit": "m2", "boq_qty": 5000, "rate": 7.5, "boq_amount": 37500, "currency": "BHD", "trade": "Mason",
  "std_output_per_day": 10, "crew_skilled": null, "crew_unskilled": null, "std_output_per_man_day": 10,
  "labour_type": "own", "subcontractor": "", "active": true, "updated_at": "2026-10-10T15:37:27+03:00" }

When a standard crew is set, std_output_per_day is per crew and std_output_per_man_day divides it by the crew size.

Daily report summaries

GET/daily-reportsread:productivity

One row per project per day. Filters: project_id, from, to (dates), updated_since.

{ "project_id": 6, "report_date": "2026-08-03", "weather": "Hot & Dry", "temperature": "41",
  "headcount": 13, "civil_man_days": 11, "mep_man_days": 5, "total_man_days": 16,
  "benchmarked_man_days": 14, "earned_man_days": 12.194, "non_productive_man_days": 1,
  "output_entered": true, "approved": true, "approved_by": "R. Menon (pm)", "approved_at": "2026-08-04T09:12:00+03:00",
  "rejected": false, "status": "approved", "updated_at": "2026-08-04T09:12:31+03:00" }

status is draft, output_entered, approved or rejected. Non-productive man-days (housekeeping, idle, shifting) are not in total_man_days.

Manpower by trade per day

GET/manpowerread:productivity

The timekeeper's site headcount. One row per trade line. Filters: project_id, from, to, updated_since.

{ "project_id": 6, "report_date": "2026-08-03", "line": 2, "trade": "Plasterer", "skilled": 3, "unskilled": 1, "total": 4,
  "labour_type": "subcontract", "subcontractor": "Gulf Plaster WLL", "subcontract_scope": "Plaster",
  "recorded_by": "T. Roy", "updated_at": "2026-08-03T18:02:11+03:00" }

Earned versus actual man-days

GET/productivity/earned-vs-actualread:productivity

Per project, month and BOQ activity, the same table as Monthly Analysis. Filters: project_id, month, from_month, to_month, updated_since.

{ "project_id": 6, "month": "2026-08", "activity_id": 1, "activity_code": "C-100", "description": "Blockwork 200mm",
  "discipline": "civil", "unit": "m2", "trade": "Mason", "output_qty": 245, "actual_man_days": 28,
  "std_output_per_man_day": 10, "earned_man_days": 24.5, "efficiency": 0.875, "extra_man_days": 3.5,
  "loaded_day_rate": 14.85, "loss_amount": 51.975, "currency": "BHD", "benchmarked": true }

efficiency is earned / actual (1.0 = on benchmark). Activities without a benchmark have benchmarked: false and no earned figures.

Monthly productivity loss

GET/productivity/monthly-lossread:productivity

One row per project per month. Filters: project_id, month, from_month, to_month, updated_since.

{ "project_id": 6, "project_name": "Alpha Tower", "month": "2026-09", "reports": 5,
  "actual_man_days": 104, "benchmarked_man_days": 94, "earned_man_days": 89.472, "extra_man_days": 9.528,
  "efficiency": 0.9518, "non_productive_man_days": 7, "loss_amount": 130.613, "currency": "BHD" }

Asset endpoints

All need read:assets; posting meter readings needs write:meters.

Asset register

GET/assetsread:assets

Plant, vehicles, tools and stock items. Filters: id, code, category, status (in_store, issued, retired), item_type (asset, stock), updated_since.

{ "id": 1, "code": "AL-EX01", "name": "Excavator 20T", "category": "plant", "item_type": "asset", "owner_type": "owned",
  "status": "issued", "operating_status": "operational", "condition": "good", "identifier": "SN-1",
  "location": "Alpha Tower Site", "store_location": "Central Store", "bin_location": "", "stock_qty": 1, "min_qty": 0,
  "uom": "each", "daily_cost": 40, "rental_rate": 55, "purchase_price": 60000, "purchase_date": "2023-01-10",
  "useful_life_years": 5, "supplier": "", "hire_contract_end": null, "expiry_date": null, "currency": "BHD",
  "created_at": "2026-01-12T10:00:00+03:00", "updated_at": "2026-10-10T15:37:27+03:00" }

Asset projects, stores and locations

GET/asset-projectsread:assets

The project list used by gate passes, timesheets and the charge sheet. Filters: kind (project, store, location), updated_since.

{ "id": 1, "name": "Alpha Tower Site", "kind": "project", "location": "Manama", "status": "active",
  "created_at": "2026-10-10T15:37:27+03:00", "updated_at": "2026-10-10T15:37:27+03:00" }

Documents with expiry

GET/asset-documentsread:assets

Registration, insurance, inspection, licence and warranty records. File contents are not sent; has_file says whether a scan is stored in Planix. Filters: asset_id, doc_type, expiring_within (days, includes expired), updated_since.

GET /asset-documents?expiring_within=30

{ "id": 1, "asset_id": 1, "asset_code": "AL-EX01", "asset_name": "Excavator 20T", "doc_type": "insurance",
  "doc_number": "INS-AL", "authority": "GIG", "issue_date": "2025-11-01", "expiry_date": "2026-10-30",
  "days_to_expiry": 20, "status": "expiring", "has_file": false, ... }

status: expired, expiring (30 days or less), valid, no_expiry.

Plant timesheets (hours)

GET/timesheetsread:assets

One row per machine per day. Filters: asset_id, project_id (asset project), from, to, updated_since.

{ "id": 1, "asset_id": 1, "asset_code": "AL-EX01", "asset_name": "Excavator 20T", "project_id": 1,
  "project_name": "Alpha Tower Site", "work_date": "2026-09-14", "hours_worked": 8, "idle_hours": 1,
  "breakdown_hours": 0, "operator": "K. Das", "fuel_litres": 60, "meter_end": 1208, "meter_unit": "hours",
  "source": "manual", "created_at": "...", "updated_at": "..." }

Meter readings

GET/meter-readingsread:assets

Filters: asset_id, from, to, updated_since (time received).

{ "id": 7, "asset_id": 1, "asset_code": "AL-EX01", "reading_time": "2026-09-30T17:00:00+03:00",
  "hour_meter": 1300.5, "odometer_km": null, "lat": 26.2, "lng": 50.58, "source": "api-v1",
  "received_at": "2026-09-30T17:02:04+03:00" }
POST/meter-readingswrite:meters

For telematics providers or your own script. Up to 1,000 readings per call. Assets are matched by code (spaces and case ignored) within your company only. A reading already received (same asset, time and source) is counted as a duplicate and ignored.

POST /meter-readings
Content-Type: application/json
Authorization: Bearer plxk_...

{ "readings": [
  { "asset_code": "AL-EX01", "reading_time": "2026-09-30T17:00:00+03:00", "hour_meter": 1300.5, "lat": 26.2, "lng": 50.58 },
  { "asset_code": "AL-VH01", "reading_time": 1790000000, "odometer_km": 4500, "source": "fleet-gps" } ] }

200 OK
{ "ok": true, "received": 2, "accepted": 2, "duplicates": 0, "rejected": [] }

reading_time is ISO 8601 or Unix seconds. Send hour_meter, odometer_km or both. Rejected rows are listed with their index and reason; the rest are saved.

Outside hire

GET/hiresread:assets

Hire records (needed, approved, closed). Filters: status (hire_needed, approved_hire, hire_closed), project_id, updated_since.

{ "id": 1, "asset_name": "Mobile crane 50T", "category": "Hire", "project_id": 1, "project_name": "Alpha Tower Site",
  "supplier": "Gulf Cranes", "status": "hire_closed", "need_from": "2026-09-18", "need_to": "2026-11-09",
  "off_hire_date": null, "used_until": "2026-09-25", "days_used": 7, "rate": 300, "rate_unit": "day",
  "total_charge": 1500, "currency": "BHD", "approved_at": "...", "created_at": "...", "updated_at": "..." }

Monthly charge sheet lines (plant cost recharge)

GET/charges?month=YYYY-MMread:assets

The lines behind Asset Intelligence > Commercial > Charge sheet, per project. This is what ERP job costing needs. month is required. Filters: project_id (asset project), line_type.

  • equipment: own plant on a gate pass, chargeable days (days on site less breakdown and approved pause days) x day rate.
  • hire: outside hire passed through, in the month the hire ended.
  • consumable: stock issued on a gate pass, quantity x unit value.
  • extra: manual lines such as mobilisation. A line with no project has project_id: null.

When commercial has locked (invoiced) a project-month, its lines are returned exactly as frozen, with locked: true and the invoice_no. Open months are live and can still change. Internal workshop costs are owner costs and are not recharge lines.

GET /charges?month=2026-09&project_id=2

{ "data": [ { "month": "2026-09", "project_id": 2, "project_name": "Alpha Villas Site", "line_type": "equipment",
  "reference": "RQ-2", "asset_code": "AL-CR01", "description": "AL-CR01 Tower crane", "date_from": "2026-09-01",
  "date_to": "2026-09-30", "days": 29, "pause_days": 3, "chargeable_days": 26, "quantity": 26, "uom": "day",
  "rate": 120, "amount": 3120, "currency": "BHD", "locked": true, "invoice_no": "INV-2-2-202609" } ],
  "meta": { "month": "2026-09", "total_amount": 3120, "currency": "BHD", ... } }

Consumable issues

GET/consumable-issuesread:assets

Every stock issue from the stores ledger. Issues made on a gate pass (GPO- reference) carry the project and appear on the charge sheet. Filters: month, from, to, project_id, updated_since.

{ "id": 1, "movement_date": "2026-09-10", "asset_id": 4, "item_code": "AL-CEM", "item_name": "Cement bag 50kg",
  "quantity": 40, "uom": "bag", "unit_value": 2.5, "amount": 100, "currency": "BHD", "reference": "GPO-1",
  "project_id": 1, "project_name": "Alpha Tower Site", "on_charge_sheet": true, "created_at": "..." }

Root cause endpoints

All need read:rootcause. Programmes are the schedules logged in Root Cause Intelligence; each update (P6 data date) adds a row to programme-updates and opens, repeats or closes delay events.

Programmes

GET/programmesread:rootcause
{ "id": 1, "name": "Alpha Master Programme", "project_id": 6, "project_label": "Alpha Tower",
  "baseline_finish": "2027-06-30", "notice_days": 28, "updates": 3, "last_data_date": "2026-09-26",
  "open_events": 2, "created_at": "...", "updated_at": "..." }

project_id is the productivity project the programme is linked to (may be null).

Programme updates

GET/programme-updatesread:rootcause

Filters: programme_id, updated_since.

{ "id": 3, "programme_id": 1, "seq": 3, "data_date": "2026-09-26", "finish_forecast": "2027-07-12",
  "completion_date": "2027-07-12", "neg_float_count": 2, "worst_float_days": -12, "critical_count": 2,
  "total_activities": 400, "created_at": "..." }

Delay events

GET/delay-eventsread:rootcause

The claims-grade delay register. Filters: programme_id, status (open, closed), responsibility, entitlement, updated_since.

{ "id": 1, "programme_id": 1, "activity_code": "A100", "activity_name": "Shop drawings approval", "wbs": "",
  "status": "closed", "first_seen_update": 1, "first_data_date": "2026-08-01", "closed_update": 3,
  "closed_data_date": "2026-09-26", "activity_start": null, "times_seen": 2, "worst_float_days": -15,
  "last_float_days": -15, "cause": "Approvals / NOC", "notes": "",
  "responsibility": "Employer", "entitlement": "Excusable and compensable",
  "notice_ref": "NOT-A100", "notice_date": "2026-08-10", "time_bar_date": "2026-08-29",
  "evidence_count": 0, "created_at": "...", "updated_at": "..." }

responsibility: Employer, Contractor, Neutral, Concurrent, Not assessed. entitlement: Excusable and compensable, Excusable not compensable, Not excusable, Not assessed. time_bar_date is the first data date plus the programme's notice days.

Windows analysis

GET/delay-windowsread:rootcause

Completion movement between consecutive updates (the first window starts at the baseline when one is set), attributed to the driving delay events: the planner's manual attribution, otherwise the delay origin with the worst float at the end of the window. Filter: programme_id.

{ "programme_id": 1, "seq": 2, "window": "U1 to U2", "from_update": "U1", "to_update": "U2",
  "from_data_date": "2026-08-01", "to_data_date": "2026-08-29", "completion_from": "2027-07-05",
  "completion_to": "2027-07-15", "movement_days": 10, "cumulative_days": 15,
  "driving_event_ids": [1], "driving_event_codes": ["A100"], "attribution": "automatic", "responsibility": "Employer" }

A window with events of both parties shows Concurrent. This is a contemporaneous record to support a windows analysis; it does not replace the method your contract requires.

ERP exports (journal-ready)

Two month-end exports produce lines your finance team can import as a journal, in CSV or JSON, using your own column names and account codes.

ExportAddressScopeWhat it posts
Plant recharge/exports/plant-recharge?month=2026-09read:assetsThe monthly charge sheet per project: debit each project's cost centre, credit your plant recovery account.
Labour productivity cost/exports/labour-cost?month=2026-09read:productivityThe cost of man-days above the benchmark per project (or per trade): debit a productivity variance account.

Add format=csv (or .csv) for a file. Optional: detail=1 for one line per charge (or per trade) instead of a summary per project, layout=single|double to try a layout without saving it, and for labour cost project_id.

Default columns

FieldContent
journal_dateLast day of the month
periodYYYY-MM
document_refInvoice number of a locked month, else PLX-PR-<company>-<project>-<YYYYMM> (labour: PLX-LC-...). Stable, so a re-import can be detected.
line_noLine number inside the document
cost_centreYour code for the project (set once), else the project name; lines with no project use UNALLOCATED or your own code
project_id, project_namePlanix project
line_type / tradeequipment, hire, consumable, extra / trade name
debit_account, credit_accountYour account codes (single-line layout)
account, debit, creditTwo-line layout: one debit line and one balancing credit line per entry
descriptionNarration, with your optional prefix
amount, currencyAmount and currency code
plant detail onlyreference, asset_code, quantity, uom, rate, locked
labour onlyactual_man_days, earned_man_days, extra_man_days, loaded_day_rate

Example: single-line layout, summary

GET /exports/plant-recharge.csv?month=2026-09

journal_date,period,document_ref,cost_centre,project_id,project_name,line_type,debit_account,credit_account,description,...,amount,currency,locked
2026-09-30,2026-09,PLX-PR-2-1-202609,CC-ALPHA-01,1,Alpha Tower Site,equipment,510100,410500,Plant recharge 2026-09 Equipment (1 line) Alpha Tower Site,...,1100,BHD,false
2026-09-30,2026-09,PLX-PR-2-1-202609,CC-ALPHA-01,1,Alpha Tower Site,hire,510200,410500,Plant recharge 2026-09 Outside hire (1 line) Alpha Tower Site,...,1500,BHD,false
2026-09-30,2026-09,INV-2-2-202609,CC-ALPHA-02,2,Alpha Villas Site,equipment,510100,410500,Plant recharge 2026-09 Equipment (1 line) Alpha Villas Site,...,3120,BHD,true

Example: two-line layout, renamed columns, semicolon separator

PostingDate;CostCenter;GLAccount;Debit;Credit;Narration;Cur;DocNo;Line
30/09/2026;CC-ALPHA-01;510100;1100;0;Plant recharge 2026-09 Equipment (1 line) Alpha Tower Site;BHD;PLX-PR-2-1-202609;1
30/09/2026;CC-ALPHA-01;410500;0;1100;Plant recharge 2026-09 Equipment (1 line) Alpha Tower Site;BHD;PLX-PR-2-1-202609;2

The JSON form returns the same rows with your headers as keys, and meta.total_amount, meta.lines and (two-line layout) meta.balanced so an integration can check the journal before posting it.

Totals you can trust. The plant recharge total equals the Asset Intelligence charge sheet for the month (all projects, less internal workshop lines). The labour cost total equals the loss in Productivity Intelligence Monthly Analysis for the same project and month.

Column mapping

A company administrator sets the mapping once on API keys under Finance export settings, separately for each export:

  • which columns to include, their order and their header names (to match your ERP import template);
  • account codes per charge type and the balancing (credit) account;
  • a cost centre code per project and one for unallocated lines;
  • single-line or two-line layout, summary or detail, date format (2026-09-30, 30/09/2026, 09/30/2026, 30.09.2026, 30-09-2026, 20260930) and CSV separator (comma, semicolon, tab, bar);
  • a short narration prefix.

The page has a preview for any month. Integrations can read the saved settings with GET /exports/mappings.

SAP, Oracle, Dynamics, Tally and others

Planix does not need to know which ERP you use. Pick the layout your import tool expects, rename the columns to its template, and schedule a download after month-end. Typical starting points (check them against your own import template and chart of accounts):

SystemSuggested setting
Microsoft Dynamics 365 Business Central / NAVSingle-line layout: general journal lines carry an account and a balancing account. Map debit_account to the account column, credit_account to the balancing account, cost_centre to your project or department dimension.
SAP (S/4HANA, Business One)Two-line layout: journal entry uploads expect one line item per account with debit and credit amounts. Map cost_centre to the cost centre or WBS element, document_ref to the reference.
Oracle (Fusion, NetSuite, JD Edwards)Two-line layout into the journal import template; period to the accounting period, cost_centre to the segment or department you use for jobs.
TallyTwo-line layout, comma separator, date format 30-09-2026 or the one your import utility needs; import the CSV through your Excel or XML voucher import tool as journal vouchers, with cost_centre as the cost centre.
Zoho Books, Odoo, QuickBooks, SageTwo-line layout with Debit and Credit columns usually matches their manual journal import.

For a fully automatic link, an ERP integration (or a small script on your server) calls the JSON export after month-end, checks meta.balanced and meta.total_amount, and posts the journal. Locked months never change, so posting them once is safe.

Power BI

Quickest: a CSV feed with Basic authentication

  1. In Power BI Desktop choose Get data, then Web.
  2. URL: https://pace.planixengineering.com/pace-backend/api-v1.php/feed/timesheets.csv
  3. When asked for credentials choose Basic. User name: api. Password: your key. Set the level to the API address so one entry covers every feed.
  4. Repeat for other feeds (feed/assets.csv, feed/daily-reports.csv, feed/delay-events.csv ...) and relate them by id.

The credentials are stored by Power BI, not in the report, and scheduled refresh in the Power BI service works with the same Basic credentials.

JSON with paging (Power Query M)

For very large tables, read 500 rows a page. RelativePath and Query keep the base address fixed, which the Power BI service needs for scheduled refresh.

let
    Base    = "https://pace.planixengineering.com/pace-backend/api-v1.php/",
    GetPage = (endpoint as text, page as number) as record =>
        Json.Document(Web.Contents(Base, [
            RelativePath = endpoint,
            Query = [ page = Text.From(page), per_page = "500" ]
        ])),
    First   = GetPage("assets", 1),
    Pages   = List.Numbers(1, First[meta][total_pages]),
    Rows    = List.Combine(List.Transform(Pages, each GetPage("assets", _)[data])),
    Result  = Table.FromRecords(Rows)
in
    Result

Month-based tables

let
    Month  = "2026-09",
    Source = Csv.Document(Web.Contents("https://pace.planixengineering.com/pace-backend/api-v1.php/",
                 [RelativePath = "exports/plant-recharge.csv", Query = [ month = Month ]]),
                 [Delimiter = ",", Encoding = 65001, QuoteStyle = QuoteStyle.Csv]),
    Table  = Table.PromoteHeaders(Source, [PromoteAllScalars = true])
in
    Table

Excel Power Query

  1. Data, Get Data, From Other Sources, From Web.
  2. Paste a feed address, for example https://pace.planixengineering.com/pace-backend/api-v1.php/feed/asset-documents.csv?expiring_within=60
  3. In the access dialog pick Basic, user name api, password your key, and apply it to the API address.
  4. Choose Load. Use Refresh All to update later.

Prefer a header to Basic authentication? Choose Advanced in step 2 and add the header X-Planix-Key with your key, with the credentials set to Anonymous. Note that the key is then saved inside the workbook, so do not share that file.

Anyone with the workbook and the stored credentials can refresh it. Use a key with only the scope the workbook needs, and revoke it when the workbook is retired.

Code samples

curl

curl -sS -H "Authorization: Bearer $PLANIX_KEY" \
  "https://pace.planixengineering.com/pace-backend/api-v1.php/exports/plant-recharge?month=2026-09&format=csv" \
  -o plant-recharge-2026-09.csv

Python: full sync with paging and rate limits

import os, time, requests

BASE = "https://pace.planixengineering.com/pace-backend/api-v1.php/"
HEAD = {"Authorization": "Bearer " + os.environ["PLANIX_KEY"]}

def fetch_all(endpoint, **params):
    page, rows = 1, []
    while True:
        r = requests.get(BASE + endpoint, headers=HEAD, timeout=60,
                         params={**params, "page": page, "per_page": 500})
        if r.status_code == 429:                       # wait and retry the same page
            time.sleep(int(r.headers.get("Retry-After", "5")))
            continue
        r.raise_for_status()
        body = r.json()
        rows += body["data"]
        if not body["meta"]["has_more"]:
            return rows
        page += 1

changed = fetch_all("delay-events", updated_since="2026-10-01T00:00:00+03:00")
print(len(changed), "delay events changed")

PowerShell (Windows scheduled task)

$h = @{ Authorization = "Bearer $env:PLANIX_KEY" }
$m = (Get-Date).AddMonths(-1).ToString("yyyy-MM")
Invoke-RestMethod -Headers $h -OutFile "C:\erp\in\plant-$m.csv" `
  -Uri "https://pace.planixengineering.com/pace-backend/api-v1.php/exports/plant-recharge?month=$m&format=csv"

Telematics: post meter readings

curl -sS -X POST -H "Authorization: Bearer $PLANIX_METER_KEY" -H "Content-Type: application/json" \
  -d '{"readings":[{"asset_code":"EX-01","reading_time":"2026-09-30T17:00:00+03:00","hour_meter":1300.5}]}' \
  https://pace.planixengineering.com/pace-backend/api-v1.php/meter-readings

Security

  • Company isolation. Every query is filtered by the company that owns the key. Ids from another company answer 404. This applies to the Planix company too.
  • Least privilege. Give each system its own key with only the scopes it needs. Revoke a key the moment a system, contractor or employee no longer needs it; it stops working at once.
  • Stored safely. Only a SHA-256 hash of each key is stored and keys are compared in constant time. The full key is shown once, when it is created.
  • Audited. Every call is logged with time, key, request, result and row count. Your administrator sees the latest calls on the API keys page; creating and revoking keys is also recorded in the Planix Access audit.
  • Personal data. The API returns names where the work needs them (approver, timekeeper, operator, document authority). It never returns passwords, phone numbers, e-mail addresses of site staff, photos or document files.
  • HTTPS only, no cookies: key calls do not create a session.

Versioning

This is version 1. New fields and endpoints may be added to version 1 at any time, so read fields by name and ignore ones you do not know. A field will not be renamed or removed within version 1.

Planix Engineering. Questions about the API: contact Planix support through your account manager.