Skip to content
PortalBase

API

Sync equipment from your fleet system

For the program your IT team writes. It does what Setup's Sync from a file does for equipment, on the schedule you set.

Your fleet, ERP or maintenance system already knows what equipment you have and where it is. Asset sync keeps PortalBase the same. It adds new units, changes names, types, places and fields, and can retire what's gone.

Over the API, your program sends the list on its own schedule. It's the same sync as Setup, under Sync from a file, with the same mapping. Each change goes in your company's audit log as its own entry, under the key's service account.

1. Before you start

  • An admin makes a service account in Setup, under Connections, with a role that has assets.manage for the whole company. Admin at the top of your company has it.
  • Then makes a key for it that can make changes. A key that only looks things up is refused, and so is an AI tool.
  • Map your columns, type names and location codes once in Setup, under Sync from a file, on the Assets tab. The API uses that mapping. The template there shows every column.

2. Send rows as JSON

POST to https://portalbaseai.com/api/v1/assets/sync with your key as Authorization: Bearer pbk_... One row per asset.

curl https://portalbaseai.com/api/v1/assets/sync \
  -H "Authorization: Bearer $PORTALBASE_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 7c2e9a14-5b3d-4f6a-8e1c-9d0b2a4f6e83" \
  -d '{
    "preview": true,
    "rows": [
      {
        "code": "T-114",
        "external_id": "ERP-88213",
        "name": "Truck 114",
        "type": "Truck",
        "org_unit": "NY",
        "status": "in service",
        "attributes": { "make": "Ford" },
        "fields": { "cost_center": "4410" }
      }
    ]
  }'
  • code finds each asset, then external_id. Both are matched ignoring case. A new asset needs a name, a type and a place.
  • external_id is your system's own id for the asset. The sync keeps it, so a renumbered unit is still found.
  • type is an asset type's name, or a name your mapping knows. org_unit is a place's code, name or path, or a location code your mapping knows.
  • status: in service or retired, or a word your mapping knows. Left out, it doesn't change.
  • attributes: the fields the asset's type defines, by key. fields: your company fields on assets, by key.
  • A value left out, or empty, is never cleared. A sync only fills in and changes.

3. Or send the list as CSV

Send the same file you'd load in Setup, with Content-Type: text/csv. The options go in the address.

curl "https://portalbaseai.com/api/v1/assets/sync?preview=true" \
  -H "Authorization: Bearer $PORTALBASE_KEY" \
  -H "Content-Type: text/csv" \
  --data-binary @equipment.csv

Each column is read by its name, through your saved mapping. A column named for one of your company fields or a type's field is read as that field. A QR column is refused. A sync never touches tags.

4. Check first

preview: true works it all out and writes nothing. The answer has the counts (added, changed, unchanged, retired, reinstated, refused) and a line for each row: what it would do, which fields would change, or why it can't. Send it again without preview to apply it.

In JSON, preview, retire_missing and max_retire go in the body beside rows. With CSV, they go in the address.

5. Retiring what's gone

  • Nothing is retired unless you send retire_missing: true.
  • With it, in-service assets at the places your list covers that aren't in the list are retired. The places it covers are the ones your rows name, and every place under them.
  • Say max_retire too: the most it may retire. If it would retire more, nothing is changed and the answer says how many.
  • Retired assets keep their history. A sync never deletes one.
  • An asset that's out of service stays as it is. Release it first to retire it.
  • An asset retired by hand stays retired. A sync brings back only the assets it retired.
curl "https://portalbaseai.com/api/v1/assets/sync?retire_missing=true&max_retire=10" \
  -H "Authorization: Bearer $PORTALBASE_KEY" \
  -H "Content-Type: text/csv" \
  -H "Idempotency-Key: 2f8b4d6e-0a1c-4e3b-9d5f-7a6c8e0b2d41" \
  --data-binary @equipment.csv

6. Retries

Send an Idempotency-Key header, a UUID you make for each sync. If the call times out, send it again with the same key. You get the first run's answer and nothing changes twice. The same key with a different list is refused with conflict.

7. The answer

A sync answers with its run_id, the counts and a line for each row. row is the line it would be in a file, so the first JSON row is row 2. A row gives the asset's id, not its name or code.

{
  "ok": true,
  "result": {
    "run_id": "9a1d3f5b-7c2e-4b8a-a6d4-1e3f5b7c9d02",
    "counts": { "added": 2, "changed": 5, "unchanged": 140, "retired": 0, "reinstated": 0, "refused": 1 },
    "rows": [
      { "row": 2, "asset_id": "4e6a8c0d-2f1b-4d3e-9a5c-7b9d1f3e5a60", "action": "changed", "fields": ["place"], "reason": null },
      { "row": 3, "asset_id": null, "action": "refused", "fields": [], "reason": "No asset type matches \"Skid steer\". Map it in Setup." }
    ]
  }
}

GET https://portalbaseai.com/api/v1/assets/sync/runs/{run_id} reads a run later. Runs are kept a year and their rows 90 days. Admins see every run in Setup, under Sync from a file.

8. Limits

  • Up to 2,000 rows, and 1 MB, in one call. Split a bigger list by place.
  • Each key may make 120 requests a minute, the same as the rest of the API.
  • A refusal answers ok: false with error.code and a sentence saying what to fix. Every path is listed at https://portalbaseai.com/api/v1/openapi.json.