If an agent needs a Neon account and the user is not around, it provisions a project now. A human claims it later if they want to keep it. The agent receives credentials scoped to one project, builds with standard Postgres tools, and hands over a claim link. Unclaimed projects expire in 72 hours (project.expires_at) and are capped at 100 MB storage and 1 GB transfer. Claim codes expire in 15 minutes (expires_in). Those are two clocks.

Start in the browser at neon.com/claimable-neon, or give an agent auth.md.

Quick start

If neon claim is not a command, or neon claim --help does not list create, use the Agent API tab.

Create a project and write its credentials to .env:

neon claim create --env-pull

Request Data API or Managed Better Auth when the app needs them:

neon claim create \
  --service data-api \
  --service auth \
  --env-pull

The CLI saves the project's identity assertion in its secure credential store. Existing commands then use the claimable project automatically:

neon branches list
neon psql --role-name neondb_owner

Agent discovery

If an agent needs a Neon account and the user is not around, neon.com/docs/llms.txt points at auth.md.

Claimable Neon implements auth.md. Agents can discover the complete authentication and provisioning flow without a vendor-specific integration.

Document or endpointPurpose
GET https://neon.com/auth.mdProtocol file (agent_auth.skill)
GET https://neon.com/.well-known/oauth-authorization-server/claimableToken, revocation, identity, and skill discovery. Issuer https://neon.com/claimable
GET https://claimable.neon.tech/.well-known/oauth-protected-resourceResource server metadata
GET https://claimable.neon.tech/.well-known/jwks.jsonPublic keys for verifying Claimable Neon tokens
POST https://claimable.neon.tech/v1/agent/identityProvision a project and issue a durable identity assertion
POST https://claimable.neon.tech/v1/oauth2/tokenExchange the assertion for a short-lived bearer access token
POST https://claimable.neon.tech/v1/oauth2/revokeRevoke an access token or identity assertion
GET https://claimable.neon.tech/v1/projects/{project_id}/credentialsRead scoped project and service credentials
POST https://claimable.neon.tech/v1/projects/{project_id}/claimCreate a short-lived human claim code
GET https://claimable.neon.tech/v1/projects/{project_id}/claimRead claim and reconciliation status
DELETE https://claimable.neon.tech/v1/projects/{project_id}Delete an unclaimed project
/v1/projects/{project_id}/... on claimable.neon.techUse supported Neon Management API operations before claiming

The identity assertion is a secret. Store it like an API key. There are no refresh tokens. Exchange the assertion again when an access token expires.

Register and provision

POST /v1/agent/identity
Content-Type: application/json
{
  "type": "anonymous",
  "capabilities": ["postgres", "data_api", "auth"],
  "source": "example-agent"
}

postgres is always requested. Add data_api or auth only when the app needs them.

The response has this shape:

{
  "registration_id": "reg_...",
  "identity_assertion": "eyJ...",
  "project": {
    "id": "quiet-fog-12345678",
    "branch_id": "br-...",
    "expires_at": "2026-08-14T12:00:00.000Z"
  },
  "capabilities": [
    {
      "capability": "postgres",
      "granted": true,
      "scopes": ["postgres.read", "postgres.write"]
    },
    {
      "capability": "data_api",
      "granted": true,
      "scopes": ["data_api.query"]
    }
  ]
}

Check every capability decision. A successful registration can contain denied optional capabilities.

Exchange the identity assertion

curl --request POST https://claimable.neon.tech/v1/oauth2/token \
  --header 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer' \
  --data-urlencode 'assertion=YOUR_IDENTITY_ASSERTION' \
  --data-urlencode 'resource=https://claimable.neon.tech/'

The response contains a bearer access_token, its scope, and its expiration:

{
  "access_token": "eyJ...",
  "token_type": "Bearer",
  "expires_in": 900,
  "scope": "postgres.read postgres.write data_api.query"
}

Pull credentials

curl https://claimable.neon.tech/v1/projects/quiet-fog-12345678/credentials \
  --header "Authorization: Bearer $ACCESS_TOKEN"
{
  "project_id": "quiet-fog-12345678",
  "branch_id": "br-...",
  "database_url": "postgresql://neondb_owner:...@ep-...-pooler.../neondb?sslmode=require",
  "expires_at": "2026-08-14T12:00:00.000Z",
  "services": {
    "data_api": {
      "url": "https://ep-....apirest.c-2.us-east-2.aws.neon.tech/neondb/rest/v1"
    },
    "auth": {
      "base_url": "https://ep-....neonauth.c-2.us-east-2.aws.neon.tech/api/auth",
      "jwks_url": "https://ep-....neonauth.c-2.us-east-2.aws.neon.tech/.well-known/jwks.json"
    }
  }
}

Only requested and granted services appear under services.

Capabilities

CapabilityAvailable before claimEnvironment variable
PostgresYesDATABASE_URL
Data APIWhen requested, or later with neon deployNEON_DATA_API_URL
Managed Better AuthWhen requested, or later with neon deployNEON_AUTH_BASE_URL
FunctionsNoRequires claiming the project
Object StorageNoRequires claiming the project
AI GatewayNoRequires claiming the project

Registration records those as { granted: false, reason: "requires_claim" }. A later protected operation returns capability_requires_claim. Preserve the denied capability and give the human a claim link; do not retry or drop it.

After create, add Auth or the Data API with neon.ts and neon deploy on the unclaimed project. After claim, the same config talks to Neon directly.

Use the Neon CLI

neon claim and its neon claimable alias manage anonymous projects. If neon claim is not a command, or neon claim --help does not list create, use the HTTP flow in this page.

neon claim create --env-pull

neon claim create --service data-api --service auth --env-pull

neon claim status

neon claim accept --no-open

neon claim list

neon claim delete --yes

neon claim accept opens a browser by default. --no-open prints the URL for a human to open.

After claim create, regular Neon CLI commands exchange the saved identity assertion and route supported management operations through Claimable Neon. Explicit Neon account credentials take precedence when you pass them.

If a neon.ts file is present, claim create requests its declared services automatically:

neon.ts
import { defineConfig } from '@neon/config/v1';

export default defineConfig({
  auth: true,
  dataApi: true,
});

Services that require human ownership still run through normal Config-as-Code planning. Claimable Neon returns capability_requires_claim for those operations, so the agent can ask for a claim instead of silently omitting part of the configuration.

Claim a project

Create a claim code with the API:

curl --request POST \
  https://claimable.neon.tech/v1/projects/quiet-fog-12345678/claim \
  --header "Authorization: Bearer $ACCESS_TOKEN"
{
  "user_code": "ABCD-2345",
  "verification_uri": "https://claimable.neon.tech/claim",
  "verification_uri_complete": "https://claimable.neon.tech/claim?user_code=ABCD-2345",
  "expires_in": 900,
  "interval": 5
}

expires_in is 900 seconds (15 minutes) today. If the unused code expires, POST /claim again. Each POST cancels the previous unused code and returns a new one. Re-issue only while project.expires_at is still in the future.

Open verification_uri_complete and sign in to Neon. Opening the URL does not freeze access. Continuing to Neon starts a transfer with a new 15-minute window: it revokes the project key, access tokens, and database password before the console transfer URL is shown. Auth and the Data API stay enabled and transfer with the project if they were enabled. If that transfer window expires before you accept, POST /claim again. The project key and database password stay revoked.

Choose the destination organization. The project then moves through these states:

  1. pending: the claim code exists, but the transfer has not completed.
  2. accepted: the project has left the unclaimed-project organization.
  3. reconciled: the identity assertion is revoked and the ceremony is finished.

Continuing to Neon revokes existing access tokens. Re-exchange the identity assertion, then poll claim status at the server-provided interval. The new token has no project scopes. It authorizes that poll, and a replacement claim code if the transfer window expires.

curl --request POST https://claimable.neon.tech/v1/oauth2/token \
  --header 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer' \
  --data-urlencode "assertion=$IDENTITY_ASSERTION" \
  --data-urlencode 'resource=https://claimable.neon.tech/'

curl https://claimable.neon.tech/v1/projects/quiet-fog-12345678/claim \
  --header "Authorization: Bearer $ACCESS_TOKEN"

Only reconciled means the assertion is dead. Use credentials from the destination Neon organization after that. Fetch a new DATABASE_URL there; Auth and the Data API keep working if they were enabled.

Add Auth or the Data API with neon.ts and neon deploy before or after claim. Data API with the default auth provider requires Auth. An external JWKS is only accepted after claim:

neon.ts
import { defineConfig } from '@neon/config/v1';

export default defineConfig({
  auth: true,
  dataApi: true,
});
neon deploy

neon checkout does not apply this to an existing branch. neon deploy (alias of neon config apply) does.

dataApi: {
  authProvider: 'external',
  jwksUrl: 'https://example.com/.well-known/jwks.json',
}

Errors

Errors use one JSON shape across provisioning, token, credential, and management endpoints:

{
  "error": {
    "code": "capability_requires_claim",
    "origin": "proxy",
    "message": "Functions require claiming this project.",
    "retryable": false,
    "request_id": "req_..."
  }
}

Use error.code for control flow and show error.message to the user. Retry only when retryable is true.

Common codes include:

CodeMeaning
invalid_requestThe request body or parameter is invalid
invalid_grantThe identity assertion cannot be exchanged. Discard it
unauthorizedNo credential was presented, or it did not verify
token_expiredThe access token expired. Re-exchange the identity assertion
scope_insufficientThe access token does not permit the operation
capability_requires_claimThe requested service or operation requires human ownership
claim_in_progressThe transfer window is still live. Poll status; mint a new code after it expires
project_claimedThe project transferred. Discard the identity assertion
project_expiredThe unclaimed window closed. Discard the identity assertion
upstream_errorA Neon API or service dependency failed

Resources