A Go package that enables the use of Neo4j Aura API in a friendly way e.g client.Instances.List(ctx) to return a list of instances in Aura.
Client Id and Secret are required and these can be obtained from the Neo4j Aura Console.
- Installation
- Quick Start
- Configuration
- Context and Timeouts
- Tenant Operations
- Instance Operations
- Snapshot Operations
- CMEK Operations
- GDS Session Operations
- Prometheus Metrics Operations
- v2beta1 API
- v2beta1 Instance Operations
- v2beta1 Database Operations (List, Get, Create, Delete)
- v2beta1 Database Backup Operations (List, Get, Create)
- v2beta1 Organization User Operations (List, Get, UpdateRole, Remove)
- v2beta1 Organization Invite Operations (List, Create, Delete)
- v2beta1 Project User Operations (List, Add, UpdateRole, Remove)
- Error Handling
- Best Practices
- CI & Releases
go get github.com/neo4j-contrib/aura-go-sdkpackage main
import (
"context"
"log"
aura "github.com/neo4j-contrib/aura-go-sdk"
)
func main() {
client, err := aura.NewClient(
aura.WithCredentials("your-client-id", "your-client-secret"),
)
if err != nil {
log.Fatalf("Failed to create client: %v", err)
}
defer client.Close()
ctx := context.Background()
instances, err := client.Instances.List(ctx)
if err != nil {
log.Fatalf("Failed to list instances: %v", err)
}
for _, instance := range instances.Data {
log.Printf("Instance: %s (ID: %s)\n", instance.Name, instance.ID)
}
}client, err := aura.NewClient(
aura.WithCredentials("client-id", "client-secret"),
)client, err := aura.NewClient(
aura.WithCredentials("client-id", "client-secret"),
aura.WithTimeout(60 * time.Second),
aura.WithMaxRetry(5),
aura.WithMaxResponseSize(20 * 1024 * 1024), // 20MB
)Use WithMaxResponseSize to override the default 10MB response body limit. Responses larger than this limit are rejected with an error, protecting against unexpectedly large payloads:
client, err := aura.NewClient(
aura.WithCredentials("client-id", "client-secret"),
aura.WithMaxResponseSize(20 * 1024 * 1024), // 20MB
)import "log/slog"
opts := &slog.HandlerOptions{Level: slog.LevelDebug}
handler := slog.NewTextHandler(os.Stderr, opts)
logger := slog.New(handler)
client, err := aura.NewClient(
aura.WithCredentials("client-id", "client-secret"),
aura.WithLogger(logger),
)Use WithBaseURL to point the client at a staging or sandbox environment:
client, err := aura.NewClient(
aura.WithCredentials("client-id", "client-secret"),
aura.WithBaseURL("https://api.staging.neo4j.io"),
)Use WithHTTPClient to inject a custom *http.Client. This is useful for
configuring mTLS, HTTP proxies, or controlling low-level transport settings:
import "net/http"
transport := &http.Transport{
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
}
httpClient := &http.Client{Transport: transport}
client, err := aura.NewClient(
aura.WithCredentials("client-id", "client-secret"),
aura.WithHTTPClient(httpClient),
)Use WithUserAgent to override the default User-Agent header. This is
useful when your application needs to be identifiable in API server logs:
client, err := aura.NewClient(
aura.WithCredentials("client-id", "client-secret"),
aura.WithUserAgent("my-app/2.1.0"),
)Use WithDefaultHeaders to attach custom headers to every API request.
Authorization, Content-Type, and User-Agent are silently ignored to
prevent accidental overrides of security-critical headers:
client, err := aura.NewClient(
aura.WithCredentials("client-id", "client-secret"),
aura.WithDefaultHeaders(map[string]string{
"X-Request-Source": "my-service",
"X-Correlation-ID": "abc-123",
}),
)Every service method accepts a context.Context as its first argument. This is the standard Go pattern and gives you full control over cancellation and deadlines on a per-call basis.
The client is configured with a default timeout (120 seconds, overridable with WithTimeout). This timeout is applied as a ceiling on each call — if the context you pass already has a shorter deadline, that shorter deadline wins.
ctx := context.Background()
instances, err := client.Instances.List(ctx)// This specific call must complete within 10 seconds
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
instance, err := client.Instances.Get(ctx, "instance-id")ctx, cancel := context.WithCancel(context.Background())
// Cancel all in-flight calls (e.g. on OS signal or user action)
go func() {
<-shutdownSignal
cancel()
}()
instances, err := client.Instances.List(ctx)
if err != nil {
if ctx.Err() == context.Canceled {
log.Println("Request was cancelled")
}
}Because context flows through every call, you can attach trace spans from any OpenTelemetry-compatible library:
ctx, span := tracer.Start(r.Context(), "list-instances")
defer span.End()
instances, err := client.Instances.List(ctx)ctx := context.Background()
tenants, err := client.Tenants.List(ctx)
if err != nil {
log.Fatalf("Error: %v", err)
}
for _, tenant := range tenants.Data {
fmt.Printf("Tenant: %s (ID: %s)\n", tenant.Name, tenant.ID)
}ctx := context.Background()
tenant, err := client.Tenants.Get(ctx, "your-tenant-id")
if err != nil {
log.Fatalf("Error: %v", err)
}
fmt.Printf("Tenant: %s\n", tenant.Data.Name)
fmt.Printf("Available instance configurations:\n")
for _, config := range tenant.Data.InstanceConfigurations {
fmt.Printf(" - %s in %s: %s memory, Type: %s\n",
config.CloudProvider,
config.RegionName,
config.Memory,
config.Type,
)
}ctx := context.Background()
instances, err := client.Instances.List(ctx)
if err != nil {
log.Fatalf("Error: %v", err)
}
fmt.Printf("Found %d instances:\n", len(instances.Data))
for _, instance := range instances.Data {
fmt.Printf(" - %s (ID: %s) on %s\n",
instance.Name,
instance.ID,
instance.CloudProvider,
)
}ctx := context.Background()
instance, err := client.Instances.Get(ctx, "your-instance-id")
if err != nil {
log.Fatalf("Error: %v", err)
}
fmt.Printf("Instance: %s\n", instance.Data.Name)
fmt.Printf("Status: %s\n", instance.Data.Status)
fmt.Printf("Connection URL: %s\n", instance.Data.ConnectionURL)
fmt.Printf("Memory: %s\n", instance.Data.Memory)
fmt.Printf("Type: %s\n", instance.Data.Type)
fmt.Printf("Region: %s\n", instance.Data.Region)
if instance.Data.CreatedAt != nil {
fmt.Printf("Created At: %s\n", instance.Data.CreatedAt.Format(time.RFC3339))
}ctx := context.Background()
config := &aura.CreateInstanceConfigData{
Name: "my-neo4j-db",
TenantID: "your-tenant-id",
CloudProvider: "gcp",
Region: "europe-west1",
Type: "enterprise-db",
Version: "5",
Memory: "8GB",
}
instance, err := client.Instances.Create(ctx, config)
if err != nil {
log.Fatalf("Error creating instance: %v", err)
}
fmt.Printf("Instance created!\n")
fmt.Printf(" ID: %s\n", instance.Data.ID)
fmt.Printf(" Connection URL: %s\n", instance.Data.ConnectionURL)
fmt.Printf(" Username: %s\n", instance.Data.Username)
fmt.Printf(" Password: %s\n", instance.Data.Password)
// ⚠️ IMPORTANT: Save these credentials securely!
// The password is only shown once during creation.The optional fields VectorOptimized, GraphAnalyticsPlugin, and CustomerManagedKeyID can also be set at creation time:
config := &aura.CreateInstanceConfigData{
Name: "my-neo4j-db",
TenantID: "your-tenant-id",
CloudProvider: "gcp",
Region: "europe-west1",
Type: "enterprise-db",
Version: "5",
Memory: "8GB",
VectorOptimized: true,
GraphAnalyticsPlugin: true,
CustomerManagedKeyID: "your-cmk-id", // enterprise tiers only
}Creates a new instance pre-loaded with the data from the latest snapshot of an existing instance.
ctx := context.Background()
config := &aura.CreateInstanceConfigData{
Name: "my-clone",
TenantID: "your-tenant-id",
CloudProvider: "gcp",
Region: "europe-west1",
Type: "enterprise-db",
Version: "5",
Memory: "8GB",
}
instance, err := client.Instances.CreateFromInstance(ctx, "source-instance-id", config)
if err != nil {
log.Fatalf("Error: %v", err)
}
fmt.Printf("Clone created: %s\n", instance.Data.ID)Creates a new instance from a specific exportable snapshot. Both the source instance ID and the snapshot ID are required.
ctx := context.Background()
config := &aura.CreateInstanceConfigData{
Name: "my-snapshot-clone",
TenantID: "your-tenant-id",
CloudProvider: "gcp",
Region: "europe-west1",
Type: "enterprise-db",
Version: "5",
Memory: "8GB",
}
instance, err := client.Instances.CreateFromSnapshot(ctx, "source-instance-id", "snapshot-id", config)
if err != nil {
log.Fatalf("Error: %v", err)
}
fmt.Printf("Clone created: %s\n", instance.Data.ID)ctx := context.Background()
updateData := &aura.UpdateInstanceData{
Name: "my-renamed-instance",
Memory: "16GB",
CDCEnrichmentMode: "FULL", // "" | "DIFF" | "FULL"
SecondariesCount: 2,
}
instance, err := client.Instances.Update(ctx, "instance-id", updateData)
if err != nil {
log.Fatalf("Error: %v", err)
}
fmt.Printf("Instance updated: %s with %s memory\n",
instance.Data.Name,
instance.Data.Memory,
)ctx := context.Background()
instance, err := client.Instances.Pause(ctx, "instance-id")
if err != nil {
log.Fatalf("Error: %v", err)
}
fmt.Printf("Instance paused. Status: %s\n", instance.Data.Status)ctx := context.Background()
instance, err := client.Instances.Resume(ctx, "instance-id")
if err != nil {
log.Fatalf("Error: %v", err)
}
fmt.Printf("Instance resumed. Status: %s\n", instance.Data.Status)ctx := context.Background()
// ⚠️ WARNING: This is irreversible!
instance, err := client.Instances.Delete(ctx, "instance-to-delete")
if err != nil {
log.Fatalf("Error: %v", err)
}
fmt.Printf("Instance %s deleted\n", instance.Data.ID)ctx := context.Background()
result, err := client.Instances.OverwriteFromInstance(ctx, "target-instance-id", "source-instance-id")
if err != nil {
log.Fatalf("Error: %v", err)
}
fmt.Printf("Overwrite initiated: %s\n", result.Data)
// Note: This is asynchronous. Monitor instance status.ctx := context.Background()
result, err := client.Instances.OverwriteFromSnapshot(ctx, "target-instance-id", "snapshot-id")
if err != nil {
log.Fatalf("Error: %v", err)
}
fmt.Printf("Overwrite from snapshot initiated\n")Snapshots.List accepts an optional filter to return snapshots for a particular day. If this is not given , nil is used instead, then snapshots for the current day are returned.
The date is of type SnapshotDate that holds the Year, Month and Day. For example, to see snapshots for 23rd March 2026
filter := aura.SnapshotDate{Year: 2026, Month: time.March, Day: 23})
Then call List
snapshots, err := client.Snapshots.List(ctx, "your-instance-id", &filter )
ctx := context.Background()
// Empty date string returns today's snapshots
snapshots, err := client.Snapshots.List(ctx, "your-instance-id", nil)
if err != nil {
log.Fatalf("Error: %v", err)
}
fmt.Printf("Found %d snapshots:\n", len(snapshots.Data))
for _, snapshot := range snapshots.Data {
fmt.Printf(" - ID: %s, Profile: %s, Status: %s\n",
snapshot.SnapshotID,
snapshot.Profile,
snapshot.Status,
)
}ctx := context.Background()
snapshots, err := client.Snapshots.List(ctx, "your-instance-id", &aura.SnapshotDate{Year: 2026, Month: time.March, Day: 23})
if err != nil {
log.Fatalf("Error: %v", err)
}
for _, snapshot := range snapshots.Data {
fmt.Printf(" - %s at %s\n", snapshot.SnapshotID, snapshot.Timestamp.Format(time.RFC3339))
}ctx := context.Background()
snapshot, err := client.Snapshots.Get(ctx, "your-instance-id", "your-snapshot-id")
if err != nil {
log.Fatalf("Error: %v", err)
}
fmt.Printf("Instance ID: %s\nSnapshot ID: %s\nStatus: %s\nTimestamp: %s\n",
snapshot.Data.InstanceID,
snapshot.Data.SnapshotID,
snapshot.Data.Status,
snapshot.Data.Timestamp.Format(time.RFC3339),
)ctx := context.Background()
snapshot, err := client.Snapshots.Create(ctx, "your-instance-id")
if err != nil {
log.Fatalf("Error: %v", err)
}
fmt.Printf("Snapshot creation initiated. Snapshot ID: %s\n", snapshot.Data.SnapshotID)
// Note: Snapshot creation is asynchronous. Poll List() to check completion status.ctx := context.Background()
result, err := client.Snapshots.Restore(ctx, "your-instance-id", "your-snapshot-id")
if err != nil {
log.Fatalf("Error: %v", err)
}
fmt.Printf("Instance ID: %s\nStatus: %s\n", result.Data.ID, result.Data.Status)ctx := context.Background()
// Pass an empty string to list all CMEKs regardless of tenant
cmeks, err := client.CMEK.List(ctx, "")
if err != nil {
log.Fatalf("Error: %v", err)
}
for _, cmek := range cmeks.Data {
fmt.Printf(" - %s (ID: %s) in tenant %s\n", cmek.Name, cmek.ID, cmek.TenantID)
}ctx := context.Background()
cmeks, err := client.CMEK.List(ctx, "your-tenant-id")
if err != nil {
log.Fatalf("Error: %v", err)
}
for _, cmek := range cmeks.Data {
fmt.Printf(" - %s\n", cmek.Name)
}ctx := context.Background()
sessions, err := client.GraphAnalytics.List(ctx)
if err != nil {
log.Fatalf("Error: %v", err)
}
for _, session := range sessions.Data {
fmt.Printf(" - %s (ID: %s)\n", session.Name, session.ID)
fmt.Printf(" Memory: %s, Status: %s\n", session.Memory, session.Status)
fmt.Printf(" Instance: %s, Expires: %s\n", session.InstanceID, session.ExpiresAt.Format(time.RFC3339))
}Each Aura instance exposes Prometheus metrics for monitoring.
ctx := context.Background()
instance, err := client.Instances.Get(ctx, "your-instance-id")
if err != nil {
log.Fatalf("Error: %v", err)
}
prometheusURL := instance.Data.MetricsURLctx := context.Background()
health, err := client.Prometheus.GetInstanceHealth(ctx, "your-instance-id", prometheusURL)
if err != nil {
log.Fatalf("Error: %v", err)
}
// OverallStatus is one of: "healthy", "warning", or "critical".
// "warning" — one or more metrics are elevated; monitor closely.
// "critical" — one or more metrics have breached a severe threshold
// and immediate action is recommended.
fmt.Printf("Health Status: %s\n", health.OverallStatus)
fmt.Printf("CPU Usage: %.2f%%\n", health.Resources.CPUUsagePercent)
fmt.Printf("Memory Usage: %.2f%%\n", health.Resources.MemoryUsagePercent)
fmt.Printf("Total Queries: %.0f\n", health.Query.QueryExecutionTotal)
if health.Connections.MaxConnections > 0 {
fmt.Printf("Active Connections: %d/%d (%.1f%%)\n",
health.Connections.ActiveConnections,
health.Connections.MaxConnections,
health.Connections.UsagePercent,
)
} else {
fmt.Printf("Active Connections: %d (max unknown for this plan)\n",
health.Connections.ActiveConnections,
)
}
if len(health.Issues) > 0 {
fmt.Println("\nIssues detected:")
for _, issue := range health.Issues {
fmt.Printf(" - %s\n", issue)
}
}
if len(health.Recommendations) > 0 {
fmt.Println("\nRecommendations:")
for _, rec := range health.Recommendations {
fmt.Printf(" - %s\n", rec)
}
}For more detailed information on Prometheus operations, see the Prometheus documentation.
The v2beta1 sub-package is a separate Go module that targets the Neo4j Aura v2beta1 API. It exposes organization and project management operations not available in the stable v1 API.
The v2beta1 package is part of the same module as the stable v1 client:
go get github.com/neo4j-contrib/aura-go-sdkimport v2beta1 "github.com/neo4j-contrib/aura-go-sdk/v2beta1"client, err := v2beta1.NewClient(
v2beta1.WithCredentials("your-client-id", "your-client-secret"),
)
if err != nil {
log.Fatalf("Failed to create client: %v", err)
}
defer client.Close()Every service call requires the caller to pass orgID and projectID explicitly. This ensures that the organization and project being acted on are always visible at the call site, preventing accidental operations in the wrong scope.
ctx := context.Background()
orgs, err := client.Organizations.List(ctx)
if err != nil {
log.Fatalf("Error: %v", err)
}
for _, org := range orgs.Data {
fmt.Printf("Organization: %s (ID: %s)\n", org.Name, org.ID)
}ctx := context.Background()
org, err := client.Organizations.Get(ctx, "your-org-uuid")
if err != nil {
log.Fatalf("Error: %v", err)
}
fmt.Printf("Organization: %s\n", org.Data.Name)ctx := context.Background()
projects, err := client.Projects.List(ctx, "your-org-uuid")
if err != nil {
log.Fatalf("Error: %v", err)
}
for _, project := range projects.Data {
fmt.Printf("Project: %s (ID: %s)\n", project.Name, project.ID)
}ctx := context.Background()
instances, err := client.Instances.List(ctx, "your-org-uuid", "your-project-uuid")
if err != nil {
log.Fatalf("Error: %v", err)
}
for _, instance := range instances.Data {
fmt.Printf("Instance: %s (ID: %s) status: %s\n", instance.Name, instance.ID, instance.Status)
}ctx := context.Background()
instance, err := client.Instances.Get(ctx, "your-org-uuid", "your-project-uuid", "a1b2c3d4-e5f6-7890-abcd-ef1234567890")
if err != nil {
log.Fatalf("Error: %v", err)
}
fmt.Printf("Instance: %s status: %s connection: %s\n",
instance.Data.Name, instance.Data.Status, instance.Data.ConnectionURL)ctx := context.Background()
req := &v2beta1.CreateInstanceRequest{
Name: "my-instance",
CloudProvider: "gcp",
Region: "europe-west1",
Type: "enterprise-db",
Memory: "8GB",
Version: "5",
}
instance, err := client.Instances.Create(ctx, "your-org-uuid", "your-project-uuid", req)
if err != nil {
log.Fatalf("Error: %v", err)
}
fmt.Printf("Created: %s (ID: %s)\n", instance.Data.Name, instance.Data.ID)
// Save instance.Data.Password securely — it is only returned once.ctx := context.Background()
req := &v2beta1.UpdateInstanceRequest{
Name: "renamed-instance",
Memory: "16GB",
}
instance, err := client.Instances.Update(ctx, "your-org-uuid", "your-project-uuid", "a1b2c3d4-e5f6-7890-abcd-ef1234567890", req)
if err != nil {
log.Fatalf("Error: %v", err)
}
fmt.Printf("Updated: %s memory: %s\n", instance.Data.Name, instance.Data.Memory)ctx := context.Background()
result, err := client.Instances.Delete(ctx, "your-org-uuid", "your-project-uuid", "a1b2c3d4-e5f6-7890-abcd-ef1234567890")
if err != nil {
log.Fatalf("Error: %v", err)
}
fmt.Printf("Deleted instance: %s\n", result.Data.ID)client.Databases manages the databases within an Aura instance. Instance IDs and database IDs are both 8-character hex strings (e.g. "abcdef01").
ctx := context.Background()
databases, err := client.Databases.List(ctx, "your-org-uuid", "your-project-uuid", "abcdef01")
if err != nil {
log.Fatalf("Error: %v", err)
}
for _, db := range databases.Data {
fmt.Printf("Database ID: %s\n", db.ID)
}ctx := context.Background()
db, err := client.Databases.Get(ctx, "your-org-uuid", "your-project-uuid", "abcdef01", "12345678")
if err != nil {
log.Fatalf("Error: %v", err)
}
fmt.Printf("Database ID: %s\n", db.Data.ID)ctx := context.Background()
db, err := client.Databases.Create(ctx, "your-org-uuid", "your-project-uuid", "abcdef01")
if err != nil {
log.Fatalf("Error: %v", err)
}
fmt.Printf("Database created: %s\n", db.Data.ID)ctx := context.Background()
// ⚠️ WARNING: This is irreversible!
result, err := client.Databases.Delete(ctx, "your-org-uuid", "your-project-uuid", "abcdef01", "12345678")
if err != nil {
log.Fatalf("Error: %v", err)
}
fmt.Printf("Deleted database: %s\n", result.Data.ID)Note: This may get truncated to just client.Backups. Not entirely sold on client.DatabaseBackups which is a bit jarring. And we've already got client.snaphots. Just store away that the naming here could alter in the future
client.DatabaseBackups manages backups for a specific database on an instance.
ctx := context.Background()
backups, err := client.DatabaseBackups.List(ctx, "your-org-uuid", "your-project-uuid", "abcdef01", "12345678")
if err != nil {
log.Fatalf("Error: %v", err)
}
for _, backup := range backups.Data {
fmt.Printf("Backup: %s status: %s exportable: %v\n", backup.ID, backup.Status, backup.Exportable)
}ctx := context.Background()
backup, err := client.DatabaseBackups.Create(ctx, "your-org-uuid", "your-project-uuid", "abcdef01", "12345678")
if err != nil {
log.Fatalf("Error: %v", err)
}
fmt.Printf("Backup created: %s status: %s\n", backup.Data.ID, backup.Data.Status)ctx := context.Background()
backup, err := client.DatabaseBackups.Get(ctx, "your-org-uuid", "your-project-uuid", "abcdef01", "12345678", "backup-id")
if err != nil {
log.Fatalf("Error: %v", err)
}
fmt.Printf("Backup: %s status: %s exportable: %v\n",
backup.Data.ID, backup.Data.Status, backup.Data.Exportable)client.OrganizationUsers manages users within an Aura organization.
ctx := context.Background()
users, err := client.OrganizationUsers.List(ctx, "your-org-uuid")
if err != nil {
log.Fatalf("Error: %v", err)
}
for _, user := range users.Data {
fmt.Printf("User: %s (ID: %s) roles: %v\n", user.Email, user.UserID, user.OrganizationRoles)
}ctx := context.Background()
user, err := client.OrganizationUsers.Get(ctx, "your-org-uuid", "a1b2c3d4-e5f6-7890-abcd-ef1234567890")
if err != nil {
log.Fatalf("Error: %v", err)
}
fmt.Printf("User: %s roles: %v\n", user.Data.Email, user.Data.OrganizationRoles)
for _, project := range user.Data.Projects {
fmt.Printf(" Project: %s (ID: %s) roles: %v\n", project.Name, project.ID, project.ProjectRoles)
}ctx := context.Background()
req := &v2beta1.UpdateOrganizationUserRequest{
OrganizationRoles: []string{"organization-member"},
}
user, err := client.OrganizationUsers.UpdateRole(ctx, "your-org-uuid", "a1b2c3d4-e5f6-7890-abcd-ef1234567890", req)
if err != nil {
log.Fatalf("Error: %v", err)
}
fmt.Printf("Updated roles: %v\n", user.Data.OrganizationRoles)ctx := context.Background()
err := client.OrganizationUsers.Remove(ctx, "your-org-uuid", "a1b2c3d4-e5f6-7890-abcd-ef1234567890")
if err != nil {
log.Fatalf("Error: %v", err)
}
fmt.Println("User removed from organization")client.OrganizationInvites manages pending invites for an Aura organization.
ctx := context.Background()
invites, err := client.OrganizationInvites.List(ctx, "your-org-uuid")
if err != nil {
log.Fatalf("Error: %v", err)
}
for _, invite := range invites.Data {
fmt.Printf("Invite: %s (ID: %s) status: %s\n", invite.Email, invite.ID, invite.Status)
}ctx := context.Background()
req := &v2beta1.CreateOrganizationInviteRequest{
Email: "new-member@example.com",
Roles: []string{"organization-member"},
}
invite, err := client.OrganizationInvites.Create(ctx, "your-org-uuid", req)
if err != nil {
log.Fatalf("Error: %v", err)
}
fmt.Printf("Invite sent to %s (ID: %s)\n", invite.Data.Email, invite.Data.ID)ctx := context.Background()
req := &v2beta1.CreateOrganizationInviteRequest{
Email: "new-member@example.com",
Roles: []string{"organization-member"},
ProjectInvites: []v2beta1.ProjectInviteEntry{
{
ProjectID: "your-project-uuid",
ProjectRoles: []string{"project-member"},
},
},
}
invite, err := client.OrganizationInvites.Create(ctx, "your-org-uuid", req)
if err != nil {
log.Fatalf("Error: %v", err)
}
fmt.Printf("Invite sent to %s (ID: %s)\n", invite.Data.Email, invite.Data.ID)ctx := context.Background()
err := client.OrganizationInvites.Delete(ctx, "your-org-uuid", "a1b2c3d4-e5f6-7890-abcd-ef1234567890")
if err != nil {
log.Fatalf("Error: %v", err)
}
fmt.Println("Invite deleted")client.ProjectUsers manages the users within an Aura project.
ctx := context.Background()
users, err := client.ProjectUsers.List(ctx, "your-org-uuid", "your-project-uuid")
if err != nil {
log.Fatalf("Error: %v", err)
}
for _, user := range users.Data {
fmt.Printf("User: %s (%s) roles: %v\n", user.Email, user.UserID, user.ProjectRoles)
}ctx := context.Background()
err := client.ProjectUsers.Add(ctx, "your-org-uuid", "your-project-uuid", &v2beta1.AddProjectUserRequest{
UserID: "user-uuid",
ProjectRoles: []string{"viewer"},
})
if err != nil {
log.Fatalf("Error: %v", err)
}ctx := context.Background()
user, err := client.ProjectUsers.UpdateRole(ctx, "your-org-uuid", "your-project-uuid", "user-uuid", &v2beta1.UpdateProjectUserRequest{
ProjectRoles: []string{"editor"},
})
if err != nil {
log.Fatalf("Error: %v", err)
}
fmt.Printf("Updated roles: %v\n", user.Data.ProjectRoles)ctx := context.Background()
err := client.ProjectUsers.Remove(ctx, "your-org-uuid", "your-project-uuid", "user-uuid")
if err != nil {
log.Fatalf("Error: %v", err)
}ctx := context.Background()
instance, err := client.Instances.Get(ctx, "your-org-uuid", "your-project-uuid", "instance-id")
if err != nil {
log.Printf("Error: %v\n", err)
return
}ctx := context.Background()
instance, err := client.Instances.Get(ctx, "your-org-uuid", "your-project-uuid", "non-existent-id")
if err != nil {
if apiErr, ok := err.(*aura.Error); ok {
fmt.Printf("API Error %d: %s\n", apiErr.StatusCode, apiErr.Message)
switch {
case apiErr.IsNotFound():
fmt.Println("Instance not found")
case apiErr.IsUnauthorized():
fmt.Println("Authentication failed - check credentials")
case apiErr.IsBadRequest():
fmt.Println("Invalid request parameters")
}
if apiErr.HasMultipleErrors() {
fmt.Println("All errors:")
for _, msg := range apiErr.AllErrors() {
fmt.Printf(" - %s\n", msg)
}
}
return
}
log.Printf("Unexpected error: %v\n", err)
return
}ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
instances, err := client.Instances.List(ctx, "your-org-uuid", "your-project-uuid")
if err != nil {
switch ctx.Err() {
case context.DeadlineExceeded:
log.Println("Request timed out")
case context.Canceled:
log.Println("Request was cancelled")
default:
log.Printf("Error: %v\n", err)
}
return
}clientID := os.Getenv("AURA_CLIENT_ID")
clientSecret := os.Getenv("AURA_CLIENT_SECRET")
if clientID == "" || clientSecret == "" {
log.Fatal("Missing AURA credentials in environment")
}
client, err := aura.NewClient(
aura.WithCredentials(clientID, clientSecret),
)ctx := context.Background()
instance, err := client.Instances.Create(ctx, config)
if err != nil {
log.Fatal(err)
}
// ⚠️ CRITICAL: Save these immediately — they are only shown once!
credentials := map[string]string{
"instance_id": instance.Data.ID,
"connection_url": instance.Data.ConnectionURL,
"username": instance.Data.Username,
"password": instance.Data.Password,
}
// Store in a secrets manager. Do NOT log passwords in production.ctx := context.Background()
instanceID := newInstance.Data.ID
for range 30 {
inst, err := client.Instances.Get(ctx, instanceID)
if err != nil {
log.Printf("Error checking status: %v", err)
} else if inst.Data.Status == aura.StatusRunning {
fmt.Println("Instance is ready!")
break
} else {
fmt.Printf("Status: %s, waiting...\n", inst.Data.Status)
}
time.Sleep(10 * time.Second)
}ctx, cancel := context.WithCancel(context.Background())
defer cancel()
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
go func() {
<-sigChan
fmt.Println("\nShutting down gracefully...")
cancel()
}()
// Pass ctx to any in-flight calls — they will be cancelled on signal
instances, err := client.Instances.List(ctx)func retryOperation(maxRetries int, fn func() error) error {
var err error
for i := range maxRetries {
err = fn()
if err == nil {
return nil
}
if apiErr, ok := err.(*aura.Error); ok {
// Don't retry client errors (4xx except 429 Too Many Requests)
if apiErr.StatusCode >= 400 && apiErr.StatusCode < 500 && apiErr.StatusCode != 429 {
return err
}
}
wait := time.Duration(math.Pow(2, float64(i))) * time.Second
fmt.Printf("Attempt %d failed, retrying in %v...\n", i+1, wait)
time.Sleep(wait)
}
return fmt.Errorf("operation failed after %d retries: %w", maxRetries, err)
}
// Usage
ctx := context.Background()
err := retryOperation(3, func() error {
_, err := client.Instances.List(ctx)
return err
})package main
import (
"context"
"fmt"
"log"
"os"
"time"
aura "github.com/neo4j-contrib/aura-go-sdk"
)
func main() {
clientID := os.Getenv("AURA_CLIENT_ID")
clientSecret := os.Getenv("AURA_CLIENT_SECRET")
tenantID := os.Getenv("AURA_TENANT_ID")
if clientID == "" || clientSecret == "" {
log.Fatal("Missing required environment variables")
}
client, err := aura.NewClient(
aura.WithCredentials(clientID, clientSecret),
aura.WithTimeout(120 * time.Second),
)
if err != nil {
log.Fatalf("Failed to create client: %v", err)
}
ctx := context.Background()
fmt.Println("=== Current Instances ===")
instances, err := client.Instances.List(ctx)
if err != nil {
log.Fatalf("Failed to list instances: %v", err)
}
for _, inst := range instances.Data {
fmt.Printf("- %s: %s (%s)\n", inst.Name, inst.ID, inst.CloudProvider)
}
if tenantID != "" {
fmt.Println("\n=== Tenant Configuration ===")
tenant, err := client.Tenants.Get(ctx, tenantID)
if err != nil {
log.Printf("Warning: Could not get tenant: %v", err)
} else {
fmt.Printf("Tenant: %s\n", tenant.Data.Name)
fmt.Printf("Available configurations: %d\n", len(tenant.Data.InstanceConfigurations))
}
}
fmt.Println("\n✓ Client is working correctly!")
}Run with:
export AURA_CLIENT_ID="your-client-id"
export AURA_CLIENT_SECRET="your-client-secret"
export AURA_TENANT_ID="your-tenant-id"
go run main.goThree GitHub Actions workflows manage CI and the release process.
| Workflow | Trigger | What it does |
|---|---|---|
| CI | Push to main, every PR |
Runs tests with the race detector, golangci-lint, and go build ./... |
| Changelog check | Every PR | Fails if the PR changes .go files but has no entry in .changes/unreleased/ |
| Release | Push of a vX.Y.Z tag |
Gates on tests, extracts the changelog section, creates a GitHub Release |
Releases follow a three-step process. changie collects the unreleased fragment files and determines the correct semver bump automatically from the change kinds (Added → minor, Fixed/Security → patch, Changed/Removed → major).
There is no manual version bump required. ClientVersion uses debug.ReadBuildInfo() at runtime to read the module version that the Go toolchain embeds when a consumer builds their application. It falls back to "development" only in local and test builds.
1. Batch and merge the changelog
changie batch # collects .changes/unreleased/*.yaml → .changes/vX.Y.Z.md
changie merge # folds that file into CHANGELOG.md2. Commit and tag
git add CHANGELOG.md .changes/
git commit -m "chore: release vX.Y.Z"
git tag vX.Y.Z
git push origin main --tags3. Workflow takes over
Pushing the tag fires the Release workflow, which:
- Runs
go test -race ./...— the release is aborted if any test fails - Extracts the
## vX.Y.Zsection fromCHANGELOG.md - Creates a GitHub Release with that text as the release notes
Because this is a Go module with no compiled binaries, the tag itself is what consumers reference:
go get github.com/neo4j-contrib/aura-go-sdk@vX.Y.ZEvery PR that changes Go source files needs a changie fragment. Run:
changie newChoose a kind and write a one-line summary, then commit the generated .yaml file alongside your code changes. The Changelog check workflow will fail the PR if this step is skipped.
To bypass the check for a PR that genuinely needs no entry (docs-only, CI-only, or test-only changes), add the no-changelog label to the PR.
Contributions are welcome! Please feel free to submit issues or pull requests.
See LICENSE file for details.