Accelerator · Integration package
Plex Keyring Schema Package
Fuuz accelerator: Plex keyring schema — 182 data models covering Plex's complete read surface, generated from the vendor API dictionary
Version: 0.6.0 · Spec: 2.0.0 · Platform: 2026.8.0 Dictionary: Plex developer portal, 2026-08-05
Overview
Installs the complete Plex read surface as Fuuz data models — 182 models, 4,560 fields — generated from Plex's own API dictionary rather than hand-written.
This is the schema layer of the Plex keyring: somewhere to land every readable Plex object, with foreign keys resolved into real relations so applications traverse rather than join.
Models only. Connections, flows and the endpoint switchboard are separate concerns.
Package Contents
plex-keyring-schema-0.6.0.tgz <- import this
├── manifest.json name, version, spec + platform version
├── definition.json the PackageDefinition / Version / Selections
└── package-data.json 182 dataModels (header, version, modelDefinition, migrations)
The same three files are also committed unpacked, so the schema is readable and diffable in the browser without downloading anything.
This is the layout exportPackageArchive produces — declarative model definitions, not a script of
create-and-deploy steps, so the order models are installed in is the installer's concern.
That is a different thing from the order DATA is loaded in, which very much is your concern — see Load order below.
What gets installed
| models | 182 — one per distinct Plex response schema |
| fields | 4,560 |
| forward relations | 465 (child → parent, plus sourceSystem on every model) |
| reverse collections | 283 (parent → children) |
Coverage: all 119 collection GETs in the portal that resolve to a typed schema, plus the 63 schemas reachable only through a single-entity GET.
By category: masterOrTransactional 99, subResource 63, analytics 19, configuration 1.
Largest models
Vendor fields only — relations and provenance columns excluded, so this reflects how much Plex actually returns rather than how well connected the model is.
| Model | Vendor fields | Source endpoint |
|---|---|---|
plexCustomerAddress |
50 | /mdm/v1/customers/{customerId}/addresses/{addressId} |
plexAccountsPayableInvoice |
47 | /accounting/v1/ap-invoices |
plexShipper |
47 | /shipping/v1/customer-shippers |
plexAccountsReceivableInvoice |
46 | /accounting/v1/ar-invoices |
plexTruck |
39 | /shipping/v1/customer-trucks |
plexPurchaseOrder |
38 | /purchasing/v1/purchase-orders |
plexActivity |
37 | /management/v1/activity-manager/{id} |
plexContainer |
34 | /inventory-tracking/v1/containers/{serialNumber} |
One tenant per PCN
Install this once per Plex PCN, into its own Fuuz tenant. The keyring is not designed for one
tenant to read several PCNs: two facilities both have a PART-1000, and sharing a keyspace
collapses them onto one row with no error and nothing in the data to show it happened. A tenant per
PCN makes that impossible by construction rather than by convention.
The PCN and source system are therefore tenant configuration, not request parameters — the read flows refuse to run until both are set.
Identity: the primary key IS the vendor's key
externalId |
Plex's own key, verbatim — partId, orderNo, whatever they call it. |
id |
the same value, copied by a create trigger. |
This looks redundant and is the single most load-bearing decision in the package, because the
platform enforces a foreign key only when it targets id. Pointed at any other unique column, a
reference resolves when it can and silently returns null when it cannot — no error on write, no
error on delete, and deletionReferenceBehavior ignored. Both behaviours were verified against a
live tenant, in both directions.
So id = externalId is what makes all 465 relations real rather than decorative. It also dedupes
the source for free: the primary key is the vendor's identifier, so the same record cannot land
twice however many times it is read.
A trigger sets it rather than the loader, so any writer gets it right — your own integration lands the same key this package's flows would.
"triggers": { "create": "... $isNilOrEmpty($r.id) ? $r~>|$|{\"id\": $string($r.externalId)}| : $r" }
Install one PCN per tenant. With the vendor's key as the primary key, two PCNs in one tenant would overwrite each other row for row.
Three design decisions worth knowing
Every field is nullable. A vendor's required flag describes their write contract, not what
their read endpoint returns — optional modules, permissions and older records all produce absent
fields. One NOT NULL on a field a given tenant does not populate fails the whole upsert batch and
takes the other 499 rows with it. The only exceptions are the two identity columns above.
A foreign key is the vendor's own column. Plex sends partId; the Part it means is the row whose
id is that value. The relation points partId → Part.id and nothing is computed. Applications get
row.part { … }, the vendor's key is still right there for tracing, and a reference to a row that
does not exist is rejected on write.
References with nothing behind them stay plain. Heat number, currency code, DUNS and tracking number look like foreign keys and are not — Plex exposes no endpoint for them. Inventing a model to point at would be a lie, so they remain strings.
Load order is a correctness requirement
Because foreign keys are enforced, an endpoint whose parent has not loaded yet is a rejected write — and rows go up in batches of 500, so one bad reference fails the batch. Order is not a freshness preference any more.
loadLayer on each endpoint is computed from the relation graph itself, not from naming
conventions: a model sits one layer past the deepest parent it references. 7 layers, no cycles
(checked with Tarjan — a cycle would have no total order and has to announce itself rather than
silently corrupt the sequence).
Run layers in ascending order, or just run the plexKeyringLoadAll flow, which chains them.
Bounding a read — Plex has no pagination
Worth knowing before planning a large extract: across 283 scraped contracts and 124 collection
endpoints, Plex documents no pagination at all — no limit/offset, no page number, no cursor, no
continuation token. _limit appears on two endpoints and $top on one, and both are caps that
truncate; neither can fetch page two.
Each endpoint therefore records what it actually supports, in paging.style:
none |
158 | no bounding mechanism — the collection arrives as it arrives |
window |
21 | a created/modified date range. Not pagination, but the only real way to bound a large read, and the basis for incremental sync |
cap |
3 | a documented truncating parameter |
Windows ship off: silently narrowing a full extract to a slice makes data look missing rather than bounded.
Provenance on every row
externalId, sourceSystemId, sourceUrl, sourceVersion, syncedAt, rawPayload, dedupeKey.
sourceSystemId is a real relation to ExternalSystem, not a bare id — so a mistyped source system
is rejected on the first row rather than discovered after a million have landed under a name nobody
recognises. Create the ExternalSystem row before loading data, or every insert is refused.
Regenerating
Generated, never hand-edited:
node platform/gen-models.js plex # vendor dictionary -> models
node platform/gen-package.js # models -> this package
The scraped Plex dictionary is deliberately not shipped here.
Before you install
A Jump Start, Not A Finished Answer
An accelerator is a pre-built, configurable starting point that addresses the bulk of the common challenges in its domain. It is not intended to solve every possible variation off the shelf — requirements that fall outside it are expected and normal for the model, not a shortcoming of this one.
Standard
Delivered and working as-is. No configuration needed.
Configurable
A base capability you adapt with the platform's own no-code and low-code tools — screen builder, transforms, workflow rules, flow parameters — without professional services.
Custom development
Feasible on the platform but outside this accelerator's current scope. A scoped change through Fuuz or a certified partner.
Once installed, it is yours
Installation forks the flows, models and screens into your environment as your own copies. Later versions published against this accelerator do not propagate to you automatically — picking up a change means deliberately re-applying it.