There’s a command line in the gofi post I wrote ten days ago that has been quietly bugging me ever since I hit publish:
gofips -H 10.20.30.1 -k --get > hosts.conf
Look at that -k. That’s me telling the tool “don’t bother checking the TLS certificate.” And the part you can’t see is worse: my UniFi admin username and password, sitting in environment variables. Both of those were compromises I made to get the thing working. Neither of them sat well.
They’re gone now. gofi speaks cloud API keys through Ubiquiti’s Site Manager connector, and the whole command becomes this:
gofips --get
No host. No -k. No password anywhere. That’s the post.
Well — mostly. There’s a genuinely useful lesson buried in here about credentials, and one gotcha that cost me twenty minutes, so let’s do it properly.
A scoped, revocable key beats handing over the master.
What Actually Changed
The old path — the one I shipped first — was the classic undocumented-UniFi-API dance. You log in with your admin credentials, the controller hands you a session cookie and a CSRF token, and you carry those around on every subsequent request. It works. It’s also the reason the tool needed to talk directly to the controller on my LAN, over that self-signed cert, with -k papering over the cert warning.
The new path routes through https://api.ui.com using an API key you create in your Ubiquiti cloud account. Two environment variables and you’re done:
export UNIFI_API_KEY=...
export UNIFI_CONSOLE_ID=...
To make the key: sign in at unifi.ui.com as Site Admin or Owner, click your profile icon, go to API, and create a key with the UniFi Applications → Network scope. Then find your console ID by asking Ubiquiti what consoles you own:
export UNIFI_API_KEY=...
curl -s -H "X-API-KEY: $UNIFI_API_KEY" https://api.ui.com/v1/hosts
Pull your console ID out of that response, export it, and every gofi tool — gofips, gofimac, gofinet — just works. No flags.
The switch is automatic, too: if UNIFI_API_KEY is set, gofi takes the connector path. If it isn’t, it falls back to username and password exactly like before. Nothing I already had scripted broke.
The Gotcha That Cost Me Twenty Minutes
Here it is, and I want it in bold because I want to save you the time:
A cloud API key from unifi.ui.com is NOT the same credential as an API key issued by the console’s own UI.
Your UDM Pro will happily let you mint a key in its local admin interface. That key is a perfectly good key… for talking to that console directly. The Site Manager connector will not accept it. You need the cloud key, created in your Ubiquiti account at unifi.ui.com, and it needs the Network scope on the UniFi Applications entry.
I did exactly the wrong one first, got a flat rejection, assumed my code was broken, and went spelunking in my own transport layer. Geesh. Two identical-looking secrets, two entirely different trust domains. Read that paragraph twice and don’t be me.
Why This Is Better Than It Sounds
Three reasons, and they’re all worth more than the flags they removed.
One: the credential is scoped and revocable. Think about what I was doing before. My UniFi admin login — the one with full control over my entire network, the one I have 2FA on for the web UI — was sitting in plaintext in a shell environment. And stuffing a password in an env var quietly defeats the 2FA I turned on, because the API path doesn’t ask for the second factor. If that env var ever leaked into a log, a shell history file, a screenshot during a demo, or a ps listing on a shared box, my remediation was change my Ubiquiti account password and then go fix every place that password lived.
An API key scoped to Network access is a different animal. It can’t change my account. If it leaks, I revoke that one key from the web UI, mint a new one, and I’m done in ninety seconds. Nothing else in my life changes.
This is just least privilege, and I know that’s an unglamorous phrase. But it’s the difference between “rotate a token” and “have a bad afternoon.” I learned that distinction in a place where compartmentalization wasn’t a best practice, it was the whole design philosophy — on a submarine, nobody gets the keys to everything just because it’s convenient.
Two: real TLS, actually verified. The -k is gone because api.ui.com has a real certificate from a real CA. My LAN controller never will — it’s self-signed, forever, and every tool that talks to it has to either skip verification or carry a pinned cert around. Skipping verification on your own LAN is mostly fine, and “mostly fine” is exactly the kind of habit that follows you into places where it isn’t. Now the default path is verified TLS with no flag to remember and no bad habit to build.
Three — and this is the one I actually use every day: it works when I’m not home.
The Second House Problem
In the last post I griped about this in passing:
Oh, and add a network at a second home? Double the friction!
The cookie-and-CSRF path needed to reach the controller directly. From my desk, fine. From anywhere else, that means a VPN back into the house, or an SSH tunnel, or some other bit of plumbing I have to stand up and maintain and debug at exactly the wrong moment. Which, realistically, meant I just didn’t do network administration when I wasn’t at home. I waited.
The connector doesn’t care where I am. It’s an outbound HTTPS call to Ubiquiti’s cloud, which then talks to my console. Sitting in an airport with a laptop, I can dump my fixed leases, add a reservation for a device someone else plugged in at the house, or ask which players are online. No tunnel. No VPN client. No firewall hole punched in anything.
Two networks in two places, one credential model, same three commands. That’s the change that turned this from a nice cleanup into something that actually altered how I work.
The controller is at home. I don't have to be.
What You Give Up
I’d rather tell you this than have you discover it.
gofips normally cross-checks your fixed-lease entries against the UDM’s live local DNS on --get, --set, and --add. That’s the drift-and-overlap audit — it’s what catches the case where a reservation exists but the name resolves somewhere else, or two entries are quietly fighting over an address. It’s a genuinely useful safety net and I’m glad it’s there.
That check needs a directly-reachable controller. The connector doesn’t give it one. So through the cloud path, the drift/overlap audit is skipped.
Everything else works. Reservations read, write, and validate exactly as before, --dry-run still previews the full change set, and the file is still the source of truth. But you don’t get the extra DNS sanity pass. My habit now: routine work goes through the connector from wherever I am, and every so often when I’m home I run a --get on the LAN path and let the audit do its thing.
And the old way is still fully supported. Don’t set UNIFI_API_KEY and gofi behaves exactly as it did before, -H and all. I didn’t replace an auth mode; I added the one I should have started with.
Don’t Get Me Wrong
An API key is still a bearer token. It’s still a secret sitting in your environment, and if you paste it into a public repo, someone gets Network scope on your console. Scoped and revocable is better — it is not magic. Keep it out of git, don’t echo it in demos, and rotate it when in doubt. The reason it’s an improvement isn’t that the secret stopped mattering. It’s that the blast radius shrank and the recovery got cheap.
That’s most of security engineering, honestly. You rarely eliminate the risk. You make the bad day smaller and the fix faster.
And Now the Agent Can Do It From Anywhere
One more thing, because it’s the part that makes me grin.
I wrote about the unifi-fixed-hosts skill that lets me just talk to my network — “is there a new device?”, “give it a fixed IP and call it xt5-lab”, done. That skill drives these exact CLI tools, which means it inherited this change for free. I didn’t touch a line of the skill.
So now that conversation works from a hotel room. Claude Code asks my network what’s on it, shows me the host block and the dry run, waits for me to say yes — with the hard review gate still firmly in place, because mutating live network config from an airport lounge is exactly when you want a human eyeball on the diff — and applies it. The tool got a better front door, and everything built on top of it walked right through.
And here’s where it gets genuinely fun, because I travel with a UniFi Travel Router — the UTR. Ubiquiti’s pitch for it is that it “instantly extends your UniFi Network wherever you are,” and for once the marketing copy is just… accurate. That little slab comes with me — hotel, rental, conference floor — and whatever I plug into it lands on my network instead of on somebody’s guest VLAN behind a captive portal. Which means the board sitting on the desk in front of me, three time zones from my house, is just another device on my network as far as my controller is concerned.
So I can pin it. By name. Right there, from the desk it’s sitting on.
That’s the part that actually changes my day. I drop a dev player on the travel router, ask Claude what just showed up, tell it to call the thing xt5-lab, eyeball the dry run, say yes. A minute later I’m typing ssh xt5-lab at a device I’m holding — with a name I chose and a reservation that survives a reflash, registered on the controller back home. No hunting for whatever address it grabbed. No hotel network nonsense in the middle.
The two halves finally meet: the travel router brings the device onto my network, and the connector brings the administration to wherever I happen to be sitting. Before this change I only had the first half — the device could join fine, but I couldn’t do a thing about its name or address without tunneling home first. Now I develop on the road exactly the way I develop at my desk, with the same names and the same muscle memory.
That’s the payoff of building on a clean module instead of a pile of scripts. I changed the auth layer in one place and three CLIs plus a Claude skill all got better.
Conclusion
Short version: gofi now supports cloud API keys through Ubiquiti’s Site Manager connector. Two env vars, no flags, verified TLS, works from anywhere, scoped credential you can revoke in ninety seconds. The username/password path still works if you want it. The only real cost is the local DNS drift audit, which the cloud path can’t perform.
If you’re running the old way, switch. It took me longer to write this post than it did to migrate. Just remember: cloud key from unifi.ui.com, not a key from the console’s own UI. That’s the whole trap.
And if you’re building anything that talks to infrastructure you own, take the general lesson even if you never touch UniFi: the moment your tool needs a human’s admin password to function, you’ve built something that can only ever be as safe as the sloppiest place that password ends up. Scoped machine credentials aren’t bureaucracy. They’re the thing that lets you stop being nervous about your own automation.
If this helps you, drop me a note on LinkedIn. And if you’re doing something clever with gofi through the connector — especially anything multi-site — I really want to hear about it.