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: live or test
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
Example API Key Object
{
  "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-keys

Parameters

FieldTypeDescription
namestringrequiredHuman-readable name for the key
environmentstringlive or test (default: live)
scopesarrayPermissions (default: ["*"] for full access)
expires_atstringOptional expiration date (ISO 8601)
Node.js
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!');
Python
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!')
Go
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
<?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-keys

Query Parameters

ParameterTypeDescription
limitintegerNumber of results per page (default: 20, max: 100)
cursorstringPagination cursor from previous response
environmentstringFilter by environment (live or test)
Node.js
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`);
}
Python
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')
Go
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
<?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/:id
Node.js
const 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/:id
Node.js - Update Scopes
const 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);
Python - Set Expiration
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/:id

Immediate 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.

Node.js
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');
Python
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.

ScopeDescription
*Full access to all resources (default)
webhooks:writeCreate and manage webhook jobs
webhooks:readView webhook jobs and delivery history
endpoints:writeCreate and manage endpoints
endpoints:readView endpoints and their status
applications:writeCreate and manage applications
applications:readView applications and statistics
event-types:writeCreate and manage event types
event-types:readView event types
api-keys:writeCreate and manage API keys
api-keys:readView API keys
Node.js - Create Scoped Key
// 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.

Node.js - Key Rotation
// 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.

Node.js - 90-Day Expiration
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.

Node.js - Audit Script
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 test environment keys for local development
  • Monitor key usage - Check last_used_at regularly 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

Related Documentation