API keys allow external services and scripts to authenticate with the Naturalead API without a user session. Each key is scoped to an account and restricted to a specific set of RBAC permissions.
Key concepts
Scoped permissions Each key is granted a subset of RBAC permissions. A key creator can only grant permissions they themselves hold.
Environment isolation Keys are either live or test. Live keys use the prefix nl_live_ and test keys use nl_test_.
Shown once The full key is returned only at creation (or rotation). It is hashed with SHA-256 before storage and can never be retrieved again.
Audit trail All key lifecycle events (create, revoke, rotate, delete) are logged to the audit log for SOC2 compliance.
Step-by-step
Choose a descriptive name, select the permissions the key needs, and pick the environment.
curl -X POST "${ API_URL }/api/api-keys" \
-H "X-API-Key: ${ API_KEY }" \
-H "Content-Type: application/json" \
-d '{
"name": "CRM lead sync",
"scopes": ["leads:view", "leads:import", "conversations:view"],
"environment": "live",
"expiresAt": "2027-01-01T00:00:00Z"
}'
import requests
headers = {
"X-API-Key" : API_KEY ,
"Content-Type" : "application/json" ,
}
response = requests.post(
f " { API_URL } /api/api-keys" ,
headers = headers,
json = {
"name" : "CRM lead sync" ,
"scopes" : [ "leads:view" , "leads:import" , "conversations:view" ],
"environment" : "live" ,
"expiresAt" : "2027-01-01T00:00:00Z" ,
},
)
data = response.json()
full_key = data[ "fullKey" ] # Save this - shown only once!
print ( f "Key created: { data[ 'apiKey' ][ 'keyPrefix' ] } ... { data[ 'apiKey' ][ 'keyHint' ] } " )
const response = await fetch ( ` ${ API_URL } /api/api-keys` , {
method: "POST" ,
headers: {
"X-API-Key" : API_KEY ,
"Content-Type" : "application/json" ,
},
body: JSON . stringify ({
name: "CRM lead sync" ,
scopes: [ "leads:view" , "leads:import" , "conversations:view" ],
environment: "live" ,
expiresAt: "2027-01-01T00:00:00Z" ,
}),
});
const data = await response . json ();
const fullKey = data . fullKey ; // Save this - shown only once!
console . log ( `Key created: ${ data . apiKey . keyPrefix } ... ${ data . apiKey . keyHint } ` );
Store the fullKey value immediately. It is returned only once and cannot be retrieved later. If lost, you must rotate the key to get a new one.
Include the key in requests via the X-API-Key header or as a Bearer token.
curl "${ API_URL }/api/leads" \
-H "X-API-Key: nl_live_a1b2c3d4..."
curl "${ API_URL }/api/leads" \
-H "Authorization: Bearer nl_live_a1b2c3d4..."
View all keys for your account. Full key values are never returned — only the prefix and last 4 characters.
curl "${ API_URL }/api/api-keys" \
-H "X-API-Key: ${ API_KEY }"
response = requests.get(
f " { API_URL } /api/api-keys" ,
headers = { "X-API-Key" : API_KEY },
)
for key in response.json():
print ( f " { key[ 'name' ] } : { key[ 'keyPrefix' ] } ... { key[ 'keyHint' ] } ( { key[ 'status' ] } )" )
const response = await fetch ( ` ${ API_URL } /api/api-keys` , {
headers: { "X-API-Key" : API_KEY },
});
const keys = await response . json ();
keys . forEach (( key ) => {
console . log ( ` ${ key . name } : ${ key . keyPrefix } ... ${ key . keyHint } ( ${ key . status } )` );
});
Rotation atomically revokes the old key and creates a new one with the same name, scopes, and environment. The old key stops working immediately.
curl -X POST "${ API_URL }/api/api-keys/${ KEY_ID }/rotate" \
-H "X-API-Key: ${ API_KEY }"
response = requests.post(
f " { API_URL } /api/api-keys/ { key_id } /rotate" ,
headers = { "X-API-Key" : API_KEY },
)
data = response.json()
new_key = data[ "fullKey" ] # Save the new key!
print ( f "Rotated: { data[ 'apiKey' ][ 'keyPrefix' ] } ... { data[ 'apiKey' ][ 'keyHint' ] } " )
const response = await fetch ( ` ${ API_URL } /api/api-keys/ ${ keyId } /rotate` , {
method: "POST" ,
headers: { "X-API-Key" : API_KEY },
});
const data = await response . json ();
const newKey = data . fullKey ; // Save the new key!
console . log ( `Rotated: ${ data . apiKey . keyPrefix } ... ${ data . apiKey . keyHint } ` );
Revoking a key disables it permanently. Revoked keys remain visible for audit purposes but can be deleted.
curl -X PATCH "${ API_URL }/api/api-keys/${ KEY_ID }/revoke" \
-H "X-API-Key: ${ API_KEY }"
response = requests.patch(
f " { API_URL } /api/api-keys/ { key_id } /revoke" ,
headers = { "X-API-Key" : API_KEY },
)
print (response.json())
const response = await fetch ( ` ${ API_URL } /api/api-keys/ ${ keyId } /revoke` , {
method: "PATCH" ,
headers: { "X-API-Key" : API_KEY },
});
console . log ( await response . json ());
Only revoked keys can be permanently deleted. Active keys must be revoked first.
curl -X DELETE "${ API_URL }/api/api-keys/${ KEY_ID }" \
-H "X-API-Key: ${ API_KEY }"
response = requests.delete(
f " { API_URL } /api/api-keys/ { key_id } " ,
headers = { "X-API-Key" : API_KEY },
)
print (response.json()) # {"success": true}
const response = await fetch ( ` ${ API_URL } /api/api-keys/ ${ keyId } ` , {
method: "DELETE" ,
headers: { "X-API-Key" : API_KEY },
});
console . log ( await response . json ()); // { success: true }
Best practices
Follow these recommendations to keep your API keys secure and manageable.
Principle of least privilege — Grant only the permissions each integration actually needs. A CRM sync that only reads leads should not have leads:import or campaigns:manage.
Set expiration dates — Use expiresAt to enforce key rotation on a schedule. Keys without expiration remain active indefinitely.
Rotate regularly — Rotate keys at least every 90 days. Use the rotate endpoint for zero-downtime replacement.
Use environment separation — Use test keys during development and live keys in production. Never share keys across environments.
Never commit keys to source control — Store keys in environment variables or a secrets manager. If a key is accidentally exposed, revoke and rotate it immediately.
Monitor usage — Check the lastUsedAt field when listing keys. Keys that have not been used in a long time may be candidates for revocation.
Rate limits apply — API keys are subject to rate limiting (500 requests/minute per key, 1000 requests/minute per IP globally).
Required permissions
Action Permission required List keys api_keys:viewCreate key api_keys:manageRevoke key api_keys:manageRotate key api_keys:manageDelete key api_keys:manage