Intentflow Intentflow

How it works

Four steps, whether it's one switch or your whole network.

Upload your config dump

A .zip, a .tar.gz, or a single .txt of show-command output and running-configs - however your backup tooling already collects it. Drop it in from your dashboard on any plan, or send it automatically from CI with a scoped API key on Business, Enterprise and Reconcile.

We parse and map it

Cisco IOS/IOS-XE, Cisco NX-OS, Juniper Junos, and Arista EOS, normalized into devices, interfaces, IP addresses, VLANs, and physical links.

Get a reconciliation report

New devices, updated fields, and anything that conflicts with data you've already curated - every field diffed against what's live in NetBox right now, viewable on the dashboard or downloadable as PDF, text or JSON, before a single write happens (view a sample report).

Sync on your terms

Confirm what you want written by hand, or, on Business, Enterprise and Reconcile, trigger the synchronize step from CI so only the safe, obvious stuff ever writes itself.

Supported platforms & commands

Filename or content is enough to identify what you've uploaded - there's no manifest to fill out first.

PlatformDevice inventoryTopology (LLDP)VLANsInterfaces / IPsVRFsFHRP (HSRP)FHRP (VRRP)Interface details
Cisco IOS / IOS-XEshow versionshow lldp neighbors detailshow vlanshow running-configvia running-configshow standbyshow vrrp briefshow interfaces
Cisco NX-OSshow versionshow lldp neighbors detailshow vlanshow running-configvia running-configshow hsrp allnot yetshow interface
Arista EOSshow versionshow lldp neighbors detailshow vlanshow running-configvia running-confignot yetnot yetshow interfaces (MTU/MAC only)
Juniper Junosshow versionshow lldp neighborsshow vlansshow configurationshow route instance detailnot yetnot yetshow interfaces (MTU only)
A batch can mix platforms freely - each file is identified on its own. Anything that isn't one of these commands is skipped with a reason, never silently dropped. Cisco and Arista devices talking to each other also benefit from show cdp neighbors detail for extra topology detail alongside LLDP. VRF membership rides along with the running-config upload for Cisco/Arista - only Junos needs a separate upload for it, since Junos VRF membership isn't visible in interface config at all. VRRP is Cisco IOS only for now, despite being Junos's own native FHRP protocol - no vendor's command output parses into VRRP data except IOS's. Interface details (speed/duplex/MTU/MAC) is a separate, optional upload - Arista and Junos have real, honest gaps in what their own command output parses into, noted above.

What gets imported

One upload can carry a device from bare identity to a fully wired-up NetBox record.

Devices

Name, model, serial, and platform from show version. Platform is written as a real NetBox Platform object (e.g. "Cisco IOS") - kept current automatically, the same "always trust what the device just reported" treatment a MAC address gets, never held as a reviewable conflict. Matched against existing NetBox devices by serial first, then hostname - never assumed to be new.

Interfaces & IP addresses

Name, description, admin state, and IP addresses parsed directly from the running-config - handled by a purpose-built parser per platform.

VLANs

Created per Site and assigned to the interfaces that carry them - untagged (access) or tagged (trunk) - combining each platform's VLAN table with its running-config's switchport settings.

Physical links (cables)

Drawn between two interfaces whenever LLDP identifies a neighbor that's also part of NetBox (either already there, or created in the same batch). A link with no resolvable neighbor is left out, never guessed.

VRFs & Prefixes

Every interface IP already implies a subnet - 10.0.1.5/24 means the prefix 10.0.1.0/24 exists - so Prefixes are derived straight from the same running-config data already being parsed, no extra upload needed. VRF membership comes along with it: Cisco/Arista carry it as a line inside the interface's own config, Junos needs the separate show route instance detail upload (see Supported platforms above). A VRF's Prefixes and IP addresses are scoped to it automatically.

Port-channels & LAGs

Bundle membership parsed from channel-group (Cisco/Arista) or 802.3ad (Junos) config - the bundle interface gets NetBox's native LAG type, and its member interfaces get linked to it.

BGP peering

Neighbor sessions from router bgp/protocols bgp config, written as real BGP session objects - AS numbers, peer addresses, and descriptions. Previewed before anything's applied, the same as a VLAN or VRF field change - a changed description shows as a reviewable conflict, and a peer with no resolvable local address shows as its own Unresolved state rather than being silently guessed at. Global/default-VRF sessions for now; VRF-scoped BGP peering is on the roadmap.

FHRP groups (HSRP & VRRP)

Gateway redundancy groups from show standby/show hsrp all/show vrrp brief, written as real FHRP group objects shared across the routers that belong to them - group ID, virtual IP, and each router's own priority. A changed virtual IP shows up as a reviewable conflict before anything's applied, the same as a VLAN or VRF field change - a wrong gateway address breaks failover, not just a cosmetic mismatch. VRRP is Cisco IOS only for now; VRRP on other platforms and FHRP on Juniper/Arista generally are on the roadmap.

Interface details

Speed, duplex, MTU, and MAC address from a separate, optional show interfaces/show interface upload - matched onto the interfaces already discovered from your running-config, not a new object of their own. Speed/duplex/MTU changes show up as reviewable conflicts the same way a description change does; MAC addresses are kept current automatically, never flagged as a conflict, since a NIC's own hardware address essentially never changes hands the way a gateway's virtual IP does. Real per-platform gaps apply - see Supported platforms above.

Inventory items

Chassis, line cards, power supplies, and fans from a separate, optional show inventory upload (Cisco IOS/NX-OS for now) - each part's name, part number, serial number, and description. A swapped part shows up as a reviewable conflict, the same way any other field change does - a changed serial number on "Power Supply 1" means someone physically replaced it.

VXLAN/EVPN segments

Layer 2 VNIs from a separate, optional VXLAN upload (Cisco IOS/NX-OS for now), matched onto the VLAN each one maps to and written as real network segment objects - shared across every device that belongs to the same segment, the same as a BGP session or FHRP group. A segment switching between classic multicast flooding and BGP EVPN shows up as a reviewable conflict.

NetBox core has no built-in object for BGP sessions - out of the box, it has nowhere to put one. We install the open-source netbox-bgp plugin on every hosted instance we provision, on every plan, so this works without you having to install or configure anything yourself.

Batch statuses

Every upload becomes a batch that moves through these states, visible on its own page.

uploaded

File received, not parsed yet - usually momentary.

parsing

Identifying and parsing each file's platform and command.

reconciled

Reconciliation is complete - the full report is ready to view or download. Nothing has been written to NetBox yet; that's a separate, explicit step.

reconciling

An on-demand re-check ("Reconcile now") is in progress - re-diffs the same upload against NetBox as it stands right now, without re-uploading. Only reachable from a batch that's already been reconciled, synchronized, or failed once; the page keeps showing that prior report while this runs.

synchronizing

Writing the requested changes to NetBox - either a human's reviewed selections, or (CI-triggered imports) the safe, unambiguous subset from an explicit synchronize call.

synchronized

Done, cleanly - nothing left to look at. Open the batch to see exactly what was written, per device and per field, in the synchronization log.

synchronized_with_issues

The synchronization itself ran without error on every device, but something is still held for a human - an unresolved conflict, a Removed IP address nobody confirmed, a cable whose neighbor couldn't be identified. Routine, not a bug - reopen the batch, resolve what's left, and sync again.

failed

At least one device raised an actual error while being written - the specific error is shown on the batch's page, per device, in the synchronization log. Not all-or-nothing: every OTHER confirmed device in the same batch still gets a full, independent attempt, so a batch can be "Failed" and still have most of its devices synchronized cleanly. Reopen and retry; it re-checks live NetBox first rather than replaying a stale plan, so anything already synchronized just shows as no change needed.

Reconciliation & synchronization states

Every device, interface, VLAN, VRF, Prefix, IP address, and physical link Import touches moves through the same small set of states, in two phases - what reconciliation found, and what synchronization actually did about it. Same underlying data, same counts, just before and after the write.

Reconciliation (before anything's written)Synchronization (what actually happened)Meaning
NewCreatedDidn't exist in NetBox yet
ChangedUpdatedExists, a field differs, safe to write - nothing's touched that field since Intentflow's last sync
Needs ReviewUnresolvedExists, a field differs, but has never been compared before - no sync history to say the imported value is any more trustworthy than what NetBox already has, which may be a value you curated yourself before ever using Import. Held the same way a conflict is, until a human picks a side.
Changed — conflictUnresolvedExists, a field differs, but it's drifted independently since the last sync - held, never overwritten, until a human picks a side
In SyncKeptExists and already matches - nothing to do, not a problem
Removed (IP addresses only)Unassigned / UnresolvedNetBox has this address assigned to an interface the upload covers, but the upload no longer lists it there - see Conflict detection for how this is scoped and why it's never automatic
n/a at reconcile timeUnresolvedOnly for physical links and IP addresses: the other end (neighbor device/interface, or the device/interface an address is really assigned to) couldn't be identified at all - genuinely unknown, not a drift
n/a - synchronization-onlyFailedA device raised an actual error while synchronizing was trying to write it - a real problem, not a routine "still needs a decision". See Batch statuses for how this affects the batch as a whole.
"Changed" is one reconciliation bucket with two outcomes underneath it, not two separate states - the same entity can have some fields safe to write and others conflicting at the same time (see Conflict detection). "Kept" and "Unresolved" are deliberately different words for two very different situations - one means nothing needed to change, the other means something still needs your decision - that both used to be called "Skipped". The on-page "View reconciled entities" and "View synchronized entities" only list entries worth a second look by default - but every downloaded report (.txt, .pdf, JSON) and the API always include every entity, Kept ones too, as a complete record.

Reconciliation's counts describe the current full diff across every device in the batch, refreshed by "Reconcile now" - Review & Sync's counts describe what the last sync actually did, which can be a smaller set if you only selected some devices, or resolved some conflicts and not others. The two won't match unless the last sync covered everything reconciliation found - that's expected, not a bug.

Data coverage & the synchronization log

Two different views into a batch, for two different questions.

What does the "Data coverage" table mean?

Each row is a data type (device inventory, interfaces/IPs, VLANs, LLDP topology), and the fraction is how many of the devices in this batch had that specific data uploaded - not a count of individual files or objects. "Interfaces/IPs: 1/2" means one of the two devices in the batch had a running-config uploaded; the other didn't. A device can be fully identified but still show a gap in another row if that command's output was never uploaded for it.

Missing source data never implies deletion or removal. Not uploading show vlan this round is never read as "this device has no VLANs" - it's read as "nothing to check this round," and no VLAN field is touched. The same holds for every data type: skip the LLDP upload and no cable is ever flagged, skip the running-config and no interface or IP address on that device is touched at all. When interface data is present, IP assignment is treated differently, but this isn't an exception to the rule above - it's the rule applying to a case where the data genuinely is there: an address previously assigned in NetBox but absent from that interface's observed configuration can be proposed for unassignment, scoped strictly to interfaces this same upload actually covers, and only ever as a proposal you explicitly confirm, never a default.

Does "Synchronized" mean everything on the page got written?

A clean "Synchronized" does mean exactly that - every confirmed entity's outcome is final, nothing left to look at. If something's still held - a field you didn't resolve, a Removed IP nobody confirmed, a physical link whose neighbor couldn't be identified - the batch instead shows "Synchronized with issues": the write itself completed with no errors, but there's a decision still waiting on you. If a device actually raised an error while being written, the batch shows "Failed", and any other confirmed device in the same batch still got its own full, independent attempt - a "Failed" batch can still have most of its devices synchronized cleanly. Open the batch and check the synchronization log for the precise, per-entity record of what actually happened either way.

What's in the synchronization log?

One line per device, interface, VLAN, VRF, Prefix, IP address, Site, Role, and cable the synchronization step touched, with its outcome - Created, Updated, Unassigned, Kept, Unresolved, or Failed (see Reconciliation & synchronization states). It's built from what actually happened during the write, not from what was merely proposed at parse time - the two can differ if something changed in NetBox between review and synchronize, since synchronizing always re-checks live NetBox first rather than trusting a stale plan.

Conflict detection & resolution

The rule this whole feature is built around: never silently overwrite something a human changed.

How is a field classified as safe vs. conflicting?

Every field Intentflow could write is compared three ways: the new value from this import, the live value in NetBox, and the value Intentflow last synced there. In order:

Imported value vs. NetBox valueEver synced before?NetBox value vs. last synced valueResult
matchNo-op - already correct, nothing to write
differnoNeeds Review - no history to say the import is any more trustworthy than what's already there
differyesmatchesSafe update - nothing's touched this field since Intentflow last wrote it
differyesdiffersConflict - something changed it independently, so it's held rather than overwritten

That "last synced" value isn't kept in some separate database - it's stored directly on the object itself, in a hidden intentflow_sync custom field created during setup, and it only ever holds what Intentflow itself last wrote. It also moves forward every time a field applies cleanly, so a manual edit only shows up as a conflict if it happened after Intentflow's most recent import touched that field - an edit from three imports ago that's already been synced over won't reappear as a conflict now.

A field that's never been synced and differs is held, not silently written - a NetBox that already had devices, interfaces, or VRFs before you ever ran an import is the normal case here, not an edge case, and there's no way to tell "this predates Import" from "this import's value is stale" without a sync history. Needs Review and Conflict resolve exactly the same way - keep NetBox's current value, or use the imported one - the difference is only in why the field is held, shown next to each one on the review screen. A field that already matches, with no sync history yet, needs no decision at all: it silently gets a baseline the next time anything else on that same object is written, so it's protected against actually going stale later without ever showing up as a false alarm now.

What's checked, at the device and interface level?

Devices: name. Interfaces: description, admin state, and (once VLAN data is present for that device) trunk/access mode and VLAN assignment - checked in both directions, so an interface that lost its VLAN assignment since the last import is caught the same way as one that gained or changed one. That check only ever runs when this round's upload actually included VLAN data for that device - see the missing-data principle above; a batch with no VLAN command uploaded leaves every interface's VLAN fields untouched, never reads the absence as "clear everything." A single interface can have one field land as a safe update and another as a conflict at the same time - resolving the conflict never blocks the unrelated safe field from applying.

How do I resolve a conflict?

Each conflicting field on the review screen gets a choice: keep NetBox's current value, or use the imported value. It defaults to keeping NetBox's value - nothing changes unless you explicitly pick otherwise. Choosing "use imported value" is itself the confirmation to apply it; there's no separate checkbox to remember on top of that choice.

What about VLANs and physical links specifically?

VLANs are created if they don't already exist, matched by Site and VLAN ID if they do - and if one does already exist, its name is re-checked for drift the same way a device or interface field is: a safe rename (nothing's touched it since our last import) applies automatically, a genuine conflict (renamed in NetBox independently) gets its own "VLAN conflicts" section on the review screen. Physical links work the same way: a link is created if neither end already has a cable, left alone as already-correct if one end is already cabled to exactly the expected neighbor, and surfaced under "Physical link conflicts" if an end is cabled to someone else - pick "use imported value" and the old cable is disconnected and the new one created, or leave it on NetBox's current connection to skip it this round. Resolving a physical-link conflict is the one place Import can delete anything at all - a narrow, Cable-only exception to the "never deletes" rule, needed because NetBox won't let an already-cabled port take a second cable.

What about VRFs and Prefixes?

Prefixes are New-or-In-Sync only, matched by identity alone (VRF + the CIDR itself, independent of Site) - a derived Prefix carries no imported description or status to compare against an existing one, so there's genuinely nothing to conflict on. VRFs get the fuller treatment: matched by name, and if a route-distinguisher and/or description were parsed for it (Cisco/Arista pull both from the VRF's own config block; Junos gets a route-distinguisher only, from the same show route instance detail upload used for membership - that platform's source command has no description field at all, so Junos never proposes touching one either way), those are re-checked for drift the same way a VLAN's name is - a safe change applies automatically, a genuine conflict gets its own "VRF conflicts" section on the review screen.

What about IP addresses?

An address that doesn't exist in NetBox yet is New. One that already exists and is assigned to exactly the device and interface this import expects is In Sync. One that already exists but is assigned to a different device or interface - or isn't assigned to anything at all - is a conflict: reassigning an address is a meaningful write (it could disrupt whatever's actually using it right now), so it's held under its own "IP address conflicts" section on the review screen with the same keep-NetBox's-value-or-use-imported choice as everything else, never silently moved.

An address NetBox has assigned to an interface this upload covers, that the upload no longer lists there, shows up separately as Removed, under its own "IP addresses no longer in this config" section. Scoped strictly to interfaces this same upload actually has data for - an interface the upload doesn't mention at all tells us nothing about its addresses, so nothing is claimed about it (see the missing-data principle above). The default is to leave it assigned; opting in unassigns it - detaches the address from the interface, the record itself, its VRF, and its history all stay in NetBox, nothing is deleted. This is never automatic, including through POST /api/reconcile/import/{batch_id}/synchronize's safe-subset path - it always requires the same explicit, per-address confirmation a reassignment conflict does.

Using Reconcile with your own NetBox

Standalone Reconcile connects to a NetBox you run yourself. Everything above works the same way - what follows is only what differs when the instance isn't ours.

How do I connect my own NetBox?

From your dashboard, enter your NetBox's base URL and an API token you generate in NetBox itself. That's the whole setup - there's nothing to install, no agent, and no inbound access to your network. Intentflow calls your NetBox's REST API the same way any other API client would. If your NetBox isn't reachable from the internet, it won't be reachable from Intentflow either; that's the one hard requirement.

Which NetBox versions are supported?

Reconciliation - reading your NetBox and showing you every difference - works across the compatible range. Synchronization is deliberately narrower, because writing into an object model we haven't tested against is how you get confidently wrong results rather than an obvious error. Your connected version is checked automatically and your dashboard always shows which row you're in.

Your NetBoxWhat worksWhy
NetBox 4.2 - 4.6Reconciliation and synchronizationCertified - covered by Intentflow's own test suite
NetBox below 4.2Not supportedThe object model differs enough that results would be unreliable
netbox_bgp 0.19Compared and synchronizedCertified - the plugin version Intentflow tests against
netbox_bgp, any other versionDiscovered and reported onlyIts objects are still found and listed; they are not compared or written
netbox_bgp not installedDiscovered and reported onlyNothing in your NetBox to compare them against

API keys. NetBox 4.5 and above issue an API token as a composite value beginning nbt_, shown once when you create it and not retrievable afterwards - copy the whole string, including the part after the dot, not the shortened key the token list shows later. NetBox below 4.5 uses a single 40-character key you can view at any time. Intentflow accepts either - paste exactly what your NetBox shows you.

A newer NetBox. When a NetBox newer than 4.6 is released, Intentflow reconciles against it and shows you every difference, but will not write to it until that version has been through the same test suite - the certified range only ever widens after CI has proven it.

What permissions does the API token need?

At minimum, read access to devices, interfaces and IP addresses - without all three, a reconciliation report would silently look like an empty NetBox rather than a permissions problem, so Intentflow refuses the connection instead of showing you that. Read access alone is a perfectly good place to start: you get the full discovery and reconciliation report, and synchronization is simply unavailable until you widen the token. To synchronize, it additionally needs write access to the object types you want written. It also needs permission to manage custom fields, unless the one field Intentflow uses is already present in your NetBox - in which case a token that can't manage custom fields is fine, and Reconcile detects that rather than assuming the worst.

Does connecting my NetBox change anything in it?

No. The connection check is strictly read-only - it reads your version, what the token can see, and which optional models exist. It creates nothing, and we hold ourselves to that in the test suite rather than as a promise.

Synchronizing does make exactly one schema addition, the first time you run it: a custom field named intentflow_sync, which records which fields Intentflow itself last wrote and when. That field is what makes the three-way comparison possible - it's how Reconcile can tell "you changed this by hand" apart from "this drifted since our last sync" and refuse to overwrite the former. Nothing else in your schema is touched, and no object is ever written until you confirm it.

What if I don't run the netbox_bgp plugin?

BGP sessions are still parsed from your configs, still counted in Discovery, and still appear in your reports - they just aren't compared against NetBox or synchronized, because there's nowhere in your NetBox to compare them against. Reconcile says so explicitly on the batch rather than quietly leaving them out: the objects are real and we found them, it's their representation in the target NetBox that's unavailable. Everything else reconciles normally. Installing the plugin later needs no action here - the next import picks it up.

What happens if I revoke or narrow the token later?

Reconcile re-checks what your NetBox can do before every import, so a token you widen (or a plugin you install, or a NetBox you upgrade) takes effect on the next run with nothing to re-enter. If a token is revoked partway through a synchronization, Reconcile stops rather than retrying every remaining object against a credential that will refuse all of them, and the report records exactly what was written before it stopped. Re-running after fixing the token is always safe.

Can I point Reconcile at a different NetBox?

Yes, but not repeatedly: a subscription covers one network, so it may be repointed at a genuinely different NetBox deployment once per month. That is what a migration needs; it is not enough to service several customers' NetBoxes in turn from one subscription. If a migration goes wrong and you need to switch back sooner, contact us and we will lift it.

Routine changes to the same NetBox do not count and are unlimited: rotating or replacing the API token, changing authentication or TLS settings, or correcting the URL you entered. Switching from http to https, adding or removing a trailing slash or an /api suffix, changing the case, or naming the default port explicitly are all recognised as the same deployment.

When you do switch, existing reconciliation history stays attached to the NetBox that produced it - the drift baseline a past report was measured against lives inside that instance, so trend comparisons do not cross the boundary.

Manual upload vs. automated (CI) imports

Same parsing and the same safety rules either way - what differs is who's around to review.

Is automated (CI-triggered) import available on every plan?

Manual upload from the dashboard is free on every plan, Free through Enterprise. Triggering imports from CI - the POST /api/reconcile/import path below - is available on Business, Enterprise and Reconcile, since it's meant for recurring, unattended use rather than a one-off upload. Standalone Reconcile is included because keeping your own NetBox current between manual uploads is the point of it. Free-tier accounts don't see the "Generate API key" option.

Is there a limit on how many imports I can run?

A standalone Reconcile subscription includes 30 reconciliation runs a month. One run covers the whole cycle for a single upload - discovery, reconciliation, your review, and synchronizing the changes you approve - so reviewing a report, re-running the reconciliation on it, or syncing it later never costs a second run. An upload we could not read anything from is not counted at all.

The allowance resets monthly from the date Reconcile was activated on your account, which is not necessarily your billing date, so an annual subscription gets the same 30 runs each month. Worth planning for if you import from CI: a nightly job is about 30 runs a month on its own, so if you also import on every merge, tell us and we will sort out the capacity.

Imports on a Hosted NetBox plan are not metered - Reconcile is a bundled feature of those plans rather than the product being bought.

Does a CI-triggered import write anything on its own?

No - uploading, whether from the dashboard or from POST /api/reconcile/import, only ever parses and reconciles. Nothing is written to NetBox until something explicitly synchronizes it. For a manual upload that's a person clicking "Sync selected changes" after reading the report; for a CI-triggered one, it's a second, deliberate call to POST /api/reconcile/import/{batch_id}/synchronize, which writes the safe subset - no-ops, safe updates, and new devices whose Site match came from your own IPAM data specifically (the one signal backed by curated data, not a guess). Anything uncertain - any conflict, any weaker-signal new device - is left for a human to resolve on the normal review screen whenever they check in.

How does the API key work?

Generated from the Import page, shown once in plaintext, never again - only its hash is stored, the same disclosure model as a GitHub personal access token. It authenticates every /api/reconcile/import... endpoint below (see API reference) - nothing else in the app. Generating a new key immediately invalidates the old one; there's no window where both work.

What does a CI job actually look like?

Point a scheduled job - a nightly GitHub Actions run, a cron on your backup box, whatever already collects your config dumps - at POST /api/reconcile/import with the API key as a bearer token and the archive as the request body. You get a batch_id back immediately; poll GET /api/reconcile/import/{batch_id} until status leaves parsing. Once it's reconciled, that same response already tells you reconciliation.new/changed/in_sync - call GET /api/reconcile/import/{batch_id}/report if you want the actual per-entity Imported-vs-NetBox detail behind those counts, not just the totals. Then decide what to do: call POST /api/reconcile/import/{batch_id}/synchronize to write the safe subset, fail the build if devices_needing_review is non-zero, post the diff to Slack, or just let it sit until someone opens the dashboard. Intentflow doesn't impose an opinion there. Full request/response shapes for every step are in the API reference below.

API reference

Five calls, four endpoints, called in this order. Every one takes Authorization: Bearer <your API key> and returns JSON.

POST /api/reconcile/import 1. upload

Uploads and starts parsing/reconciling a batch - same parser, same conflict rules as a dashboard upload. Never writes to NetBox; always lands on reconciled (or failed) for you to inspect before deciding anything.

Request

multipart/form-data file: your .zip / .tar.gz / .txt (max 50 MB)

Response — 202

{ "batch_id": "f4de4f62-0d96-40a0-9592-109e929c86fd" }
GET /api/reconcile/import/{batch_id} 2. poll

Poll this until status leaves parsing/reconciling/synchronizing. reconciliation appears once a diff exists; synchronization appears once a synchronize has actually run - same New/Changed/Needs Review/In Sync and Created/Updated/Unassigned/Kept/Unresolved/Failed counts shown on the dashboard for this exact batch, never a separately-computed set (see Reconciliation & synchronization states). A non-zero needs_review never gets touched by POST /api/reconcile/import/{batch_id}/synchronize either - it's held for a human exactly like a conflict is. status itself can be synchronized (clean), synchronized_with_issues (ran cleanly, something's still held), or failed (at least one device raised an actual error - other devices in the same batch may still have synchronized).

Response — after reconciling, before synchronizing

{ "batch_id": "f4de4f62-...", "status": "reconciled", "error": null, "devices_applied_automatically": 0, "devices_needing_review": 0, "reconciliation": { "new": 25, "changed": 6, "in_sync": 7, "needs_review": 0 } }

Response — after a CI-triggered synchronize, some devices still need review

{ "batch_id": "f4de4f62-...", "status": "reconciled", "error": null, "devices_applied_automatically": 4, "devices_needing_review": 1, "reconciliation": { "new": 25, "changed": 6, "in_sync": 7, "removed": 1, "needs_review": 4 }, "synchronization": { "created": 25, "updated": 4, "unassigned": 0, "kept": 7, "unresolved": 7, "failed": 0 } }

status stays reconciled here rather than turning terminal, specifically for the CI/API auto-synchronize path (POST /api/reconcile/import/{batch_id}/synchronize) - so the batch stays fully actionable on the normal dashboard for whatever's still held. A manual synchronize from the dashboard instead resolves to a terminal synchronized (clean), synchronized_with_issues (ran cleanly, something's still held), or failed (at least one device raised an actual error) - this same endpoint reflects whichever status the batch is actually in, regardless of which path last touched it.

GET /api/reconcile/import/{batch_id}/report 3. inspect (optional)

The detail behind the counts above - every entity's Imported-vs-NetBox diff, and, once synchronized, what was actually done about each one. Same complete-record rule as the dashboard's downloads: every entity is included, In Sync/Kept ones too, not just what changed.

Response

{ "batch_id": "f4de4f62-...", "status": "synchronized", "reconciliation": [ { "entity": "sw-lab-01", "status": "In Sync", "changes": [] }, { "entity": "sw-lab-01 VLAN 10 (Management)", "status": "Changed", "changes": [ { "field": "name", "imported": "Management", "netbox": "DATA" } ] } ], "synchronization_log": [ { "device": "sw-lab-01", "entity_type": "vlan", "entity_name": "VLAN 10 (Management)", "status": "Updated", "changes": [ { "field": "name", "imported": "Management", "netbox": "DATA" } ] } ] }

synchronization_log is only present once a synchronize has run - it's the same record as the dashboard's synchronization log (see Data coverage & the synchronization log), just under the API's own field name. reconciliation is null if the batch's diff data has since expired (kept 30 days) - synchronization_log survives that, since it's a permanent record of the batch, not a snapshot of live NetBox state at reconcile time.

POST /api/reconcile/import/{batch_id}/synchronize 4. synchronize

Writes whatever's unambiguous - no-ops, safe updates, and new devices whose Site match came from your own IPAM data specifically (see Manual vs. automated). Everything else is held for a human on the normal review screen. Only valid while status is reconciled. Returns immediately; poll step 2 again for the outcome.

Response — 202

{ "batch_id": "f4de4f62-...", "status": "synchronizing" }
GET /api/reconcile/import/{batch_id} 5. poll again

Same endpoint as step 2 - call it again after synchronizing to see the final synchronization counts and, if anything's still devices_needing_review > 0, open the dashboard to resolve what's left.

Every endpoint 404s (not 403) on a batch_id your key doesn't own, and 401s on a missing or invalid key. POST /api/reconcile/import and POST /api/reconcile/import/{batch_id}/synchronize both 403 on tiers without automation - automated imports are Business, Enterprise and Reconcile (manual upload from the dashboard is free on every plan).

Known limitations

Said plainly, not buried in a footnote.

Questions we haven't covered here?

Reach out at support@intentflow.co