Authentication
All API requests require authentication using an API key. API keys provide secure access to the Pondral AI Visibility platform and can be scoped to specific permissions.
Getting Your API Key
- Log in to your Pondral dashboard
- Open Settings and find the API keys card
- Give the key a descriptive name (e.g. "Production server")
- Select the scopes your application needs
- Click "Create new key"
- Copy and save the key immediately — it is not shown again
Keys are available on the Growth plan and above, and stop working one year after they are created. The listing in Settings shows each key's expiry date.
Making Authenticated Requests
Include your API key in the Authorization header with the Bearer scheme:
curl -X GET https://pondral.com/api/v1/projects \ -H "Authorization: Bearer YOUR_API_KEY"
JavaScript / TypeScript
const response = await fetch(
'https://pondral.com/api/v1/projects',
{
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
}
);
const data = await response.json();API Key Scopes
Scopes limit what an API key can do. Create keys with only the scopes you need for better security.
| Scope | Description |
|---|---|
read | Read-only access to every GET endpoint. Grants nothing that creates, spends or deletes. |
analyze | Run AI visibility analyses |
projects | Create and list projects |
results | Read analysis results |
reports | Access generated reports |
monitoring | Manage monitoring schedules |
webhooks | Create and manage webhooks |
usage | View usage statistics |
Security Best Practices
- Never commit API keys to version control
- Use environment variables to store API keys securely
- Use separate API keys for different environments (dev, staging, prod)
- Rotate API keys regularly
- Revoke keys immediately if they are compromised
- Only request the minimum scopes needed for your use case
- Verify webhook signatures using the provided secret
Rate Limits
API requests are rate-limited based on your subscription tier. Limits are reset every 60 seconds. The remaining requests in the current window are returned in the X-RateLimit-Remaining header.
The API is available on the Growth plan and above. A call made with a key on any other plan returns 403 PLAN_REQUIRED, so the Free and SMB limits that used to be listed here described requests that could never succeed.
Growth
300 req/min
Agency
1,000 req/min
Error Codes & Status Codes
| Code | HTTP Status | Meaning |
|---|---|---|
INVALID_API_KEY | 401 | API key is missing, invalid, or expired |
INSUFFICIENT_SCOPE | 403 | API key does not have permission for this endpoint |
RATE_LIMIT_EXCEEDED | 429 | Too many requests in the time window |
VALIDATION_ERROR | 400 | Invalid request parameters or body |
NOT_FOUND | 404 | The requested resource does not exist |
BILLING_LIMIT_EXCEEDED | 403 | Monthly usage limit reached for this tier |
ENGINE_FAILURE | 502 | AI engine error (try again or use different engine) |
ENGINE_NOT_ALLOWED | 403 | Your plan does not include the requested engine |
BILLING_UNAVAILABLE | 503 | Plan or quota lookup failed (transient, retry) |
INTERNAL_ERROR | 500 | Server error (try again or contact support) |
Webhook Security
Every delivery is signed with HMAC-SHA256 using your webhook secret. The signed material is the X-Webhook-Timestamp header value, a period, then the raw request body — so a captured delivery cannot be replayed later with the signature still verifying. Verify the signature, then reject deliveries whose timestamp is more than five minutes old.
Verifying Signatures (Node.js)
import crypto from 'crypto';
export function verifyWebhookSignature(
payload: string,
timestamp: string,
signature: string,
secret: string
): boolean {
const hmac = crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${payload}`)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(hmac),
Buffer.from(signature)
);
}
// In your webhook handler:
const signature = req.headers['x-webhook-signature'];
const timestamp = req.headers['x-webhook-timestamp'];
const payload = req.body.toString();
if (!verifyWebhookSignature(payload, timestamp, signature, webhookSecret)) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Replay window: reject deliveries older than 5 minutes.
if (Math.abs(Date.now() - Date.parse(timestamp)) > 5 * 60 * 1000) {
return res.status(401).json({ error: 'Stale delivery' });
}
// Process webhook...