API Keys
API keys authenticate your requests to the Hook Mesh API. Manage keys programmatically to enable secure access control, key rotation, and scope limiting.
The API Key Object
API keys provide authenticated access to your organization's resources. Each key can be scoped to specific permissions and rotated without downtime.
Attributes
- idstring
- Unique identifier for the API key (KSUID format)
- org_idstring
- Organization that owns this API key
- namestring
- Human-readable name for identification (e.g., "Production Server")
- keystring
- The API key value (only returned on creation, never shown again)
- prefixstring
- First 8 characters of the key for identification (e.g., "sk_live_")
- environmentstring
- Environment mode:
liveortest - scopesarray
- Permissions granted to this key (e.g., ["webhooks:write", "endpoints:read"])
- last_used_atstring | null
- ISO 8601 timestamp of the last API request with this key
- expires_atstring | null
- Optional expiration date for automatic key rotation
- created_atstring
- ISO 8601 timestamp when the key was created
{
"id": "key_2Zy3X8a9b0c1d2e3f4g5h6i7",
"org_id": "org_xyz789",
"name": "Production Server",
"prefix": "sk_live_",
"environment": "live",
"scopes": ["*"],
"last_used_at": "2026-01-20T14:30:00Z",
"expires_at": null,
"created_at": "2026-01-15T10:30:00Z"
}Key Security
The full key value is only returned when you create a new API key. After creation, only the prefix is shown for identification. Store the full key securely.
Create an API Key
Generate a new API key for authenticating requests. The full key value is returned only once on creation.
POST /v1/api-keysParameters
| Field | Type | Description |
|---|---|---|
| name | string | requiredHuman-readable name for the key |
| environment | string | live or test (default: live) |
| scopes | array | Permissions (default: ["*"] for full access) |
| expires_at | string | Optional expiration date (ISO 8601) |
import fetch from 'node-fetch';
const response = await fetch('https://api.hookmesh.com/v1/api-keys', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.HOOKMESH_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'Production Server',
environment: 'live',
scopes: ['*']
})
});
const apiKey = await response.json();
// IMPORTANT: Save the full key value immediately - it won't be shown again
console.log('New API Key:', apiKey.key);
console.log('Store this securely in your environment variables!');import requests
import os
response = requests.post(
'https://api.hookmesh.com/v1/api-keys',
headers={
'Authorization': f'Bearer {os.environ["HOOKMESH_API_KEY"]}',
'Content-Type': 'application/json'
},
json={
'name': 'Production Server',
'environment': 'live',
'scopes': ['*']
}
)
api_key = response.json()
# IMPORTANT: Save the full key value immediately - it won't be shown again
print(f'New API Key: {api_key["key"]}')
print('Store this securely in your environment variables!')package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
)
type APIKeyRequest struct {
Name string `json:"name"`
Environment string `json:"environment"`
Scopes []string `json:"scopes"`
}
func main() {
payload := APIKeyRequest{
Name: "Production Server",
Environment: "live",
Scopes: []string{"*"},
}
body, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", "https://api.hookmesh.com/v1/api-keys", bytes.NewBuffer(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("HOOKMESH_API_KEY"))
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var apiKey map[string]interface{}
json.NewDecoder(resp.Body).Decode(&apiKey)
// IMPORTANT: Save the full key value immediately - it won't be shown again
fmt.Printf("New API Key: %s\n", apiKey["key"])
fmt.Println("Store this securely in your environment variables!")
}<?php
$ch = curl_init('https://api.hookmesh.com/v1/api-keys');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('HOOKMESH_API_KEY'),
'Content-Type: application/json'
],
CURLOPT_POSTFIELDS => json_encode([
'name' => 'Production Server',
'environment' => 'live',
'scopes' => ['*']
])
]);
$response = curl_exec($ch);
$apiKey = json_decode($response, true);
// IMPORTANT: Save the full key value immediately - it won't be shown again
echo "New API Key: {$apiKey['key']}\n";
echo "Store this securely in your environment variables!\n";
curl_close($ch);Store Keys Immediately
The full API key is only shown once during creation. Store it securely in your environment variables or secrets manager. If you lose the key, you'll need to create a new one.
List API Keys
Retrieve all API keys for your organization. Only the key prefix is returned for security.
GET /v1/api-keysQuery Parameters
| Parameter | Type | Description |
|---|---|---|
| limit | integer | Number of results per page (default: 20, max: 100) |
| cursor | string | Pagination cursor from previous response |
| environment | string | Filter by environment (live or test) |
const response = await fetch(
'https://api.hookmesh.com/v1/api-keys?environment=live',
{
headers: {
'Authorization': `Bearer ${process.env.HOOKMESH_API_KEY}`
}
}
);
const { data, next_cursor } = await response.json();
console.log(`Found ${data.length} API keys`);
// Check for unused keys
const unusedKeys = data.filter(key => !key.last_used_at);
if (unusedKeys.length > 0) {
console.warn(`${unusedKeys.length} keys have never been used`);
}response = requests.get(
'https://api.hookmesh.com/v1/api-keys',
headers={'Authorization': f'Bearer {os.environ["HOOKMESH_API_KEY"]}'},
params={'environment': 'live'}
)
result = response.json()
print(f'Found {len(result["data"])} API keys')
# Check for unused keys
unused_keys = [key for key in result['data'] if not key.get('last_used_at')]
if unused_keys:
print(f'{len(unused_keys)} keys have never been used')package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
)
func main() {
req, _ := http.NewRequest("GET",
"https://api.hookmesh.com/v1/api-keys?environment=live",
nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("HOOKMESH_API_KEY"))
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
data := result["data"].([]interface{})
fmt.Printf("Found %d API keys\n", len(data))
// Check for unused keys
unusedCount := 0
for _, key := range data {
keyMap := key.(map[string]interface{})
if keyMap["last_used_at"] == nil {
unusedCount++
}
}
if unusedCount > 0 {
fmt.Printf("%d keys have never been used\n", unusedCount)
}
}<?php
$url = 'https://api.hookmesh.com/v1/api-keys?' . http_build_query([
'environment' => 'live'
]);
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('HOOKMESH_API_KEY')
]
]);
$response = curl_exec($ch);
$result = json_decode($response, true);
echo "Found " . count($result['data']) . " API keys\n";
// Check for unused keys
$unusedKeys = array_filter($result['data'], function($key) {
return empty($key['last_used_at']);
});
if (count($unusedKeys) > 0) {
echo count($unusedKeys) . " keys have never been used\n";
}
curl_close($ch);Retrieve an API Key
Get details for a specific API key by ID. The full key value is never returned.
GET /v1/api-keys/:idconst keyId = 'key_2Zy3X8a9b0c1d2e3f4g5h6i7';
const response = await fetch(
`https://api.hookmesh.com/v1/api-keys/${keyId}`,
{
headers: {
'Authorization': `Bearer ${process.env.HOOKMESH_API_KEY}`
}
}
);
const apiKey = await response.json();
console.log('Key name:', apiKey.name);
console.log('Last used:', apiKey.last_used_at || 'Never');
console.log('Scopes:', apiKey.scopes.join(', '));Update an API Key
Update the name, scopes, or expiration date for an API key. The key value itself cannot be changed.
PATCH /v1/api-keys/:idconst keyId = 'key_2Zy3X8a9b0c1d2e3f4g5h6i7';
const response = await fetch(
`https://api.hookmesh.com/v1/api-keys/${keyId}`,
{
method: 'PATCH',
headers: {
'Authorization': `Bearer ${process.env.HOOKMESH_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'Read-Only Key',
scopes: ['webhooks:read', 'endpoints:read', 'applications:read']
})
}
);
const updated = await response.json();
console.log('Key updated:', updated.id);from datetime import datetime, timedelta
key_id = 'key_2Zy3X8a9b0c1d2e3f4g5h6i7'
# Set expiration to 90 days from now
expires_at = (datetime.utcnow() + timedelta(days=90)).isoformat() + 'Z'
response = requests.patch(
f'https://api.hookmesh.com/v1/api-keys/{key_id}',
headers={
'Authorization': f'Bearer {os.environ["HOOKMESH_API_KEY"]}',
'Content-Type': 'application/json'
},
json={
'expires_at': expires_at
}
)
updated = response.json()
print(f'Key will expire on: {updated["expires_at"]}')Delete an API Key
Permanently delete an API key. All requests using this key will immediately fail with 401 Unauthorized.
DELETE /v1/api-keys/:idImmediate Revocation
Deleting an API key immediately revokes access. Any applications using this key will fail to authenticate. Ensure you've rotated to a new key before deleting the old one.
const keyId = 'key_2Zy3X8a9b0c1d2e3f4g5h6i7';
// Verify the key hasn't been used recently
const key = await fetch(
`https://api.hookmesh.com/v1/api-keys/${keyId}`,
{
headers: { 'Authorization': `Bearer ${process.env.HOOKMESH_API_KEY}` }
}
).then(r => r.json());
const lastUsed = key.last_used_at ? new Date(key.last_used_at) : null;
const daysSinceUse = lastUsed
? (Date.now() - lastUsed.getTime()) / (1000 * 60 * 60 * 24)
: Infinity;
if (daysSinceUse < 7) {
console.warn(`Warning: Key was used ${Math.round(daysSinceUse)} days ago`);
}
// Delete the key
const response = await fetch(
`https://api.hookmesh.com/v1/api-keys/${keyId}`,
{
method: 'DELETE',
headers: {
'Authorization': `Bearer ${process.env.HOOKMESH_API_KEY}`
}
}
);
console.log('API key deleted');from datetime import datetime
key_id = 'key_2Zy3X8a9b0c1d2e3f4g5h6i7'
# Verify the key hasn't been used recently
key = requests.get(
f'https://api.hookmesh.com/v1/api-keys/{key_id}',
headers={'Authorization': f'Bearer {os.environ["HOOKMESH_API_KEY"]}'}
).json()
if key.get('last_used_at'):
last_used = datetime.fromisoformat(key['last_used_at'].replace('Z', '+00:00'))
days_since_use = (datetime.now(last_used.tzinfo) - last_used).days
if days_since_use < 7:
print(f'Warning: Key was used {days_since_use} days ago')
# Delete the key
response = requests.delete(
f'https://api.hookmesh.com/v1/api-keys/{key_id}',
headers={'Authorization': f'Bearer {os.environ["HOOKMESH_API_KEY"]}'}
)
print('API key deleted')Available Scopes
Limit API key permissions to specific resources and operations for improved security.
| Scope | Description |
|---|---|
| * | Full access to all resources (default) |
| webhooks:write | Create and manage webhook jobs |
| webhooks:read | View webhook jobs and delivery history |
| endpoints:write | Create and manage endpoints |
| endpoints:read | View endpoints and their status |
| applications:write | Create and manage applications |
| applications:read | View applications and statistics |
| event-types:write | Create and manage event types |
| event-types:read | View event types |
| api-keys:write | Create and manage API keys |
| api-keys:read | View API keys |
// Create a read-only monitoring key
const monitoringKey = await fetch('https://api.hookmesh.com/v1/api-keys', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.HOOKMESH_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'Monitoring Dashboard',
environment: 'live',
scopes: [
'webhooks:read',
'endpoints:read',
'applications:read'
]
})
}).then(r => r.json());
// Create a write-only webhook sender key
const senderKey = await fetch('https://api.hookmesh.com/v1/api-keys', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.HOOKMESH_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'Webhook Sender Service',
environment: 'live',
scopes: ['webhooks:write']
})
}).then(r => r.json());Common Patterns
Zero-Downtime Key Rotation
Rotate API keys without service interruption by creating a new key before deleting the old one.
// Step 1: Create new API key
const newKey = await fetch('https://api.hookmesh.com/v1/api-keys', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.HOOKMESH_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'Production Server (v2)',
environment: 'live',
scopes: ['*']
})
}).then(r => r.json());
console.log('New API key created:', newKey.key);
console.log('Update your environment variables with this key');
// Step 2: Deploy new key to production (manual step)
// Update environment variable HOOKMESH_API_KEY=newKey.key
// Deploy and verify all services are using the new key
// Step 3: Wait 24-48 hours to ensure all instances updated
await new Promise(resolve => setTimeout(resolve, 48 * 60 * 60 * 1000));
// Step 4: Delete old API key
const oldKeyId = 'key_old123';
await fetch(`https://api.hookmesh.com/v1/api-keys/${oldKeyId}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${newKey.key}` // Use new key to delete old
}
});
console.log('Old API key deleted');Automatic Key Expiration
Set expiration dates to enforce regular key rotation and reduce security risk.
const expiresAt = new Date();
expiresAt.setDate(expiresAt.getDate() + 90); // 90 days from now
const response = await fetch('https://api.hookmesh.com/v1/api-keys', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.HOOKMESH_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'Temporary Integration Key',
environment: 'test',
scopes: ['*'],
expires_at: expiresAt.toISOString()
})
});
const apiKey = await response.json();
console.log('Key expires on:', apiKey.expires_at);Audit Unused Keys
Regularly check for unused or stale keys and delete them to reduce attack surface.
const response = await fetch(
'https://api.hookmesh.com/v1/api-keys?limit=100',
{
headers: {
'Authorization': `Bearer ${process.env.HOOKMESH_API_KEY}`
}
}
);
const { data: keys } = await response.json();
// Find keys never used or not used in 90+ days
const ninetyDaysAgo = new Date();
ninetyDaysAgo.setDate(ninetyDaysAgo.getDate() - 90);
const staleKeys = keys.filter(key => {
if (!key.last_used_at) return true; // Never used
const lastUsed = new Date(key.last_used_at);
return lastUsed < ninetyDaysAgo; // Not used in 90 days
});
console.log(`Found ${staleKeys.length} stale API keys:`);
staleKeys.forEach(key => {
const lastUsed = key.last_used_at
? new Date(key.last_used_at).toLocaleDateString()
: 'Never';
console.log(`- ${key.name} (last used: ${lastUsed})`);
});
// Optionally delete stale keys
if (process.env.AUTO_DELETE_STALE === 'true') {
for (const key of staleKeys) {
await fetch(`https://api.hookmesh.com/v1/api-keys/${key.id}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${process.env.HOOKMESH_API_KEY}`
}
});
console.log(`Deleted: ${key.name}`);
}
}Best Practices
- ✓Use scoped keys - Limit permissions to only what's needed (e.g., webhooks:write for sending services)
- ✓Rotate keys regularly - Set 90-day expiration or rotate manually every quarter
- ✓Store keys securely - Use environment variables, never hardcode in source code
- ✓Use test keys for development - Create separate
testenvironment keys for local development - ✓Monitor key usage - Check
last_used_atregularly to identify unused keys - ✓Use descriptive names - Name keys by purpose and environment (e.g., "Production Webhook Sender")
- ✓Delete compromised keys immediately - If a key is exposed, delete it and create a new one right away
- ✓Audit keys quarterly - Review all keys every 3 months and delete unused ones