End-to-End Workflow
AIM License Required
Application Infrastructure Mapping (AIM) is a premium add-on. The features described here are available only when the uploaded product license has the AIM feature enabled – see go.ipfabric.io/aim for what it covers.
To get started with AIM, contact your Customer Success Manager or email support.ipfabric.io, then upload the reissued license file.
The two AIM endpoints described in
Overview are asynchronous: each one
schedules a background job and returns a jobId. A complete integration
therefore combines them with the Jobs table and the AIM inventory tables:
- Import the data, as CSV files or a JSON body –
POST /aim/import/{snapshotId}. - Poll the import job until it finishes –
POST /tables/jobs. - Look up the application or flow ID –
POST /tables/inventory/applicationsorPOST /tables/inventory/applications/flows. - Calculate the path lookups –
POST /aim/flow-path-lookups/{snapshotId}. - Poll the path-lookup job until it finishes –
POST /tables/jobs. - Read the results –
POST /tables/inventory/applications/devices.
Design the sequence to be repeatable
Data ingested through POST /aim/import/{snapshotId} is not persisted in the
snapshot, so unloading and loading that snapshot discards it and the sequence
has to run again. Both endpoints are safe to re-run – the import merges on
the keys in your CSV files, and calculated flows are skipped – so the whole
script can simply be executed a second time. See
Imported Data Does Not Survive a Snapshot Reload
for the alternative that persists.
Supporting API Endpoints
Beyond the two AIM endpoints, the workflow uses the Jobs table to track progress and the AIM inventory tables to read the results:
/api/tables/jobs– aPOSTmethod to poll the status of scheduled jobs. Documented on this page./api/tables/inventory/applications,.../workloads,.../flows, and.../devices–POSTmethods to read the ingested data. For their columns, request fields, and response examples, see AIM Inventory Tables.
Headers must contain Content-Type: application/json and X-API-Token:. See
Authentication for alternatives.
Monitoring the Job
Poll the jobId returned by either AIM endpoint through the Jobs table until
isDone is true.
POST /api/tables/jobs
Content-Type: application/json
Warning
The Jobs table is not snapshot-scoped – do not include a snapshot key
in the request body, or the request is rejected.
Polling a Job
curl -X POST 'https://{ipf_server}/api/tables/jobs' \
--header 'Content-Type: application/json' \
--header 'X-API-Token: {api_token}' \
-d '{
"columns": ["id","name","status","isDone","startedAt","finishedAt","reason","snapshot","username"],
"filters": { "id": ["eq", "184"] },
"pagination": { "limit": 1, "start": 0 }
}'
{
"data": [
{
"id": "184",
"name": "aimFlowPathLookup",
"status": "done",
"isDone": true,
"startedAt": 1787065241653,
"finishedAt": 1787065241909,
"reason": null,
"snapshot": "8366fec8-da3e-4635-9cb3-97008ca6435b",
"username": "admin"
}
],
"_meta": { "limit": 1, "start": 0, "count": 1, "size": 1 }
}
The name column identifies the job type: aimImport for the import and
aimFlowPathLookup for the path-lookup calculation. Timestamps are Unix
milliseconds.
Finding a job without its ID
If you lose the jobId, filter on the action name instead and sort by
schedule time – for example
"filters": { "name": ["eq", "aimImport"] } with
"sort": { "column": "scheduledAt", "order": "desc" }.
Job Status Values
Poll until isDone is true, then branch on status:
status |
Meaning |
|---|---|
scheduled |
Queued, not started yet. |
running |
In progress. |
done |
Completed successfully. |
error |
Failed. reason contains the cause. |
cancelled |
Cancelled before completion. |
stopping |
Stop requested. |
stopped |
Stopped by a user. |
Reading Validation Errors
Where a validation error surfaces depends on how the data was sent.
CSV upload. The job ends with status: "error" and reason holds a
JSON-encoded object pinpointing the offending row:
{
"file": "workloads.csv",
"row": 3,
"message": "enforcementMode: Invalid enum value. Expected 'visibility_only' | 'selective' | 'full' | 'idle' | 'unknown', received 'strict'"
}
row counts the header as row 1, so row: 3 is the second data row. It is
null for errors that are not tied to a specific row, such as an unresolved
cross-reference.
JSON body. Row-level problems never reach a job – the request itself is
rejected with 422 before one is scheduled, and the response lists every
problem at once:
{
"code": "API_VALIDATION_FAILED",
"message": "Invalid Input",
"errors": [
{ "field": ["workloads", 1, "enforcementMode"], "message": "Invalid enum value. Expected 'visibility_only' | 'selective' | 'full' | 'idle' | 'unknown', received 'strict'" }
]
}
The field path carries the array index, so the row that a CSV reports as
row: 3 appears here as 1.
Cross-references between applications, workloads, and flows need a database
lookup, so they are checked inside the job in both formats – a 202 followed by
a failed job is still possible with a JSON body.
file follows the format you sent
A CSV upload reports the file name, flows.csv. A JSON body reports the
property name instead, so the same cross-reference failure reads:
{
"file": "flows",
"row": null,
"message": "dstWorkloadExternalId 'wl-999' not found in workloads data"
}
Match on the section rather than on the exact string if your client handles both formats.
Follow-up jobs after a successful import
A successful import also schedules the standard post-processing jobs for the
snapshot, such as topology calculation and intent verification. These appear
as separate entries in the Jobs table. They do not block the path-lookup
calculation, so you can proceed as soon as the aimImport job reports
done.
Reading the Results
The four AIM inventory tables – their columns, request fields, and response examples – are documented in AIM Inventory Tables. This section covers only what the workflow needs from each of them.
| Purpose | Table | What to read |
|---|---|---|
| Verify the import | Applications | flows and workloads should match your CSV row counts. dataSourceName confirms the data came from the manual source. |
Obtain an applicationId |
Applications | The id column. flowsCalculated shows how many of the application’s flows already have a result. |
Obtain a flowId |
Flows | The id column. Filter on "pathLookupsCalculationStatus": ["eq", false] to list only the flows still needing a calculation. |
| Inspect workload interfaces | Workloads | workloadInterface holds one interface address per row. |
| Confirm the outcome | Devices | hostname and sn of each traversed device. Filter on flowId for one flow’s devices, or de-duplicate on sn for a unique device list. |
After a successful calculation, re-reading the Applications table is the quickest
confirmation: flowsCalculated and devices should both have increased.
Complete Example Script
The following script imports the CSV files, waits for the import to finish, calculates the path lookups for one application, waits again, and lists the resulting devices.
Complete Workflow
#!/usr/bin/env bash
set -euo pipefail
IPF_SERVER="ipfabric.example.com"
IPF_TOKEN="{api_token}"
SNAPSHOT="{snapshotId}"
CSV_DIR="./aim-data"
api() {
curl -sk -X POST "https://${IPF_SERVER}/api/$1" \
-H "X-API-Token: ${IPF_TOKEN}" \
-H 'Content-Type: application/json' \
-d "$2"
}
# Wait until a job reaches a terminal state; fail on a non-"done" outcome.
wait_for_job() {
local job_id="$1" result status
while :; do
result=$(api "tables/jobs" "{
\"columns\":[\"id\",\"name\",\"status\",\"isDone\",\"reason\"],
\"filters\":{\"id\":[\"eq\",\"${job_id}\"]},
\"pagination\":{\"limit\":1,\"start\":0}}")
[ "$(echo "$result" | jq -r '.data[0].isDone')" = "true" ] && break
sleep 3
done
status=$(echo "$result" | jq -r '.data[0].status')
if [ "$status" != "done" ]; then
echo "Job ${job_id} finished with status '${status}':" >&2
echo "$result" | jq -r '.data[0].reason' >&2
return 1
fi
echo "Job ${job_id} completed."
}
# 1. Import the CSV files.
import_job=$(curl -sk -X POST \
"https://${IPF_SERVER}/api/aim/import/${SNAPSHOT}" \
-H "X-API-Token: ${IPF_TOKEN}" \
-F "applications=@${CSV_DIR}/applications.csv;type=text/csv" \
-F "workloads=@${CSV_DIR}/workloads.csv;type=text/csv" \
-F "interfaces=@${CSV_DIR}/interfaces.csv;type=text/csv" \
-F "flows=@${CSV_DIR}/flows.csv;type=text/csv" | jq -r .jobId)
echo "Import job: ${import_job}"
wait_for_job "$import_job"
To send a JSON payload instead, replace step 1 with the following. The rest
of the script is unchanged, because both formats return the same jobId:
# 1. Import a JSON payload. A 422 here means a row failed validation, and
# no job was created - so check for jobId before waiting on it.
response=$(curl -sk -X POST \
"https://${IPF_SERVER}/api/aim/import/${SNAPSHOT}" \
-H "X-API-Token: ${IPF_TOKEN}" \
-H 'Content-Type: application/json' \
--data-binary "@${CSV_DIR}/aim-import.json")
import_job=$(echo "$response" | jq -r '.jobId // empty')
if [ -z "$import_job" ]; then
echo "Import rejected:" >&2
echo "$response" | jq -r '.errors[]? | " \(.field | join(".")): \(.message)"' >&2
exit 1
fi
echo "Import job: ${import_job}"
wait_for_job "$import_job"
# 2. Resolve the application ID from its externalId.
app_id=$(api "tables/inventory/applications" "{
\"columns\":[\"id\",\"name\",\"externalId\"],
\"filters\":{\"externalId\":[\"eq\",\"app-001\"]},
\"snapshot\":\"${SNAPSHOT}\",
\"pagination\":{\"limit\":1,\"start\":0}}" | jq -r '.data[0].id')
echo "Application ID: ${app_id}"
# 3. Calculate the path lookups for that application.
lookup_job=$(curl -sk -X POST \
"https://${IPF_SERVER}/api/aim/flow-path-lookups/${SNAPSHOT}" \
-H "X-API-Token: ${IPF_TOKEN}" -H 'Content-Type: application/json' \
-d "{\"applicationId\":\"${app_id}\"}" | jq -r .jobId)
echo "Path-lookup job: ${lookup_job}"
wait_for_job "$lookup_job"
# 4. List the resulting devices.
api "tables/inventory/applications/devices" "{
\"columns\":[\"hostname\",\"sn\",\"srcApplicationName\",\"dstApplicationName\",\"applicationFlow\"],
\"snapshot\":\"${SNAPSHOT}\",
\"pagination\":{\"limit\":100,\"start\":0}}" | jq
Troubleshooting
| Symptom | Cause and resolution |
|---|---|
400 -- At least one file must be provided |
Nothing to import. For an upload, send at least one CSV as multipart/form-data and do not set Content-Type manually. For a JSON body, include at least one non-empty array. |
400 -- Malformed JSON |
The request is declared as application/json but the body is not valid JSON. Most often -H 'Content-Type: application/json' was sent alongside -F file parts; drop the header and let curl build the multipart one. |
422 on the import endpoint |
Invalid snapshotId, or a JSON row failed validation. errors[].field names the array, the row index, and the property. No job was created, so fix the payload and resend. |
403 on either AIM endpoint |
Missing AIM license, or the API token’s policy does not cover the endpoint. |
404 on the path-lookup endpoint, but the snapshot exists |
The snapshot is not loaded. Path lookups need the snapshot’s routing data. |
413, as an HTML page rather than JSON |
The request exceeds the 20 MB limit, which covers the whole request in both formats. Split the data and import in several requests. |
Import job status: "error" |
Read reason for the file, row, and message. Fix that row and re-import – applications, workloads, and interfaces merge on re-import, so retrying is safe. With a JSON body this indicates a cross-reference failure, since row errors are caught before the job. |
| A flow appears twice after re-importing the same data | The flow leaves a field of the flow key empty – a workload reference, srcIp, dstIp, protocol, srcPort, dstPort, policyDecision, or flowDirection – and empty values do not match on re-import. Send all key fields – see How Data Is Merged. |
Import succeeded but the Applications devices count is 0 |
Expected. Run the path-lookup calculation. |
Flows devices is empty rather than a number |
That flow has no path-lookup result yet. On the Flows table such a flow returns null, not 0; check pathLookupsCalculationStatus for the state. |
Path lookup done but devices is still 0 |
The flow’s addresses do not trace a path in this snapshot. flowsCalculated and pathLookupsCalculationStatus still advance, because a result is recorded even when no device is traversed. Verify that srcIp and dstIp exist in the snapshot’s routing tables. |
| Workload interfaces disappeared after an import | workloads.csv was uploaded without interfaces.csv. Re-send both files together. |
| All imported AIM data gone after unloading and loading the snapshot | Expected. Data ingested through /aim/import is written to the inventory tables only and is not persisted in the snapshot, so a reload discards it. Run the sequence again, or use the settings-based CSV import for data that has to survive a reload – see Imported Data Does Not Survive a Snapshot Reload. |
Request to /api/v8.1/aim/... returns 404 |
API paths are not version-prefixed. Use /api/aim/... and select the version with the X-API-Version header. |