HelloJohn / docs
Authentication

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 caseAuth method
CLI tool calling your APIAPI key
Backend service calling another serviceAPI key
GitHub Actions / CI/CD pipelineAPI key
Cron job or scheduled taskAPI key
Webhooks sending eventsAPI key or HMAC
Users logging inEmail/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

  1. Go to API Keys in the admin panel
  2. Click Create API key
  3. Enter a name (e.g. ci-pipeline, backend-service)
  4. Select scopes (permissions this key can use)
  5. 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):

ScopeAccess
users:readList and get users
users:writeCreate, update, delete users
tenants:readList and get tenants
tenants:writeCreate, update, delete tenants
sessions:readList sessions
sessions:writeRevoke sessions
audit:readRead audit log
webhooks:writeManage 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.

On this page