API Keys (M2M)
Create and manage API keys for machine-to-machine authentication in HelloJohn. CLI tools, backend services, webhooks, and CI/CD pipelines.
API keys are for machine-to-machine (M2M) authentication — when a server, CLI tool, or automated process needs to call HelloJohn or your API without a human user involved.
When to use API keys
| Use case | Auth method |
|---|---|
| CLI tool calling your API | API key |
| Backend service calling another service | API key |
| GitHub Actions / CI/CD pipeline | API key |
| Cron job or scheduled task | API key |
| Webhooks sending events | API key or HMAC |
| Users logging in | Email/password, OAuth, magic link |
API keys are long-lived secrets. Never expose them in frontend code, browser environments, or version control.
Creating an API key
From the dashboard
- Go to API Keys in the admin panel
- Click Create API key
- Enter a name (e.g.
ci-pipeline,backend-service) - Select scopes (permissions this key can use)
- Copy the key — it won't be shown again
Via REST API
POST /v2/admin/api-keys
Authorization: Bearer $ADMIN_TOKEN
Content-Type: application/json
{
"name": "backend-service",
"scopes": ["users:read", "tenants:read"],
"expires_at": "2027-01-01T00:00:00Z" // optional
}Response:
{
"id": "key_01HX...",
"name": "backend-service",
"key": "hj_live_xxxxxxxxxxxxxxxxxxxxxxxxxxx",
"scopes": ["users:read", "tenants:read"],
"created_at": "2026-01-15T10:00:00Z",
"expires_at": "2027-01-01T00:00:00Z"
}The key value is only returned once at creation time.
Via hjctl
hjctl keys create --name ci-pipeline --scopes "users:read,tenants:read"Using an API key
Pass the key in the Authorization header:
curl https://your-instance.hellojohn.dev/v2/admin/users \
-H "Authorization: Bearer hj_live_xxxxxxxxxxxxxxxxxxxxxxxxxxx"Or using the SDK:
const { HelloJohn } = require('@hellojohn/js')
const hj = new HelloJohn({
instanceUrl: 'https://your-instance.hellojohn.dev',
apiKey: process.env.HELLOJOHN_API_KEY
})
const users = await hj.users.list({ tenantId: 'ten_...' })import "github.com/hellojohn/go-sdk"
client := hellojohn.New(hellojohn.Config{
InstanceURL: "https://your-instance.hellojohn.dev",
APIKey: os.Getenv("HELLOJOHN_API_KEY"),
})
users, err := client.Users.List(ctx, &hellojohn.ListUsersParams{
TenantID: "ten_...",
})from hellojohn import HelloJohn
hj = HelloJohn(
instance_url="https://your-instance.hellojohn.dev",
api_key=os.environ["HELLOJOHN_API_KEY"]
)
users = hj.users.list(tenant_id="ten_...")Scopes
API keys support fine-grained scopes. Assign only the permissions needed (principle of least privilege):
| Scope | Access |
|---|---|
users:read | List and get users |
users:write | Create, update, delete users |
tenants:read | List and get tenants |
tenants:write | Create, update, delete tenants |
sessions:read | List sessions |
sessions:write | Revoke sessions |
audit:read | Read audit log |
webhooks:write | Manage webhooks |
admin:* | Full admin access (use with caution) |
Rotating API keys
API keys cannot be updated — to rotate one, create a new key and delete the old one:
# 1. Create new key
hjctl keys create --name backend-service-v2 --scopes "users:read"
# 2. Update your services to use the new key
# 3. Delete the old key
hjctl keys delete key_01HX...Plan key rotations during low-traffic windows. There's no grace period — once a key is deleted, all requests using it return 401 immediately.
Listing and auditing keys
# List all keys
hjctl keys list
# See usage for a specific key
hjctl keys usage key_01HX...Or via API:
GET /v2/admin/api-keys{
"keys": [
{
"id": "key_01HX...",
"name": "backend-service",
"scopes": ["users:read"],
"last_used_at": "2026-03-07T14:22:00Z",
"expires_at": null
}
]
}Expiring keys
Set an expiry date on keys for time-limited access (e.g. contractor access, temporary integrations):
{
"name": "contractor-access",
"scopes": ["users:read"],
"expires_at": "2026-06-01T00:00:00Z"
}Expired keys return 401 Unauthorized with error: "api_key_expired".
Per-tenant API keys
To scope an API key to a specific tenant (so it can only access that tenant's data):
POST /v2/admin/tenants/{tenantId}/api-keys
{
"name": "tenant-integration",
"scopes": ["users:read", "users:write"]
}Tenant-scoped keys automatically restrict all operations to that tenant — they cannot access other tenants' data.