feat: add Claimable Neon page, docs and auth.md agent discovery - #5507
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
De-ASI-INTERFACE
approved these changes
Aug 12, 2026
andrelandgraf
force-pushed
the
feat/claimable-postgres-page
branch
from
August 16, 2026 16:02
7fd5cb9 to
a53288d
Compare
andrelandgraf
marked this pull request as ready for review
August 16, 2026 16:02
andrelandgraf
requested review from
danieltprice,
philip and
ruf-io
as code owners
August 16, 2026 16:02
De-ASI-INTERFACE
approved these changes
Aug 16, 2026
De-ASI-INTERFACE
approved these changes
Aug 16, 2026
Keep both neon plugins (from main) and neon claim (this branch) in the CLI nav and schema.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The job
neon.com is where both humans and agents arrive first, and neither had a path to a Claimable Neon project from here. A human had no page on neon.com that provisions one. An agent had no document on neon.com that describes the protocol, so the only discoverable entry was the retired
claimable-postgresagent skill, which describednpx neon-newandneon.newrather than the current identity and claim flow.This branch makes neon.com the entry for both:
/claimable-neonprovisions a temporary project from the browser, backed by a server route./auth.mdis the protocol document an agent fetches, linked as the first Common Queries entry indocs/llms.txt./.well-known/oauth-authorization-server/claimableis the RFC 8414 metadata that points an agent at the token, revoke and JWKS endpoints.claimable.neon.techremains the service. neon.com only discovers and proxies.Why the issuer is a path identifier
Publishing this metadata at
https://neon.com/.well-known/oauth-authorization-serverwould make neon.com itself the authorization server for every agent that probes the apex. RFC 8414 allows an issuer with a path component, and the metadata for such an issuer is served with that path inserted after.well-known. So the issuer ishttps://neon.com/claimable, the document is at/.well-known/oauth-authorization-server/claimable, and neon.com's apex well-known path stays unused.The protocol document has to live on the issuer host for that to hold, which is why
auth.mdsits athttps://neon.com/auth.mdand not onclaimable.neon.tech.scripts/verify-agent-endpoints.mjsasserts both: thatagent_auth.skillshares the issuer's origin, and that the issuer path is non-empty.The page
/claimable-neonrenders three sections.The hero holds the provisioning form. Lakebase Postgres is always included and shown as a locked row. Data API and Managed Better Auth are toggles, each labelled "Stays enabled after claim." Below the button: "No account or payment details required. Unclaimed projects expire in 72 hours and are capped at 100 MB storage and 1 GB transfer."
On success the form is replaced in place by a result card that focuses its own heading and announces itself through
role="status". It shows the project id, one pill per capability decision with denied ones spelled out underneath, and copy buttons forDATABASE_URL,NEON_DATA_API_URL,NEON_AUTH_BASE_URLand the claim link. The claim link is copyable as well as openable, because opening it in a new tab is not the only way a person needs it. Two buttons follow: "Open the claim link" and "Create another project", the second of which resets the form.The card states what claiming costs, keyed to what was actually granted rather than to a fixed sentence:
The middle section shows the same service through
auth.md, the Neon CLI andneon.ts, each with a copyable sample. The CLI card carries the fallback in its own description: ifneon claimis not a command, use theauth.mdcard.POST /api/claimable-neon
The page calls one route. The request body is the optional service list; anything outside
authanddata-apiis refused before a single upstream call.{ "project": { "id": "quiet-fog-12345678", "branch_id": "br-...", "expires_at": "2026-08-14T12:00:00.000Z" }, "capabilities": [ { "capability": "postgres", "granted": true }, { "capability": "data_api", "granted": true }, { "capability": "auth", "granted": true } ], "credentials": { "project_id": "quiet-fog-12345678", "branch_id": "br-...", "database_url": "postgresql://...", "services": { "data_api": { "url": "https://....../rest/v1" }, "auth": { "base_url": "https://....../api/auth" } } }, "claim": { "verification_uri_complete": "https://claimable.neon.tech/claim?user_code=ABCD-2345", "expires_in": 900 } }Status is 201, and every response carries
Cache-Control: no-store.The route makes four upstream calls in order: register an anonymous identity with
postgresplus the requested services andsource: "neon_website", exchange the returned assertion for an access token, read credentials, create a claim code. The identity assertion is used server-side and is not part of the response, so the browser holds the project credentials and the claim link and nothing durable. The CLI path is where an assertion gets persisted.Every upstream payload is shape-checked before it is used. The claim link is additionally checked against the configured origin, so a redirected or substituted
verification_uri_completenever reaches the page as a button.If anything fails after registration succeeded, the route deletes the project it just created, minting a token first if the failure happened before it had one.
error.codeinvalid_requestapplication/json, the body is not JSON, orservicescontains anything other thanauthanddata-apiinvalid_configurationCLAIMABLE_NEON_ORIGINis not a bare http(s) originclaimable_service_unavailableinvalid_claimable_responseclaimable_cleanup_failedrate_limit_exceededand itsretryableflag survive to the pageCLAIMABLE_NEON_ORIGINis new in.env.exampleand defaults tohttps://claimable.neon.tech. It must be an origin with no path, query or fragment; a value likehttps://claimable.example/v1fails the request withinvalid_configurationrather than being silently trimmed.What an agent fetches
docs/llms.txtgains a first Common Queries entry, "Need a Neon account and the user is not around: provision one now; they claim it later", pointing athttps://neon.com/auth.md.auth.mdputs the CLI first and the HTTP flow second. It opens with an account check, so an agent that already has credentials does not end up on this path:Then the CLI, with the fallback stated rather than assumed:
The HTTP sections document registration, the JWT bearer exchange, credentials, the claim ceremony and revocation. Two details are called out because they are the ones a client gets wrong: the metadata's
claim_endpointtakes{"claim_token": "<identity_assertion>"}while the project-scopedPOST /v1/projects/<id>/claimuses the access token, and both create the same claim code; and continuing to Neon revokes existing access tokens, so the assertion has to be re-exchanged for a token that authorizes claim-status polling only.claimable.neon.tech/auth.mdis named as the authority for field names, so this copy cannot drift into being wrong about the wire format.Issuer metadata
{ "issuer": "https://neon.com/claimable", "token_endpoint": "https://claimable.neon.tech/v1/oauth2/token", "revocation_endpoint": "https://claimable.neon.tech/v1/oauth2/revoke", "jwks_uri": "https://claimable.neon.tech/.well-known/jwks.json", "grant_types_supported": ["urn:ietf:params:oauth:grant-type:jwt-bearer"], "token_endpoint_auth_methods_supported": ["none"], "response_types_supported": [], "agent_auth": { "skill": "https://neon.com/auth.md", "identity_endpoint": "https://claimable.neon.tech/v1/agent/identity", "claim_endpoint": "https://claimable.neon.tech/v1/agent/identity/claim", "identity_types_supported": ["anonymous"] } }The values come from a
CLAIMABLEblock insrc/constants/agent-discovery.js, which is where every other agent-discovery surface already keeps its facts. The route handler is a one-line wrapper around the builder, andconfig/agent-endpoints.yamlregisters the endpoint so the existing verifier picks it up. The newclaimable-authorization-servervalidator checks the RFC 8414 shape, compares issuer, token endpoint, JWKS, skill and identity endpoint against the constants, requiresnoneintoken_endpoint_auth_methods_supported, and readspublic/auth.mdto confirm it contains all five URLs.public/auth.mdis added to the workflow's path filter so editing the document reruns that check.CLI reference
content/docs/cli/claim.mddocumentsneon claim, withneon claimableas an alias, in the setup group of the CLI index.The page states what the generated schema cannot:
--api-keyand--profileare refused,--env-pullis on by default and writes to an existing.envor otherwise.env.local,neon.tssupplies the service list when it is present, and opening the accept URL does not freeze access while continuing to Neon rotatesDATABASE_URL.scripts/docs-checks/neonctl/overrides.jsondescribes the repeatable--serviceflag.Naming and redirects
Claimable PostgresbecomesClaimable Neonacross docs, and the reference page moves./claimable-postgres/claimable-neon/claimable/claimable-neon/launchpad,/instagres/claimable-neonhttps://neon.new/docs/reference/claimable-postgres,.md/docs/reference/claimable-neon/docs/ai/skills/claimable-postgres/*and the four.well-knownskill paths/auth.mdThe retired skill's URLs go to
auth.mdrather than to the docs page, because whatever followed those URLs was an agent looking for a protocol.The
claimable-postgresskill is gone fromconfig/skills.json, both.well-knownskill indexes, the AI catalog and the static markdown manifest. Its content is now split betweenpublic/auth.mdand a reference file under theneonskill.The
neonskill is pinned to8a4200b2fdf8c4a5b301c717064d8aeb58c8eea5instead of trackingmain, because this branch depends on the version of that skill which carriesreferences/claimable-neon.mdand the "Starting without a Neon account" section. That section gates the path twice: the account check first, then an explicit ask before using Claimable Neon at all, since a temporary project is a workaround rather than the normal way to get a Neon project.Also in here
neon-postgres-agent-platformsstill tracksmain, so re-running the skill sync pulled its upstream "SDK vs agent tools" content intopublic/docs/ai/skills/. Unrelated to Claimable Neon, and it is what the sync produces on this ref.llms.txttagline insrc/scripts/llms-index-config.jswas rewritten to the current product wording (Lakebase Postgres, Managed Better Auth) while the Common Queries entry was added to the same file.generate-skills-index.test.jsusedclaimable-postgresas its second fixture skill. It now usesneon-functions, so the test no longer depends on a skill that is not installed.content/docs/guides/tanstack-start.mdloses a five-tabnpx neon-newblock in favour of one sentence, andcontent/docs/guides/platform-integration-overview.mdnow distinguishes the two flows: neon.new keeps the connection string, Claimable Neon rotatesDATABASE_URL.scripts/verify-agent-endpoints.mjsinside the existing models.dev branch, from the formatter.mainby merge.neon plugins(landed on main) andneon claimboth stay in the CLI nav and the 4.4.0 schema.Verification
At
3119597:npx vitest runpasses, 778 tests across 46 files.npm run verify:agent-endpointspasses, 9/9 endpoints and 44/44 checks, including all 14 checks of the newclaimable-authorization-serverentry.npx eslintclean on the route, the page, the component, the metadata handler and the constants.The 12 route tests in the diff cover, as behaviours:
services: ["auth", "data-api"]becomescapabilities: ["postgres", "data_api", "auth"]expires_inof-1, both rejected and the project deletedclaimable_service_unavailableCLAIMABLE_NEON_ORIGINset to an origin with a path, and to a bare hostname, both refused before any callThe page was driven in a browser against live Claimable Neon: idle form, create (postgres only, and Data API plus Auth), result card with
Copy Claim link(accessible name becomesCopied), explainer copy that opening the claim link does not freeze access, andCreate another projectreturning the idle form. Redirects are declarative innext.config.jsand were not exercised against a running server.For your attention
/claimable-neonis reachable through the redirects, the docs pages andlinks.claimableNeon, but nothing in the header or footer menus points at it. That is deliberate for now; adding it is a separate decision./launchpadand/instagresno longer leave neon.com. They were temporary redirects tohttps://neon.newand now land on/claimable-neon. Anyone who bookmarked them expecting neon.new gets a different product surface with a different claim behaviour.DATABASE_URL; neon.new did not. Every surface in this branch says so, and the platform integration guide now contrasts the two explicitly, but it is a real behavioural difference for partners who documented the old flow.expires_atandexpires_inare rendered as local times without a server-side sanity check beyond "parses" and "positive". A wrong clock upstream shows a wrong deadline.claimable_cleanup_failedreturns 500 and leaves the project behind. The response says it will expire on its own, which is true, but a run of these leaves unclaimed projects that nobody asked for. The failure is logged with the project id.expires_atand the result card uses it, but the pre-provisioning line under the button does not, so a change to the service defaults needs a change here.neonskill pin will go stale. It is there so this branch does not depend on an unmerged upstream state. It should go back tomainonce the pinned commit is the tip.