SDKs
Arcan provides official client SDKs for Go, Python, and Node.js. All SDKs have zero external dependencies and use only standard library modules.
Install
- Go
- Python
- Node.js
go get getarcan.dev/arcan/sdk/go
pip install arcan
Requires Python 3.8+.
npm install @getarcan/sdk
Requires Node.js 18+.
Quick Start
- Go
- Python
- Node.js
package main
import (
"fmt"
"log"
arcan "getarcan.dev/arcan/sdk/go"
)
func main() {
client, err := arcan.New(
arcan.WithURL("https://arcan.internal:8443"),
arcan.WithToken("arc_xxx"),
arcan.WithRealm("production"),
arcan.WithEnv("prod"),
)
if err != nil {
log.Fatal(err)
}
dbURL, err := client.Get("DATABASE_URL")
if err != nil {
log.Fatal(err)
}
fmt.Println(dbURL)
}
from arcan import ArcanClient
client = ArcanClient(
url="https://arcan.internal:8443",
token="arc_xxx",
realm="production",
env="prod",
)
db_url = client.get("DATABASE_URL")
import { ArcanClient } from '@getarcan/sdk';
const client = new ArcanClient({
url: 'https://arcan.internal:8443',
token: 'arc_xxx',
realm: 'production',
env: 'prod',
});
const dbUrl = await client.get('DATABASE_URL');
Environment Variable Auto-Config
All SDKs support automatic configuration from environment variables. Set ARCAN_URL, ARCAN_TOKEN, ARCAN_REALM, and ARCAN_ENV, then create a client with no arguments:
- Go
- Python
- Node.js
client, err := arcan.New()
if err != nil {
log.Fatal(err)
}
dbURL := client.MustGet("DATABASE_URL")
client = ArcanClient()
db_url = client.get("DATABASE_URL")
const client = new ArcanClient();
const dbUrl = await client.get('DATABASE_URL');
API Reference
Client Options
- Go
- Python
- Node.js
| Option | Default | Description |
|---|---|---|
WithURL(url) | ARCAN_URL or https://localhost:8443 | Server URL |
WithToken(token) | ARCAN_TOKEN | Auth token (required) |
WithRealm(realm) | ARCAN_REALM or "default" | Realm/namespace |
WithEnv(env) | ARCAN_ENV or "dev" | Environment |
WithInsecureSkipVerify(bool) | true | Accept self-signed certs |
WithCacheTTL(duration) | 5m | Cache TTL (0 to disable) |
| Parameter | Default | Description |
|---|---|---|
url | ARCAN_URL or https://localhost:8443 | Server URL |
token | ARCAN_TOKEN | Auth token (required) |
realm | ARCAN_REALM or "default" | Realm/namespace |
env | ARCAN_ENV or "dev" | Environment |
verify_ssl | False | Verify TLS certificates |
cache_ttl | 300 | Cache TTL in seconds (0 = disabled) |
| Option | Default | Description |
|---|---|---|
url | ARCAN_URL or https://localhost:8443 | Server URL |
token | ARCAN_TOKEN | Auth token (required) |
realm | ARCAN_REALM or "default" | Realm/namespace |
env | ARCAN_ENV or "dev" | Environment |
rejectUnauthorized | false | Reject invalid TLS certs |
cacheTtlMs | 300000 | Cache TTL in ms (0 = disabled) |
Methods
All SDKs provide the same core methods:
| Method | Description |
|---|---|
Get(key) / get(key) | Fetch a secret value |
GetWithMeta(key) / get_meta(key) / getMeta(key) | Fetch secret with metadata |
List() / list() | Fetch all secrets |
Set(key, value) / set(key, value) | Create or update a secret |
Delete(key) / delete(key) | Delete a secret |
Health() / health() | Check server health |
Go additionally has MustGet(key) which panics on error (useful for initialization).
Error Handling
- Go
- Python
- Node.js
import "errors"
val, err := client.Get("MY_SECRET")
if errors.Is(err, arcan.ErrNotFound) {
// Secret does not exist
} else if errors.Is(err, arcan.ErrAuth) {
// Invalid or expired token
} else if errors.Is(err, arcan.ErrConnection) {
// Cannot reach server
} else if errors.Is(err, arcan.ErrServer) {
// Server returned 5xx
}
from arcan import ArcanClient, NotFoundError, AuthError, ConnectionError, ServerError
try:
val = client.get("MY_SECRET")
except NotFoundError:
print("Secret does not exist")
except AuthError:
print("Invalid or expired token")
except ConnectionError:
print("Cannot reach Arcan server")
except ServerError:
print("Server returned an error")
import {
ArcanClient,
NotFoundError,
AuthError,
ConnectionError,
ServerError,
} from '@getarcan/sdk';
try {
const val = await client.get('MY_SECRET');
} catch (err) {
if (err instanceof NotFoundError) {
// Secret does not exist
} else if (err instanceof AuthError) {
// Invalid or expired token
} else if (err instanceof ConnectionError) {
// Cannot reach server
} else if (err instanceof ServerError) {
// Server returned 5xx
}
}
Caching
All SDKs cache secrets in memory for 5 minutes by default. The cache is thread-safe. Write operations (Set, Delete) automatically invalidate relevant cache entries.
- Go
- Python
- Node.js
// Disable caching
client, _ := arcan.New(arcan.WithCacheTTL(0))
// Cache for 30 seconds
client, _ := arcan.New(arcan.WithCacheTTL(30 * time.Second))
# Disable caching
client = ArcanClient(token="arc_xxx", cache_ttl=0)
# Cache for 30 seconds
client = ArcanClient(token="arc_xxx", cache_ttl=30)
// Disable caching
const client = new ArcanClient({ token: 'arc_xxx', cacheTtlMs: 0 });
// Cache for 30 seconds
const client = new ArcanClient({ token: 'arc_xxx', cacheTtlMs: 30_000 });
Real-World Examples
Go: Database connection with retry
package main
import (
"database/sql"
"log"
"net/http"
"time"
_ "github.com/lib/pq"
arcan "getarcan.dev/arcan/sdk/go"
)
func main() {
// Auto-configures from ARCAN_URL, ARCAN_TOKEN, ARCAN_REALM, ARCAN_ENV
secrets, err := arcan.New()
if err != nil {
log.Fatalf("arcan init failed: %v", err)
}
// Fetch database connection string
dsn, err := secrets.Get("DATABASE_URL")
if err != nil {
log.Fatalf("failed to fetch DATABASE_URL: %v", err)
}
// Connect to PostgreSQL
db, err := sql.Open("postgres", dsn)
if err != nil {
log.Fatalf("failed to open database: %v", err)
}
defer db.Close()
db.SetMaxOpenConns(25)
db.SetMaxIdleConns(5)
db.SetConnMaxLifetime(5 * time.Minute)
if err := db.Ping(); err != nil {
log.Fatalf("database unreachable: %v", err)
}
log.Println("Connected to database successfully")
// Fetch API key for downstream service
apiKey := secrets.MustGet("PAYMENT_API_KEY")
http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
if err := db.Ping(); err != nil {
http.Error(w, "database unhealthy", http.StatusServiceUnavailable)
return
}
w.Write([]byte("ok"))
})
_ = apiKey // used in payment handler
log.Fatal(http.ListenAndServe(":8080", nil))
}
Python: FastAPI application with secrets
from contextlib import asynccontextmanager
import asyncpg
from fastapi import FastAPI
from arcan import ArcanClient, ConnectionError, AuthError
# Initialize Arcan — reads ARCAN_URL, ARCAN_TOKEN, ARCAN_REALM, ARCAN_ENV
secrets = ArcanClient()
@asynccontextmanager
async def lifespan(app: FastAPI):
# Startup: fetch secrets and connect to database
try:
db_url = secrets.get("DATABASE_URL")
redis_url = secrets.get("REDIS_URL")
except ConnectionError:
raise RuntimeError("Cannot reach Arcan server — is ARCAN_URL correct?")
except AuthError:
raise RuntimeError("Arcan token is invalid or expired — check ARCAN_TOKEN")
app.state.db = await asyncpg.create_pool(db_url, min_size=5, max_size=20)
print(f"Database pool created with {app.state.db.get_size()} connections")
yield
# Shutdown
await app.state.db.close()
app = FastAPI(lifespan=lifespan)
@app.get("/users/{user_id}")
async def get_user(user_id: int):
row = await app.state.db.fetchrow("SELECT * FROM users WHERE id = $1", user_id)
if not row:
return {"error": "user not found"}
return dict(row)
Node.js: Express app with connection pooling
import express from 'express';
import { Pool } from 'pg';
import { ArcanClient, ConnectionError, AuthError } from '@getarcan/sdk';
// Auto-configures from ARCAN_URL, ARCAN_TOKEN, ARCAN_REALM, ARCAN_ENV
const secrets = new ArcanClient();
async function main() {
// Fetch all required secrets up front
let dbUrl: string;
let stripeKey: string;
try {
dbUrl = await secrets.get('DATABASE_URL');
stripeKey = await secrets.get('STRIPE_SECRET_KEY');
} catch (err) {
if (err instanceof ConnectionError) {
console.error('Cannot reach Arcan server. Check ARCAN_URL.');
} else if (err instanceof AuthError) {
console.error('Arcan token is invalid. Check ARCAN_TOKEN.');
}
process.exit(1);
}
const pool = new Pool({ connectionString: dbUrl, max: 20 });
const app = express();
app.get('/health', async (_req, res) => {
try {
await pool.query('SELECT 1');
res.json({ status: 'ok' });
} catch {
res.status(503).json({ status: 'unhealthy' });
}
});
app.listen(3000, () => {
console.log('Server running on :3000');
});
}
main();
Cache Behavior
All SDKs cache secrets in memory for 5 minutes by default. Here is how the cache works in practice:
- Go
- Python
- Node.js
client, _ := arcan.New(arcan.WithCacheTTL(2 * time.Minute))
// First call: makes an HTTP request to Arcan server (~50ms)
val1, _ := client.Get("DATABASE_URL")
// Second call within 2 minutes: returns from cache (~0ms)
val2, _ := client.Get("DATABASE_URL")
// After updating a secret, the cache entry is automatically invalidated
client.Set("DATABASE_URL", "postgres://new-host:5432/db")
// Next call fetches the fresh value from the server
val3, _ := client.Get("DATABASE_URL")
client = ArcanClient(cache_ttl=120) # 2 minutes
# First call: HTTP request to server
val1 = client.get("DATABASE_URL")
# Second call within 2 minutes: served from cache
val2 = client.get("DATABASE_URL")
# Writes invalidate the cache automatically
client.set("DATABASE_URL", "postgres://new-host:5432/db")
# Next read fetches the updated value
val3 = client.get("DATABASE_URL")
const client = new ArcanClient({ cacheTtlMs: 120_000 }); // 2 minutes
// First call: HTTP request to server
const val1 = await client.get('DATABASE_URL');
// Second call within 2 minutes: served from cache
const val2 = await client.get('DATABASE_URL');
// Writes invalidate the cache automatically
await client.set('DATABASE_URL', 'postgres://new-host:5432/db');
// Next read fetches the updated value
const val3 = await client.get('DATABASE_URL');
For long-running applications, the default 5-minute cache TTL works well — it reduces API load while ensuring secrets refresh within minutes of a rotation. For short-lived jobs (CI/CD, cron), set cacheTtlMs: 0 (or equivalent) to always fetch fresh values.
Environment Variable Auto-Config in Practice
Set these four environment variables in your deployment platform (ECS task definition, Kubernetes pod spec, systemd unit, etc.) and the SDK requires zero configuration in code:
export ARCAN_URL=https://arcan.internal:8443
export ARCAN_TOKEN=arc_prod_a1b2c3d4e5f6
export ARCAN_REALM=myapp
export ARCAN_ENV=prod
Then in your application:
- Go
- Python
- Node.js
// No URL, token, realm, or env needed — all from environment
client, err := arcan.New()
if err != nil {
log.Fatal(err)
}
dbURL := client.MustGet("DATABASE_URL")
# Picks up all four env vars automatically
client = ArcanClient()
db_url = client.get("DATABASE_URL")
// Zero-config — reads from ARCAN_URL, ARCAN_TOKEN, ARCAN_REALM, ARCAN_ENV
const client = new ArcanClient();
const dbUrl = await client.get('DATABASE_URL');
This pattern keeps secrets out of your codebase entirely. The only secret your deployment platform needs is the Arcan token — everything else is fetched at runtime.
Complete Example: Fetch DATABASE_URL and Connect to PostgreSQL
Each example below shows the full flow: configure via environment variables, connect to Arcan, fetch DATABASE_URL, connect to PostgreSQL, and handle errors.
Go
package main
import (
"database/sql"
"errors"
"fmt"
"log"
"os"
_ "github.com/lib/pq"
arcan "getarcan.dev/arcan/sdk/go"
)
func main() {
// Option 1: Auto-config from environment variables
// Set these in your deployment platform (ECS task def, K8s pod spec, systemd unit):
// ARCAN_URL=https://arcan.internal:8443
// ARCAN_TOKEN=arc_prod_a1b2c3d4
// ARCAN_REALM=myapp
// ARCAN_ENV=prod
client, err := arcan.New()
if err != nil {
// Option 2: Explicit config (useful for local development)
client, err = arcan.New(
arcan.WithURL("https://arcan.internal:8443"),
arcan.WithToken(os.Getenv("ARCAN_TOKEN")),
arcan.WithRealm("myapp"),
arcan.WithEnv("prod"),
)
if err != nil {
log.Fatalf("Failed to initialize Arcan client: %v", err)
}
}
// Fetch the database connection string
dsn, err := client.Get("DATABASE_URL")
if err != nil {
if errors.Is(err, arcan.ErrNotFound) {
log.Fatal("DATABASE_URL not found in Arcan. Run: arcan kv set DATABASE_URL '<value>' -r myapp -e prod")
} else if errors.Is(err, arcan.ErrAuth) {
log.Fatal("Arcan token is invalid or expired. Check ARCAN_TOKEN.")
} else if errors.Is(err, arcan.ErrConnection) {
log.Fatalf("Cannot reach Arcan server at %s. Check ARCAN_URL.", os.Getenv("ARCAN_URL"))
}
log.Fatalf("Failed to fetch DATABASE_URL: %v", err)
}
// Connect to PostgreSQL
db, err := sql.Open("postgres", dsn)
if err != nil {
log.Fatalf("Failed to parse DATABASE_URL: %v", err)
}
defer db.Close()
if err := db.Ping(); err != nil {
log.Fatalf("Cannot reach database: %v", err)
}
fmt.Println("Connected to PostgreSQL via Arcan successfully")
// Use the database
var version string
if err := db.QueryRow("SELECT version()").Scan(&version); err != nil {
log.Fatal(err)
}
fmt.Printf("PostgreSQL version: %s\n", version)
}
Python
import sys
import psycopg2
from arcan import ArcanClient, NotFoundError, AuthError, ConnectionError
def main():
# Auto-config from environment variables:
# ARCAN_URL=https://arcan.internal:8443
# ARCAN_TOKEN=arc_prod_a1b2c3d4
# ARCAN_REALM=myapp
# ARCAN_ENV=prod
client = ArcanClient()
# Fetch the database connection string
try:
dsn = client.get("DATABASE_URL")
except NotFoundError:
print("DATABASE_URL not found in Arcan.")
print("Run: arcan kv set DATABASE_URL '<value>' -r myapp -e prod")
sys.exit(1)
except AuthError:
print("Arcan token is invalid or expired. Check ARCAN_TOKEN.")
sys.exit(1)
except ConnectionError:
print("Cannot reach Arcan server. Check ARCAN_URL.")
sys.exit(1)
# Connect to PostgreSQL
try:
conn = psycopg2.connect(dsn)
conn.autocommit = True
except psycopg2.OperationalError as e:
print(f"Cannot connect to database: {e}")
sys.exit(1)
print("Connected to PostgreSQL via Arcan successfully")
# Use the database
with conn.cursor() as cur:
cur.execute("SELECT version()")
version = cur.fetchone()[0]
print(f"PostgreSQL version: {version}")
conn.close()
if __name__ == "__main__":
main()
Node.js
import { Pool } from 'pg';
import {
ArcanClient,
NotFoundError,
AuthError,
ConnectionError,
} from '@getarcan/sdk';
async function main() {
// Auto-config from environment variables:
// ARCAN_URL=https://arcan.internal:8443
// ARCAN_TOKEN=arc_prod_a1b2c3d4
// ARCAN_REALM=myapp
// ARCAN_ENV=prod
const client = new ArcanClient();
// Fetch the database connection string
let dsn: string;
try {
dsn = await client.get('DATABASE_URL');
} catch (err) {
if (err instanceof NotFoundError) {
console.error('DATABASE_URL not found in Arcan.');
console.error('Run: arcan kv set DATABASE_URL \'<value>\' -r myapp -e prod');
} else if (err instanceof AuthError) {
console.error('Arcan token is invalid or expired. Check ARCAN_TOKEN.');
} else if (err instanceof ConnectionError) {
console.error('Cannot reach Arcan server. Check ARCAN_URL.');
} else {
console.error('Failed to fetch DATABASE_URL:', err);
}
process.exit(1);
}
// Connect to PostgreSQL
const pool = new Pool({ connectionString: dsn, max: 20 });
try {
const result = await pool.query('SELECT version()');
console.log('Connected to PostgreSQL via Arcan successfully');
console.log(`PostgreSQL version: ${result.rows[0].version}`);
} catch (err) {
console.error('Cannot connect to database:', err);
process.exit(1);
}
// Graceful shutdown
process.on('SIGTERM', async () => {
await pool.end();
process.exit(0);
});
}
main();
Set the four environment variables (ARCAN_URL, ARCAN_TOKEN, ARCAN_REALM, ARCAN_ENV) in your deployment platform once. Then every SDK call in your application code requires zero configuration -- just ArcanClient() with no arguments.
Security
- Use valid TLS certificates in production (not self-signed)
- Store API tokens in your platform's secret management (not hardcoded)
- Use read-only tokens (
arcan token create --scopes read) for applications that only need to read secrets - Enable audit logging on the Arcan server to track all secret access