August 14, 2026

Netlify’s Database API Has 19 Operations. The One My Agents Need Returns 401.

Dear Netlify,

I’m one of yours. For the last several weeks I’ve been building a product on your platform: a personal relationship-intelligence app (think private, single-user CRM, or customer relationship management) built as a PWA (progressive web app) over about 500 real people. It has a JSON API and an MCP (Model Context Protocol) server so my AI agents can work the same data I do. React on your CDN (content delivery network), Netlify Functions, Netlify Database, the works. The engineering team is me plus Claude Code sessions, and the choice of your platform was not incidental. When Matt Biilmann wrote that Agent Experience (AX) is “the holistic experience AI agents will have as the user of a product or platform”, I decided to test the thesis the only way that means anything: bet a real product on it. “Is this the Netlify-intended path?” is a standing design rule in my repo.

The bet is mostly paying off. My agent team shipped 81 pull requests in 16 days, July 30 to August 14, with continuous integration (CI), signed-commit provenance checks, and pre-merge browser testing against your deploy previews. That velocity is partly your doing, and I’ll get specific about the credit before I get specific about the problem.

Because there is a problem. One API operation my agents need returns 401 {"message":"This operation must be performed from the Netlify dashboard."}. An error message that tells an autonomous agent to go click a UI. From the company that coined AX, more than a year after declaring AX “in practice”, that sentence is the whole story. So this is an open letter: here’s what you got right, here’s exactly where it breaks, and here’s the short list of fixes, most of them small. (And if you searched your way here holding that error and just need the current answer, jump to the postscript, then come back.)

What you got right (and it’s a lot)

The best agent-native database idea shipping anywhere right now is yours. When Netlify Database went to general availability (GA) in April 2026, its flagship behavior was that every deploy preview automatically gets its own forked database branch: a full copy of production data, migrations auto-applied, no code changes, and previews cost zero credits. Your docs promise that “every agent run gets its own database branch automatically, so agents can experiment… in fully isolated environments,” and it’s true. It let my team move every piece of data-bearing verification (API smoke tests, seed-fidelity audits, two-browser Playwright journeys) onto free previews before merge, against the full dataset.

And it’s not just the database:

  • netlify logs --json gives an unattended agent structured, streaming function logs. That’s real observability for a reader with no eyes.
  • netlify api <operation> exposes your entire OpenAPI surface as a CLI passthrough. It’s how we measured everything in this post.
  • The cost model is agent-shaped. Previews and branch deploys are free; only a production deploy bills. An agent can verify everything pre-merge without spending a dollar.
  • You ship documentation written for agents. The context-and-tools repo with its per-domain SKILL.md files answers Biilmann’s machine-ready-docs question in the most literal way possible. Almost nobody else does this.

I mean all of that. Which is why the next part reads the way it does.

Branches are born and never die

Here’s the thing about minting a database branch for every pull request: something has to delete them. Nothing does. No TTL (time-to-live), no cleanup when the PR closes, no expiry when the preview deploy expires. Your own troubleshooting docs quietly admit this: the documented remedy for hitting the branch limit is “Delete branches that are no longer needed (e.g. for closed pull requests), or upgrade your plan.” Closed PRs leaving live database branches behind is the expected state of the product.

At my team’s velocity that’s about 3.6 new branches a day, which makes any branch cap a date on the calendar rather than a hypothetical. On August 14 I counted 59 branches on one database, and the arithmetic reconciled exactly: every PR whose preview ever built, plus production, main, and two agent runs, minus nothing. Branches mint about 20 seconds after a PR opens; even a chunk of my docs-only PRs, which are supposed to skip preview builds, minted branches anyway. At one point I realized that opening a pull request for the research document about branch accumulation was going to mint another branch. The leak documents itself.

Fine, I thought. This is a managed platform with a real API. We’ll automate the cleanup.

One delete operation, three contradicting stories

My agents enumerated all 19 database operations in your OpenAPI spec (v2.57.0). Exactly one deletes a branch: deleteSiteDatabaseBranch. There’s no archive, no prune, no bulk delete. The only other operation that reduces your branch count is deleteSiteDatabase, which destroys the entire database, every branch, and every snapshot. The API’s one working alternative to deleting a branch is deleting everything.

The spec documents the branch delete as an ordinary authenticated delete: a 204 on success, no auth caveat, no per-operation security override; the same OAuth security as the GET on the identical path. So we called it. As of August 13, with both a Personal Access Token and the CLI’s own OAuth token:

DELETE /sites/{site_id}/database/branch/{branch_id}
→ 401 {"message":"This operation must be performed from the Netlify dashboard."}

while GET on the identical path succeeds with the same token. (Strictly speaking that’s a 403’s job; the token is fine, the policy isn’t.) When we searched for that error string, we got zero hits on the indexed web. As far as I can tell, my agents are the first to publish it, which is part of why I’m publishing it.

It gets stranger. Three of your surfaces tell three different stories about this one endpoint:

  1. The OpenAPI spec keys the delete by branch_id (“The branch ID”).
  2. Your docs site keys the same path by {deploy_id}: “Deletes the database branch associated with a deploy.”
  3. The live server, probed read-only, resolves the branch name and returns Not Found for a real deploy id.

Three identifier stories, and the API disagrees with two of them. Meanwhile the dashboard docs, the place the 401 sends you, document branch management as view, edit data, and copy connection string. No delete-branch action appears; the role matrix covers deleting a database and never mentions deleting a branch. The delete button exists in the UI, but the error message directs you to a capability the docs say isn’t there.

And here’s the one that actually stings, because it’s so close to great. Your own agent context file for the database, skills/netlify-database/SKILL.md, tells agents: “DELETE /sites/{site_id}/database/branch/{deploy_id} | Delete a deploy’s branch,” and responsibly adds that branch deletes “require explicit user confirmation first.” You wrote AX-grade agent documentation, with safety guidance, for an endpoint that 401s every agent that follows it.

Now, the generous reading, because it deserves stating: maybe the 401 is a deliberate safety gate. Branch deletion is destructive, agents are new, and one of the forum threads I’ll cite below involves production data lost to an automated cleanup. If that’s the reasoning, I respect the instinct. But look at the implementation: the gate is documented nowhere, it contradicts your own spec and your own agent skill (which already prescribes the right safeguard: explicit human confirmation), and it blocks the human too. There is no confirmation to give: the call fails with the CLI’s own OAuth token, the token a human mints by logging in interactively, exactly as it fails with an agent’s access token. A gate that protects by being secret still fails the AX test. The fix for “destructive operations need confirmation” is a documented confirmation mechanism, or a soft delete, or a TTL. It isn’t an unexplained 401.

One more thing my agents checked so yours don’t have to: this isn’t an upstream limitation. You launched this product as Netlify DB, “powered by Neon”, and in Neon, branch deletion is a first-class primitive: one CLI command, one API call. Your GA docs no longer name the engine underneath, so I won’t guess at today’s architecture, but whichever copy-on-write Postgres sits down there can delete a branch. The gate lives in your platform layer, a product choice. (The beta’s escape hatch, claiming your database into your own Neon account, isn’t documented for GA databases either.) And either way, the contract my agents hold is with your API, not your vendor’s.

We checked every other door. The official @netlify/mcp server (v1.15.1) has no database branch tools at all; its roadmap comment in src/tools/index.ts reads [] database, unchecked, on a main branch pushed August 13. (Its one database-adjacent tool returns an instruction to install the npm package.) The CLI (v27.1.1) has no branch subcommand. The MCP server resolves the same token, so it can’t out-privilege one. And browser-automating the dashboard is off the table: your Acceptable Use Policy reads as forbidding automated access to the website except through the API, and an agent that respects ambiguous terms has to treat that as a no. The carve-out is the exact surface that returns the 401.

I’m not the only one standing at this wall. A Pro-plan customer has been stuck at “193 branches out of the 150 allowed limit” since June, agent runs failing, credits burning, and worse: their branches are phantoms the dashboard itself won’t delete, so even the door the 401 points to is closed for them. The thread has no staff reply after ten weeks. On August 14 another builder joined it: “Been trying to figure out an AX solution for Claude Code and am stuck as well. I can’t believe the answer is I have to delete these in the GUI?” A second thread describes a database deleted during routine cleanup of idle branches, production down, also unanswered. I don’t read malice in any of this. I read an area of the platform that hasn’t yet gotten the support muscle the rest of it clearly has.

Before I lecture anyone: my agents were wrong too

Honesty requires this section. While investigating your API, my agent team produced four bad revisions of its own analysis, and every error had the same shape: asserting how a system behaves instead of reading it. The best one: an early version of our cleanup script matched 47 branches for deletion, deleted none of them (it had read the wrong id field), and calmly printed “Deleted 0”. A no-op formatted exactly like success. The fix that lives in the repo now throws an error rather than report a partial reclaim as done, and the research file carries a house rule stamped on top: “re-verify by attempting, not by trusting this file.”

Agent teams fail exactly the way platforms fail: by shipping confident text that disagrees with the running system. The cure is the same on both sides. Make the truth machine-checkable, and when the docs and the probe disagree, the probe wins.

Which brings me to the twist. My team spent a day of effort and some genuine alarm believing we were over a 50-branch cap, because that’s what our cached notes said. A second adversarial pass re-read your billing docs: the limits are 20 branches on Free, 100 on Personal, 300 on Pro, 450 on Enterprise. We were at 59 of 100. Never in danger. And the caps have moved: that June forum user’s error message said 150 branches was the Pro limit; your billing docs now say 300. Your database changelog says nothing about it; three entries total, none since June 22. I’ll own my team’s stale cache. But this is the AX lesson in miniature: agents cache facts harder than humans do. When limits change with no changelog entry, every cached copy (human memory, agent memory, vendored docs) goes stale with no signal. Changelog discipline is an AX feature.

The scorecard, in your own terms

Biilmann’s essay poses three questions for platforms. Here’s how the database branch story answers each of them.

“Is it simple for an Agent to get access to operating a platform on behalf of a user?” For deploys, logs, environment variables, previews: yes, genuinely, better than anyone. For the database branch lifecycle: the only door is a 401 whose body says “use the dashboard,” and the dashboard is fenced against automation by policy. The agent is structurally locked out of a maintenance task that the platform’s own automation creates.

“Are there clean, well described APIs that agents can operate?” The API is clean. The descriptions are the problem: the spec, the docs site, and the live server tell three different stories about the one endpoint that matters here, and the error it returns points to a dashboard capability the docs don’t describe.

“Are there machine-ready documentation and context for LLMs and agents to properly use the available platform and SDKs?” They exist, and that’s real credit. They also disagree with the spec, the server, and each other, and the limits they’d cache moved with no changelog entry. Machine-ready docs that are wrong are worse for agents than no docs. Agents don’t squint. I’ve seen this from the publisher’s side too: when I read my own server logs to see what actually crawls this blog, the machine readers were already the audience.

AX isn’t a feature list. It’s a contract-keeping discipline. An agent’s entire experience of your platform is: read the contract, call the API, trust the response. Every place the contract and the server disagree is 100 percent of some agent’s experience.

The asks

All of these are specific, and most are small.

  1. Make deleteSiteDatabaseBranch work as documented, or document the gate and say why. Either one resolves the AX break; silence is the only wrong answer. While you’re in there, reconcile the identifier: the spec says branch_id, the docs say deploy_id, the server resolves names.
  2. Ship branch lifecycle. You already know the deploy and PR that created each branch; deploy objects carry database_branch_id. “Delete the branch when its PR closes” is one webhook away from making this entire post unnecessary. Even a TTL would do.
  3. Check the [] database box in the MCP roadmap. An agent-native database whose official MCP server has no database tools is half a sentence. Finish the sentence.
  4. Changelog the limits. The caps changed (at least the Pro cap, from 150 to 300) and nobody was told. Silent limit changes are AX debt, and agents compound it.
  5. Serve your docs through your MCP server. This is the one I’d build first. Why does a coding agent have to scrape docs.netlify.com? Vectorize the docs and let @netlify/mcp answer “how do I delete a database branch” with the current, correct, versioned answer. You’d be the first platform where an agent never has to guess which of three contradictory pages is true.
  6. Put some support muscle behind the database tags in your forum. Ten quiet weeks on a branch-limit thread, and an unanswered data-loss report, undercut a lot of good keynote material. The people in those threads are your believers. They’re building the case studies.

For the builders (and agents) who searched their way here

If you landed on this post holding the same error, here is the current answer, as of August 14, 2026. There is no API, CLI, or MCP path to delete a Netlify Database branch. deleteSiteDatabaseBranch returns 401 {"message":"This operation must be performed from the Netlify dashboard."} (still returning as of my last check, August 13) even though the spec documents it as an ordinary authenticated delete and Netlify’s own agent skill tells agents to call it. Deletion works only as a manual action in the Netlify dashboard: open your database’s branch list and use the delete action on each branch row; it’s there, though the docs don’t mention it. I know of no supported way to stop branches minting in the first place; in my measurement, even pull requests that skipped their preview builds minted branches anyway. The per-database branch caps are 20 (Free), 100 (Personal), 300 (Pro), and 450 (Enterprise), per the billing docs linked above. You can count your branches with netlify api listSiteDatabaseBranches, but filter the output down to ids, names, and dates. The response carries live connection strings for every branch, and neither you nor your agent should ever print those to a log.

If Netlify ships a fix, I’ll update this post and say thank you in the same place I said all this. That’s the deal with an open letter.

Matt, if you read this far: I didn’t write two thousand words because I’m annoyed. I wrote them because I think you’re right. AX is the correct frame for this era, you coined it, and your platform is closer to living it than anyone else’s, which is exactly why the gap between the keynote and the 401 is worth two thousand words of a customer’s Thursday. The reproduction steps are all linked above; treat them as a bug report with love. We’re rooting for you.

Darin

This is the first post in a series on building with an AI engineering team on Netlify. The product runs; the agents ship; the interesting part is where the platform meets the practice.