Skill v1.0.1
currentAutomated scan100/100+6 new
name: neo4j-aura-provisioning-skill description: 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. version: 1.0.1 allowed-tools: Bash WebFetch
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:
creating→running - 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 DB →
neo4j-cypher-skill - GDS algorithms on Aura →
neo4j-gds-skill(Pro with plugin) orneo4j-aura-graph-analytics-skill(serverless) - neo4j-admin / cypher-shell →
neo4j-cli-tools-skill - Application driver setup → use a language driver skill (python, javascript, java, go, dotnet)
Instance Tier Decision Table
| Tier | API type code | Memory | GDS | Replicas | Use when | |
|---|---|---|---|---|---|---|
| AuraDB Free | free-db | 1 GB | ❌ | ❌ | Dev/demo; ≤200k nodes/400k rels | |
| AuraDB Professional | professional-db | 2–64 GB | plugin available | ❌ | Production workloads | |
| AuraDB Business Critical | business-critical | 4–384 GB | plugin available | ✅ | HA, multi-AZ, SLA | |
| AuraDB VDC | enterprise-db | custom | ✅ | ✅ | Dedicated infra, compliance | |
| AuraDS Professional | professional-ds | 2–64 GB | ✅ built-in | ❌ | Data science / GDS | |
| AuraDS Enterprise | enterprise-ds | custom | ✅ | ✅ | Enterprise 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):
# macOScurl -L https://github.com/neo4j/aura-cli/releases/latest/download/aura-cli-darwin-amd64.tar.gz | tar xzsudo 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 .envINSTANCE_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.jsonINSTANCE_ID=$(echo "$RESPONSE" | jq -r '.data.id')PASSWORD=$(echo "$RESPONSE" | jq -r '.data.password')
Instance create request body fields:
| Field | Required | Values | |
|---|---|---|---|
name | ✅ | any string | |
cloud_provider | ✅ | gcp aws azure | |
region | ✅ | see region table | |
type | ✅ | see tier table | |
tenant_id | ✅ | from tenant list | |
memory | ✗ | 1GB 2GB 4GB 8GB … 384GB | |
version | ✗ | 5 (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 STATUSecho "Polling for status=$TARGET (max ${MAX_WAIT}s)..."while [ $ELAPSED -lt $MAX_WAIT ]; doSTATUS=$(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 1sleep 10; ELAPSED=$((ELAPSED + 10))doneecho "TIMEOUT after ${MAX_WAIT}s — last status: $STATUS" && return 1}poll_status "$INSTANCE_ID" "running" 600
REST equivalent:
while true; doSTATUS=$(curl -s "https://api.neo4j.io/v1/instances/$INSTANCE_ID" \-H "Authorization: Bearer $TOKEN" | jq -r '.data.status')[ "$STATUS" = "running" ] && breaksleep 10done
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 <<EOFNEO4J_URI=${CONNECTION_URI}NEO4J_USERNAME=neo4jNEO4J_PASSWORD=${PASSWORD}NEO4J_DATABASE=neo4jAURA_INSTANCE_ID=${INSTANCE_ID}EOF# Ensure .env never committedgrep -q '^\.env$' .gitignore 2>/dev/null || echo '.env' >> .gitignore# Verify connectivitycypher-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.7curl -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, requestsCLIENT_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_urldef poll_status(token: str, instance_id: str, target: str, timeout: int = 600) -> None:deadline = time.time() + timeoutwhile 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:returnif 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 againprint(f"ID: {instance['id']}")print(f"URI: neo4j+s://{instance['id']}.databases.neo4j.io")print(f"Password: {instance['password']}") # log to secure vault NOWpoll_status(token, instance["id"], "running", timeout=600)print("Instance ready.")
Region Codes
AWS
| Region code | Location | |
|---|---|---|
us-east-1 | N. Virginia | |
us-east-2 | Ohio | |
us-west-2 | Oregon | |
eu-west-1 | Ireland | |
eu-west-3 | Paris | |
eu-central-1 | Frankfurt | |
ap-southeast-1 | Singapore | |
ap-southeast-2 | Sydney | |
ap-south-1 | Mumbai | |
sa-east-1 | São Paulo |
GCP
| Region code | Location | |
|---|---|---|
europe-west1 | Belgium | |
europe-west3 | Frankfurt | |
europe-west4 | Netherlands | |
us-central1 | Iowa | |
us-east1 | S. Carolina | |
us-east4 | N. Virginia | |
asia-east1 | Taiwan | |
asia-northeast1 | Tokyo | |
asia-southeast1 | Singapore | |
australia-southeast1 | Sydney |
Azure
| Region code | Location | |
|---|---|---|
eastus | E. US | |
eastus2 | E. US 2 | |
westeurope | Netherlands | |
northeurope | Ireland | |
uksouth | London | |
southeastasia | Singapore | |
brazilsouth | Brazil | |
koreacentral | Korea |
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 varclient_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.passwordsensitive = 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
| Error | Cause | Fix | |
|---|---|---|---|
403 Forbidden after working | Token expired (1 h TTL) | Re-run get_token() | |
409 Conflict on create | Name already exists in tenant | Change name or delete existing | |
422 on pause | Instance not running | Check status; wait for ongoing op to finish | |
422 on resume | Instance not paused | Check status | |
422 on resize | Below current usage | Reduce data first; can't shrink below usage | |
| Region not found | Tier doesn't support that region | Use Free tier on GCP only; Pro/BC on all 3 clouds | |
| Credentials lost after create | Password only returned at create time | Delete + recreate — no reset exists | |
429 Too Many Requests | Rate limit hit (25 req/min Free, 125 req/min Pro+) | Add time.sleep(2) between polling calls | |
instance list returns empty | Wrong credential active | aura-cli credential use --name <name> |
API Rate Limits
| Tier | Requests/minute | |
|---|---|---|
| Free / Pro Trial (no billing) | 25 | |
| Pro with billing, BC, VDC | 125 |
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.envin.gitignorebefore proceeding - Never print
PASSWORDin CI logs — write to secrets vault (AWS Secrets Manager, GitHub secret, Vault) - Use
from_env()/os.environ— never hardcode credentials - If
.envabsent:python-dotenvload_dotenv()auto-loads; do NOT prompt user unless loading fails
WebFetch — Current Docs
| Need | URL | |
|---|---|---|
| REST API spec (OpenAPI) | https://neo4j.com/docs/aura/platform/api/specification/ | |
| CLI reference | https://neo4j.com/docs/aura/aura-cli/ | |
| Region list | https://neo4j.com/docs/aura/managing-instances/regions/ | |
| Auth details | https://neo4j.com/docs/aura/api/authentication/ | |
| Instance actions | https://neo4j.com/docs/aura/managing-instances/instance-actions/ |
Checklist
- [ ]
.envcreated with URI/user/password;.envin.gitignore - [ ] Initial credentials saved to secure storage immediately after create
- [ ]
poll_statuscalled after create — do NOT connect before status =running - [ ]
poll_statuscalled 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