Not every customer can migrate now. Some can't afford the cash-flow hit of an 8–12 week swap. Some have processes so unusual that a modern general-purpose system would, at first, fit them worse. For those tenants we built the API-only pattern: the legacy PHP/Drupal ERP stays as the single source of truth, but we put a thin modern REST layer in front of it.
What it's good for, what it isn't
Good:
- Plug modern frontends (Storefront, mobile, partner portal) onto the ERP without touching the Drupal core.
- Expose ERP data to external systems (NAV, banking, BI) much more easily.
- Let the modern stack's chat assistant (Nortinia Engine) query the legacy ERP.
Not good:
- NO new business logic lives here. New logic is built in the modern stack. The REST layer is a mirror, not a place for business rules.
- Bulk writes (10k+ row imports) still go through the legacy admin UI.
The six endpoint groups
Every API-only deployment we ship gets these six out of the box:
- Customers —
GET /api/v1/customers,GET /:id,POST(allowlisted tenants only),PATCHlikewise. - Products — product-master reads + stock lookup.
- Orders — sales orders, status reads, status transitions.
- Invoices — issued invoices, PDF generation.
- Stock — point-in-time stock snapshot, per warehouse.
- Reports — predefined reports run with parameters.
Writes are off by default. Per-tenant approval flips them on, and every mutation is audited.
The three operational pillars
API-key auth. Every call carries X-Api-Key: <key> scoped to a tenant. Rotated every 90 days.
Rate limit. Default 60 req/min/tenant. Burst allowance up to 300 on request. The legacy DB cannot take more, and the layer exists precisely so a runaway mobile-app retry loop does not nuke the Drupal install.
Idempotency key. Every POST/PATCH expects an Idempotency-Key header. The response is cached for 24 hours. This is the only defense against network flakes that does NOT double-insert into the legacy DB — Drupal has no idea it exists, and never will.
The hidden trap: CP1250
The legacy DB stores text columns in CP1250. Has done since the 2008 Drupal 6 install. The modern REST API expects UTF-8. Solution: on-the-fly transcoding on every read (PHP's iconv), and back on every write. Two interesting consequences:
- A handful of accented characters technically valid in CP1250 are not canonical Hungarian (e.g. some old Drupal rows store
\u0151instead ofő). A small sanitiser runs on every read. - Searching with
LIKE %xy%will not work accent-insensitively against the legacy DB; the REST layer either filters in-memory (small lists) or refuses (large lists) — so the frontend cannot offer full-text search, only prefix sets.
Why we don't fork legacy data
We keep one source of truth: the legacy DB. The REST layer does not persistently cache data, only short in-memory TTLs (60s for products, 15s for stock). Duplicating data would mean keeping two stores in sync — exactly the opposite of why we adopt API-only.
Where this leads
API-only is not a dead end. Modern UI components already work against the legacy ERP through the REST layer; when the tenant is ready to migrate, the frontend stays exactly the same — only the backend switches. The REST contract is stable, so the migration is invisible at the UI level.