Best Practices
Get the most out of the Piloterr API with these recommendations for reliability, efficiency, and security.
Follow these guidelines to build integrations that are reliable, cost-efficient, and easy to maintain.
Credit efficiency
Check your balance before large batches
Before triggering a high-volume operation, verify you have enough credits. The Usage endpoint is free and returns your current balance in real time.
curl "https://api.piloterr.com/v2/usage" \
-H "x-api-key: YOUR_API_KEY"Enable Auto Top-Up to automatically refill your balance when it drops below a threshold, so your workflows keep running without interruption.
Avoid redundant calls
Cache results on your side whenever the underlying data is unlikely to change between requests. Re-fetching identical inputs wastes credits.
const cache = new Map<string, unknown>()
async function fetchCached(key: string, fetcher: () => Promise<unknown>) {
if (cache.has(key)) return cache.get(key)
const result = await fetcher()
cache.set(key, result)
return result
}Only call what you need
Each endpoint has a defined credit cost listed on the Credits page. Prefer lighter endpoints when a full response is not required.
Authentication
Store keys in environment variables
Never hardcode your API key in source code. Use environment variables and load them at runtime.
const apiKey = process.env.PILOTERR_API_KEY
if (!apiKey) throw new Error("API key is not set")import os
api_key = os.environ["PILOTERR_API_KEY"]$apiKey = getenv('PILOTERR_API_KEY');apiKey := os.Getenv("PILOTERR_API_KEY")Use separate keys per environment
Create distinct API keys for development, staging, and production in your dashboard. This lets you revoke or rotate a compromised key without affecting other environments.
| Environment | Recommended category |
|---|---|
| Local dev | development |
| CI / staging | development |
| Production | production |
Rotate keys periodically
Treat API keys like passwords. Set an expiry date on sensitive keys and rotate them on a regular schedule from the API Keys section of your account settings.
Never expose your API key in client-side code, browser requests, or public repositories. Always proxy calls through your own backend.
Error handling
Always check the HTTP status code
Do not assume a request succeeded. Every response should be checked against its HTTP status code before processing the body. See the Error Handling guide for a full breakdown.
Retry on transient errors
Server errors (500) and network timeouts are transient. Implement exponential backoff to retry automatically:
async function fetchWithRetry(url: string, options: RequestInit, maxRetries = 3) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
const res = await fetch(url, options)
if (res.status === 500 && attempt < maxRetries) {
await new Promise(r => setTimeout(r, 500 * 2 ** attempt))
continue
}
return res
} catch {
if (attempt === maxRetries) throw new Error("Max retries reached")
await new Promise(r => setTimeout(r, 500 * 2 ** attempt))
}
}
}Never retry on 402 without topping up
A 402 Payment Required response means your balance is empty. Retrying the same call immediately will not help. Add credits from your dashboard or enable Auto Top-Up.
Rate limits
Spread requests over time
If you need to send a large number of calls, distribute them across time rather than firing them all at once. A simple delay between iterations prevents hitting rate limits.
const DELAY_MS = 100 // adjust based on your plan
for (const item of items) {
await processItem(item)
await new Promise(r => setTimeout(r, DELAY_MS))
}Monitor the 401 Rate Limit Exceeded response
When rate-limited, the API returns 401 with status Rate Limit Exceeded. Back off and wait before retrying. Consider upgrading your plan if you consistently hit limits.
Security
Never make API calls from the browser
Your API key would be visible to anyone who inspects the network tab. Always route calls through your own server.
Browser → Your backend → Piloterr APIValidate and sanitize inputs
If your application accepts user-supplied parameters that are passed to the API (URLs, search queries, etc.), sanitize them to prevent injection or unexpected behavior.
Monitoring
Track credit consumption
Query the Usage endpoint on a schedule (e.g. daily) and alert when consumption exceeds a threshold. This prevents surprises at the end of a billing period.
| Metric | Why it matters |
|---|---|
remaining | Warns you before credits run out |
subscription.percent_used | Tracks your plan utilization |
credits.remaining | Monitors bonus credit balance |
Log request context
Store the HTTP status code and a timestamp for every API call. This makes it easy to audit credit consumption and debug failures after the fact.
async function apiCall(endpoint: string, params: Record<string, string>) {
const url = new URL(`https://api.piloterr.com${endpoint}`)
Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v))
const res = await fetch(url.toString(), {
headers: { "x-api-key": process.env.API_KEY! },
})
console.log(JSON.stringify({
endpoint,
status: res.status,
billed: res.status === 200 || res.status === 201,
timestamp: new Date().toISOString(),
}))
return res
}Checklist
Use this checklist before going to production:
- API key stored in an environment variable, not in source code
- Separate keys for development and production
- HTTP status codes checked on every response
- Retry logic with exponential backoff for
500errors - No retries on
402without adding credits first - Rate limit handling in place
- Credit balance monitored and alerts configured
- Auto Top-Up enabled (or a manual top-up process defined)
- All API calls proxied through your backend
See also: Error Handling for a complete list of status codes and Glossary for term definitions.