I’ve been a bit of a fan of using Cisco’s moquery utility for ACI for querying objects, and have written and talked about that before.
But moquery has bugs – many of the -x options that make most sense when used with specific objects (like -x query-target=children) simply will not work with moquery. (See the discussion here).
So even though moquery is pretty easy to use, I’ve moved on from moquery to use the more functional icurl utility – also included on the Cisco APIC and switches.
I’ll present this in three parts. In today’s post I’ll discuss how you can use jq (the JSON Query utility) to make the output of icurl look much more better than the raw JSON format that icurl produces. I’ll cover five lessons where I dig a little deeper into jq each time. Here are the lesson topics and the icurl | jq combination I’ll discuss in each lesson for those of you who just want to get the answers without reading the whole discussion:
- Lesson #1.1 – pretty print output using jq
icurl -s http://localhost/api/node/class/fvAEPg.json | jq
- Lesson #1.2 – Harnessing the Power of jq – selecting keys
icurl -s "http://localhost/api/node/class/fvAEPg.json" | jq '.imdata[].fvAEPg.attributes.name'
- Lesson #1.3 – Making jq work for you – creating JSON output
icurl -s "http://localhost/api/node/class/fvAEPg.json" | jq '.imdata[].fvAEPg.attributes |{EPG: .name, Class_ID: .pcTag}'
- Lesson #1.4 – Caressing jq – parsing keys
icurl -s "http://localhost/api/node/class/fvAEPg.json" | jq '.imdata[].fvAEPg.attributes | (.dn|capture("tn-(?<T>.*)/ap").T), (.dn|capture("ap-(?<A>.*)/epg").A), (.dn|capture("epg-(?<E>.*)").E)'
- Lesson #1.5 – Deep dive into jq – advanced features
icurl -s "http://localhost/api/node/class/fvAEPg.json" | jq '.imdata[].fvAEPg.attributes | {tenant: (.dn|capture("tn-(?<T>.*)/ap").T), AP: (.dn|capture("ap-(?<A>.*)/epg").A), EPG: (.dn|capture("epg-(?<E>.*)").E)}' | jq -s 'group_by(.tenant) | map({key: (.[0].tenant), value: [.[] | {AP: .AP, EPG: .EPG}] }) | from_entries'
In the second part, I’ll look at getting the most out of icurl using the Cisco ACI API REST options, and in part #3, I’ll give you a ton of practical examples
Part #1 – JSON Query (jq) – icurl’s best friend

Lesson #1.1 – pretty print output using jq
The output from the icurl command can be delivered in either xml (using icurl http://localhost/api/node/class/fvAEPg.xml) or JSON format. I’ll be using JSON format for the rest of this article because the ACI APIC (and switches) come with the jq command-line JSON processor built in.
The trick to use jq to make the JSON output from icurl more readable – the simplest way is to simply pipe the output of the icurl command into jq
T17@apic1:~> icurl -s http://localhost/api/node/class/fvAEPg.json|jq
{
"totalCount": "8",
"imdata": [
{
"fvAEPg": {
"attributes": {
"annotation": "",
"childAction": "",
"configIssues": "",
"configSt": "applied",
"descr": "",
"dn": "uni/tn-infra/ap-ave-ctrl/epg-ave-ctrl",
"exceptionTag": "",
"extMngdBy": "",
"floodOnEncap": "disabled",
"fwdCtrl": "",
"hasMcastSource": "no",
"isAttrBasedEPg": "no",
"isSharedSrvMsiteEPg": "no",
"lcOwn": "local",
"matchT": "AtleastOne",
"modTs": "2024-03-14T15:08:33.838+11:00",
"monPolDn": "uni/tn-common/monepg-default",
"name": "ave-ctrl",
"nameAlias": "",
"pcEnfPref": "unenforced",
"pcTag": "32770",
"pcTagAllocSrc": "idmanager",
"prefGrMemb": "exclude",
"prio": "unspecified",
"scope": "3014656",
"shutdown": "no",
"status": "",
"triggerSt": "triggerable",
"txId": "5764607523034234882",
"uid": "0",
"userdom": "all"
}
}
},
<...snip...>
We can make jq work a lot harder than that.
Lesson #1.2 – Harnessing the Power of jq – selecting keys
Challenge: Extract the EPG name and pcTag values for all EPGs in the system.
JSON query has the power to extract the values of any key, so looking at the output above, the full path to the name key we are interested in is:
.imdata[].fvAEPg.attributes.name
To reference this specific key in jq simply requires the path above to be added to the jq command.
So to get just the name values:
T17@apic1:~> icurl -s http://localhost/api/node/class/fvAEPg.json | jq '.imdata[].fvAEPg.attributes.name' "ave-ctrl" "default" "SharedServices_EPG" "AppServers_EPG" "WebServers_EPG" "AppServers_EPG" "WebServers_EPG" "DB.Servers_EPG"
Tip:![]() |
If you don’t want the quotes symbols, add a -r (raw) option to the jq command:jq -r .imdata[].fvAEPg.attributes.name |
That’s great. But the challenge was to get the EPG name and pcTag values. To do that, I need to modify the jq query to
jq '.imdata[].fvAEPg.attributes | .name, .pcTag'
Note the use of the pipe | and the comma , between the key indexes. Here’s the result from my lab
T17@apic1:~> icurl -s http://localhost/api/node/class/fvAEPg.json | jq '.imdata[].fvAEPg.attributes|.name,.pcTag' "ave-ctrl" "32770" "default" "16387" "SharedServices_EPG" "10930" "AppServers_EPG" "49153" "WebServers_EPG" "32770" "AppServers_EPG" "49154" "WebServers_EPG" "49153" "BD.Servers_EPG" "49155""
Now that we’ve got icurl and jq producing the values we want, you’ll have noticed that the field names have disappeared. But don’t worry, you can add your own and produce your own JSON output by modifying the field selector portion – i.e. the bit that reads
.name,.pcTag
Lesson #1.3 – Making jq work for you – creating JSON output
Challenge: Add labels for the EPG name and pcTag from the previous output.
This is done by enclosing the key list in braces {} and adding your own keys – like this:
{EPG: .name, Class_ID: .pcTag}
Now your output will look like:
T17@apic1:~> icurl -s http://localhost/api/node/class/fvAEPg.json | jq '.imdata[].fvAEPg.attributes |{EPG: .name, Class_ID: .pcTag}' { "EPG": "ave-ctrl", "Class_ID": "32770" } { "EPG": "default", "Class_ID": "16387" } { "EPG": "SharedServices_EPG", "Class_ID": "10930" } { "EPG": "AppServers_EPG", "Class_ID": "49153" } { "EPG": "WebServers_EPG", "Class_ID": "32770" } { "EPG": "AppServers_EPG", "Class_ID": "49154" } { "EPG": "WebServers_EPG", "Class_ID": "49153" } { "EPG": "BD.Servers_EPG", "Class_ID": "49155" }
But given the same EPG names occur more than once, perhaps a little more information would be nice. Like the Tenant name and the Application Profile name.
And if you look at the output of the original command, you’ll see the dn key has all the information you need. E.g.
"dn": "uni/tn-infra/ap-ave-ctrl/epg-ave-ctrl",
Lesson #1.4 – Caressing jq – parsing keys
Challenge: Extract the Tenant, Application Profile and EPG names from the dn for all EPGs in the system.
Using the fact that the Tenant name, Application Profile name and EPG name always appears after a tn-, ap- or epg- prefix, and are always followed by a
/ character or end of line, I can use regex and jq‘s sub-command capture to extract exactly what we want between tn- and /ap, etc like this:
T17@apic1:~> icurl -s http://localhost/api/node/class/fvAEPg.json | jq '.imdata[].fvAEPg.attributes | (.dn|capture("tn-(?<T>.*)/ap").T), (.dn|capture("ap-(?<A>.*)/epg").A), (.dn|capture("epg-(?<E>.*)").E)' "infra" "ave-ctrl" "ave-ctrl" "infra" "access" "default" "common" "SharedServices_AP" "SharedServices_EPG" "Tenant01" "2Tier_AP" "AppServers_EPG" "Tenant01" "2Tier_AP" "WebServers_EPG" "Tenant17" "2Tier_AP" "AppServers_EPG" "Tenant17" "2Tier_AP" "WebServers_EPG" "Tenant17" "2Tier_AP" "BD.Servers_EPG"
In the example above T, A and E are just a variable names.
But the output is still a bit messy. So why not tidy it up by putting the output back into JSON format! The logic is the same as in Lesson #1.3, it is just a little more convoluted because of the all the captures.
So how about:
T17@apic1:~> icurl -s http://localhost/api/node/class/fvAEPg.json | jq '.imdata[].fvAEPg.attributes | {tenant: (.dn|capture("tn-(?<T>.*)/ap").T), AP: (.dn|capture("ap-(?<A>.*)/epg").A), EPG: (.dn|capture("epg-(?<E>.*)").E)}' { "tenant": "infra", "AP": "ave-ctrl", "EPG": "ave-ctrl" } { "tenant": "infra", "AP": "access", "EPG": "default" } { "tenant": "common", "AP": "SharedServices_AP", "EPG": "SharedServices_EPG" } { "tenant": "Tenant01", "AP": "2Tier_AP", "EPG": "AppServers_EPG" } { "tenant": "Tenant01", "AP": "2Tier_AP", "EPG": "WebServers_EPG" } { "tenant": "Tenant17", "AP": "2Tier_AP", "EPG": "AppServers_EPG" } { "tenant": "Tenant17", "AP": "2Tier_AP", "EPG": "WebServers_EPG" } { "tenant": "Tenant17", "AP": "2Tier_AP", "EPG": "BD.Servers_EPG" }
If only it were possible to group all the Application Profiles and EPGs within each tenant…
Lesson #1.5 – Deep dive into jq – advanced features
Challenge: Extract the Application Profile and EPG names for each Tenant, and display them grouped by Tenant.
To achieve this, I’ll need to dig deep into the powers of jq. See if you can figure how this works!
T17@apic1:~> icurl -s http://localhost/api/node/class/fvAEPg.json | jq '.imdata[].fvAEPg.attributes | {tenant: (.dn|capture("tn-(?<T>.*)/ap").T), AP: (.dn|capture("ap-(?<A>.*)/epg").A), EPG: (.dn|capture("epg-(?<E>.*)").E)}' | jq -s 'group_by(.tenant) | map({key: (.[0].tenant), value: [.[] | {AP: .AP, EPG: .EPG}] }) | from_entries' { "Tenant01": [ { "AP": "2Tier_AP", "EPG": "AppServers_EPG" }, { "AP": "2Tier_AP", "EPG": "WebServers_EPG" } ], "Tenant17": [ { "AP": "2Tier_AP", "EPG": "AppServers_EPG" }, { "AP": "2Tier_AP", "EPG": "WebServers_EPG" }, { "AP": "2Tier_AP", "EPG": "BD.Servers_EPG" } ], "common": [ { "AP": "SharedServices_AP", "EPG": "SharedServices_EPG" } ], "infra": [ { "AP": "ave-ctrl", "EPG": "ave-ctrl" }, { "AP": "access", "EPG": "default" } ] }
Wrap up
In this post I’ve shown how jq can be used in its simplest form to just pretty-print the output from an icurl command to a full-blown massage of the data to produce an elegant display of useful data.
In the next post, I’ll look more closely at how to get the data you want using some of the many Cisco ACI API REST options.
Until then, happy querying
RedNectar

Hi Chris,
Again a great post. My question is, how to paste the command with question mark into APIC CLI?
Unfortunately, the APIC CLI removes the
?when I paste the command directly. To work around I use:?characters.?usingCtrl+Vfollowed by?But I hope there is a better solution.
Thank you
Hi Karol,
The simplest answer is to make sure that you are at the bash prompt (as shown in all the examples –
T17@apic1:->), rather than theapic1#promptTo get to the bash prompt, simply type the command
bashat theapic1#promptTHX 🙂
Pingback: moquery vs icurl bake off | RedNectar's Blog
Pingback: Mastering Cisco ACI API using icurl and jq. Part 3 | RedNectar's Blog
Pingback: Mastering Cisco ACI API using icurl and jq. Part 2 | RedNectar's Blog
Hi,
Thank you for your blogpost, it is very informative.
I have question here because I don’t get it why it doesn’t work.
As an example dn:
“dn”: “uni/tn-TST-CUSTOMER2/ap-TST-CUSTOMER2/epg-VLAN401”,
for the given dn following should capture the TST-CUSTOMER2
icurl -k -s “https://localhost/api/node/class/fvAEPg.json” | jq ‘.imdata[].fvAEPg.attributes | {tenant: (.dn|capture(“tn-(.*)/ap”).T)}’
but instead I just get nothing returned. Do you habe may be an idea?
Thanks in advance!
Regards,
Oz.
Hi Oz,
I think stupid WordPress has reformatted your question and removed the < and > symbols and the characters between them. AND reformatted your quotes symbols!!!
Apart from that – I think you missed a question-mark – try
icurl -k -s "https://localhost/api/node/class/fvAEPg.jso" | jq '.imdata[].fvAEPg.attributes | {tenant: (.dn|capture("tn-(?<T>.*)/ap").T)}'