CtrlK
BlogDocsLog inGet started
Tessl Logo

neo4j-aura-provisioning-skill

Provisions and manages Neo4j Aura instances via CLI (aura-cli v1.7+) or REST API. Use when creating, pausing, resuming, resizing, or deleting AuraDB Free/Professional/Business Critical/VDC instances; downloading credentials; scripting CI/CD pipelines; polling async status; or using the Terraform neo4j/neo4j-aura provider. Covers auth setup (client credentials OAuth2), credential lifecycle (download once — never recoverable), instance type selection, region codes, and Python provisioning scripts. Does NOT handle Cypher queries — use neo4j-cypher-skill. Does NOT cover Graph Data Science algorithms — use neo4j-gds-skill or neo4j-aura-graph-analytics-skill. Does NOT cover neo4j-admin/cypher-shell — use neo4j-cli-tools-skill.

91

1.37x
Quality

88%

Does it follow best practices?

Impact

99%

1.37x

Average score across 3 eval scenarios

SecuritybySnyk

Advisory

Suggest reviewing before use

SKILL.md
Quality
Evals
Security

When to Use

  • Creating an Aura instance (CLI, REST API, Python, Terraform)
  • Pausing, resuming, resizing, or deleting an instance
  • Downloading initial credentials from creation response
  • Polling instance status: creatingrunning
  • Setting up CI/CD provisioning or teardown pipelines
  • Choosing instance tier (Free vs Professional vs Business Critical vs VDC)

When NOT to Use

  • Cypher queries against running DBneo4j-cypher-skill
  • GDS algorithms on Auraneo4j-gds-skill (Pro with plugin) or neo4j-aura-graph-analytics-skill (serverless)
  • neo4j-admin / cypher-shellneo4j-cli-tools-skill
  • Application driver setup → use a language driver skill (python, javascript, java, go, dotnet)

Instance Tier Decision Table

TierAPI type codeMemoryGDSReplicasUse when
AuraDB Freefree-db1 GBDev/demo; ≤200k nodes/400k rels
AuraDB Professionalprofessional-db2–64 GBplugin availableProduction workloads
AuraDB Business Criticalbusiness-critical4–384 GBplugin availableHA, multi-AZ, SLA
AuraDB VDCenterprise-dbcustomDedicated infra, compliance
AuraDS Professionalprofessional-ds2–64 GB✅ built-inData science / GDS
AuraDS Enterpriseenterprise-dscustomEnterprise GDS

AuraDB Free limits: 200k nodes, 400k rels; auto-pauses after 72 h inactivity; deleted if paused >30 days; no resize.


Auth Setup

CLI (aura-cli v1.7+)

Install (binary, not pip):

# macOS
curl -L https://github.com/neo4j/aura-cli/releases/latest/download/aura-cli-darwin-amd64.tar.gz | tar xz
sudo mv aura-cli /usr/local/bin/
aura-cli -v          # verify

Add credentials (from console.neo4j.io → Account Settings → API Credentials):

aura-cli credential add \
  --name "my-creds" \
  --client-id "$AURA_CLIENT_ID" \
  --client-secret "$AURA_CLIENT_SECRET"
aura-cli credential use --name "my-creds"

Verify:

aura-cli instance list --output table

REST API — Get Bearer Token

Token endpoint: POST https://api.neo4j.io/oauth/token Token expires: 3600 s (1 h). On 403 → refresh token.

TOKEN=$(curl -s --request POST 'https://api.neo4j.io/oauth/token' \
  --user "${AURA_CLIENT_ID}:${AURA_CLIENT_SECRET}" \
  --header 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'grant_type=client_credentials' \
  | jq -r '.access_token')
echo "Token: ${TOKEN:0:20}..."

Use in all subsequent calls: --header "Authorization: Bearer $TOKEN"


Step 1 — List Tenants (Projects)

CLI:

aura-cli tenants list --output table
# Copy TENANT_ID for create operations

REST:

curl -s https://api.neo4j.io/v1/tenants \
  -H "Authorization: Bearer $TOKEN" | jq '.data[] | {id, name}'

Step 2 — Create Instance

CRITICAL: Capture output immediately. Initial password shown ONCE — never retrievable again. If lost: delete and recreate. Store aura-creds.json before doing anything else.

CLI

aura-cli instance create \
  --name "my-instance" \
  --cloud-provider gcp \
  --region europe-west1 \
  --type professional-db \
  --tenant-id "$TENANT_ID" \
  --output json | tee aura-creds.json

# Extract for .env
INSTANCE_ID=$(jq -r '.id' aura-creds.json)
PASSWORD=$(jq -r '.password' aura-creds.json)

REST API (full create)

RESPONSE=$(curl -s -X POST https://api.neo4j.io/v1/instances \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name":           "my-instance",
    "cloud_provider": "gcp",
    "region":         "europe-west1",
    "type":           "professional-db",
    "tenant_id":      "'"$TENANT_ID"'",
    "memory":         "4GB",
    "version":        "5"
  }')
echo "$RESPONSE" | tee aura-creds.json
INSTANCE_ID=$(echo "$RESPONSE" | jq -r '.data.id')
PASSWORD=$(echo "$RESPONSE"   | jq -r '.data.password')

Instance create request body fields:

FieldRequiredValues
nameany string
cloud_providergcp aws azure
regionsee region table
typesee tier table
tenant_idfrom tenant list
memory1GB 2GB 4GB 8GB384GB
version5 (default)

Step 3 — Poll Until RUNNING (CRITICAL — All Ops Are Async)

ALL lifecycle operations (create, pause, resume, resize) are async. Do NOT attempt connection or next operation until status = running (or paused for pause op).

poll_status() {
  local INSTANCE_ID=$1 TARGET=$2 MAX_WAIT=${3:-600}
  local ELAPSED=0 STATUS
  echo "Polling for status=$TARGET (max ${MAX_WAIT}s)..."
  while [ $ELAPSED -lt $MAX_WAIT ]; do
    STATUS=$(aura-cli instance get --instance-id "$INSTANCE_ID" --output json \
             | jq -r '.status' 2>/dev/null)
    echo "  [${ELAPSED}s] status=$STATUS"
    [ "$STATUS" = "$TARGET" ] && echo "Ready." && return 0
    [ "$STATUS" = "destroying" ] && echo "ERROR: instance is being destroyed" && return 1
    sleep 10; ELAPSED=$((ELAPSED + 10))
  done
  echo "TIMEOUT after ${MAX_WAIT}s — last status: $STATUS" && return 1
}

poll_status "$INSTANCE_ID" "running" 600

REST equivalent:

while true; do
  STATUS=$(curl -s "https://api.neo4j.io/v1/instances/$INSTANCE_ID" \
    -H "Authorization: Bearer $TOKEN" | jq -r '.data.status')
  [ "$STATUS" = "running" ] && break
  sleep 10
done

Status lifecycle:

creating → running → pausing → paused → resuming → running
                  ↘ destroying → (gone)

Step 4 — Write .env and Verify

CONNECTION_URI="neo4j+s://${INSTANCE_ID}.databases.neo4j.io"

cat > .env <<EOF
NEO4J_URI=${CONNECTION_URI}
NEO4J_USERNAME=neo4j
NEO4J_PASSWORD=${PASSWORD}
NEO4J_DATABASE=neo4j
AURA_INSTANCE_ID=${INSTANCE_ID}
EOF

# Ensure .env never committed
grep -q '^\.env$' .gitignore 2>/dev/null || echo '.env' >> .gitignore

# Verify connectivity
cypher-shell -a "$CONNECTION_URI" -u neo4j -p "$PASSWORD" "RETURN 'connected' AS status"

Step 5 — Lifecycle Operations

All operations require instance in the correct state. Wrong-state ops return 4xx error.

Pause

Required state: running

aura-cli instance pause --instance-id "$INSTANCE_ID"
poll_status "$INSTANCE_ID" "paused" 600

Resume

Required state: paused

aura-cli instance resume --instance-id "$INSTANCE_ID"
poll_status "$INSTANCE_ID" "running" 900   # resume can take longer

Resize (Professional+ only — NOT Free)

Required state: running; instance remains available during resize.

# REST only — CLI resize not available in v1.7
curl -s -X PATCH "https://api.neo4j.io/v1/instances/$INSTANCE_ID" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"memory": "8GB"}'
poll_status "$INSTANCE_ID" "running" 600
# Cannot reduce below current usage level

Delete

IRREVERSIBLE. Export snapshots first if data needed.

aura-cli instance delete --instance-id "$INSTANCE_ID"
# No poll needed — immediate

REST:

curl -s -X DELETE "https://api.neo4j.io/v1/instances/$INSTANCE_ID" \
  -H "Authorization: Bearer $TOKEN"

Python CI/CD Provisioning Script

import os, time, requests

CLIENT_ID     = os.environ["AURA_CLIENT_ID"]
CLIENT_SECRET = os.environ["AURA_CLIENT_SECRET"]
BASE          = "https://api.neo4j.io/v1"

def get_token() -> str:
    r = requests.post(
        "https://api.neo4j.io/oauth/token",
        auth=(CLIENT_ID, CLIENT_SECRET),
        data={"grant_type": "client_credentials"},
    )
    r.raise_for_status()
    return r.json()["access_token"]

def auth_headers(token: str) -> dict:
    return {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}

def create_instance(token: str, tenant_id: str, **kwargs) -> dict:
    payload = {"tenant_id": tenant_id, "version": "5", **kwargs}
    r = requests.post(f"{BASE}/instances", headers=auth_headers(token), json=payload)
    r.raise_for_status()
    return r.json()["data"]   # contains id, password, connection_url

def poll_status(token: str, instance_id: str, target: str, timeout: int = 600) -> None:
    deadline = time.time() + timeout
    while time.time() < deadline:
        r = requests.get(f"{BASE}/instances/{instance_id}", headers=auth_headers(token))
        r.raise_for_status()
        status = r.json()["data"]["status"]
        print(f"  status={status}")
        if status == target:
            return
        if status == "destroying":
            raise RuntimeError("Instance destroyed unexpectedly")
        time.sleep(10)
    raise TimeoutError(f"Instance {instance_id} did not reach '{target}' in {timeout}s")

# --- usage ---
token    = get_token()
instance = create_instance(
    token,
    tenant_id = os.environ["AURA_TENANT_ID"],
    name           = "ci-test-instance",
    cloud_provider = "aws",
    region         = "us-east-1",
    type           = "professional-db",
    memory         = "2GB",
)
# SAVE CREDENTIALS IMMEDIATELY — password never retrievable again
print(f"ID:       {instance['id']}")
print(f"URI:      neo4j+s://{instance['id']}.databases.neo4j.io")
print(f"Password: {instance['password']}")   # log to secure vault NOW

poll_status(token, instance["id"], "running", timeout=600)
print("Instance ready.")

Region Codes

AWS

Region codeLocation
us-east-1N. Virginia
us-east-2Ohio
us-west-2Oregon
eu-west-1Ireland
eu-west-3Paris
eu-central-1Frankfurt
ap-southeast-1Singapore
ap-southeast-2Sydney
ap-south-1Mumbai
sa-east-1São Paulo

GCP

Region codeLocation
europe-west1Belgium
europe-west3Frankfurt
europe-west4Netherlands
us-central1Iowa
us-east1S. Carolina
us-east4N. Virginia
asia-east1Taiwan
asia-northeast1Tokyo
asia-southeast1Singapore
australia-southeast1Sydney

Azure

Region codeLocation
eastusE. US
eastus2E. US 2
westeuropeNetherlands
northeuropeIreland
uksouthLondon
southeastasiaSingapore
brazilsouthBrazil
koreacentralKorea

Enterprise tiers (Business Critical, VDC) add 20+ additional regions per provider. Check console for full list. Free tier: GCP only; limited subset of regions.


Terraform Provider

terraform {
  required_providers {
    aura = {
      source  = "neo4j/neo4j-aura"
    }
  }
}

provider "aura" {
  client_id     = var.aura_client_id       # or AURA_CLIENT_ID env var
  client_secret = var.aura_client_secret   # or AURA_CLIENT_SECRET env var
}

resource "aura_instance" "db" {
  name           = "prod-db"
  type           = "professional-db"
  cloud_provider = "gcp"
  region         = "europe-west1"
  memory         = "4GB"
  tenant_id      = var.aura_tenant_id
}

output "neo4j_uri" {
  value     = "neo4j+s://${aura_instance.db.id}.databases.neo4j.io"
  sensitive = false
}
output "neo4j_password" {
  value     = aura_instance.db.password
  sensitive = true
}

After terraform apply: poll status before marking infra ready — Terraform resource creation returns when API call completes, not when DB is running.


Common Errors

ErrorCauseFix
403 Forbidden after workingToken expired (1 h TTL)Re-run get_token()
409 Conflict on createName already exists in tenantChange name or delete existing
422 on pauseInstance not runningCheck status; wait for ongoing op to finish
422 on resumeInstance not pausedCheck status
422 on resizeBelow current usageReduce data first; can't shrink below usage
Region not foundTier doesn't support that regionUse Free tier on GCP only; Pro/BC on all 3 clouds
Credentials lost after createPassword only returned at create timeDelete + recreate — no reset exists
429 Too Many RequestsRate limit hit (25 req/min Free, 125 req/min Pro+)Add time.sleep(2) between polling calls
instance list returns emptyWrong credential activeaura-cli credential use --name <name>

API Rate Limits

TierRequests/minute
Free / Pro Trial (no billing)25
Pro with billing, BC, VDC125

Poll interval: ≥10 s to stay within limits on Free; 5 s safe on Pro+. On Retry-After header in 5xx response: wait that many seconds before retry.


Security Rules

  • Write initial credentials to .env; verify .env in .gitignore before proceeding
  • Never print PASSWORD in CI logs — write to secrets vault (AWS Secrets Manager, GitHub secret, Vault)
  • Use from_env() / os.environ — never hardcode credentials
  • If .env absent: python-dotenv load_dotenv() auto-loads; do NOT prompt user unless loading fails

WebFetch — Current Docs

NeedURL
REST API spec (OpenAPI)https://neo4j.com/docs/aura/platform/api/specification/
CLI referencehttps://neo4j.com/docs/aura/aura-cli/
Region listhttps://neo4j.com/docs/aura/managing-instances/regions/
Auth detailshttps://neo4j.com/docs/aura/api/authentication/
Instance actionshttps://neo4j.com/docs/aura/managing-instances/instance-actions/

Checklist

  • .env created with URI/user/password; .env in .gitignore
  • Initial credentials saved to secure storage immediately after create
  • poll_status called after create — do NOT connect before status = running
  • poll_status called after pause/resume
  • Correct tier selected (Free for dev, Pro+ for production, BC for HA)
  • Region confirmed available for chosen tier and cloud provider
  • Tenant ID provided for all create/list operations (required in multi-tenant orgs)
  • Token refreshed if > 1 h old (or 403 received)
  • Delete confirmed by user — data loss is permanent, no recovery
Repository
neo4j-contrib/neo4j-skills
Last updated
Created

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.