Best Practices

Keep Your Private Key Secret

Never expose your private key in client-side JavaScript, mobile apps, or public repositories. All API calls should be made from a secure server-side environment.

Use Accurate Timestamps

The API rejects requests where X-Timestamp is more than 5 minutes from the server clock. Make sure your server's clock is synchronized using NTP. If you see REQUEST_EXPIRED errors, check your system time.

Respect Rate Limits

Requests are rate-limited per public key (or per IP if no key is provided).
ConditionLimit
With X-Public-Key header60 requests/minute per key
Without X-Public-Key header10 requests/minute per IP
When rate-limited, the API returns 429 Too Many Requests. Implement exponential backoff in your retry logic.

Include the Full Path in Signatures

When generating the signature, the path component must include the full URI with query string and a leading /. For example: /api/v1/events?count=5, not /events?count=5 or api/v1/events.

Handle Errors Gracefully

Always check HTTP status codes before processing response bodies. The API uses standard status codes:
  • 200 — Success
  • 401 — Authentication failure (bad key, bad signature, or expired timestamp)
  • 403 — Account inactive
  • 429 — Rate limited
  • 500 — Server error (retry with backoff)

Cache When Appropriate

For data that doesn't change frequently (e.g., events list), consider caching responses for a reasonable TTL (e.g., 5–15 minutes) to reduce API calls and improve your application's performance.

Example Code

const crypto = require("crypto");

// Helper: create signed headers for any request
function createSignedHeaders(method, path, body = "") {
  const publicKey = process.env.API_PUBLIC_KEY;
  const privateKey = process.env.API_PRIVATE_KEY;
  const timestamp = Math.floor(Date.now() / 1000).toString();

  const signingString = `${timestamp}\n${method}\n${path}\n${body}`;
  const signature = crypto
    .createHmac("sha256", privateKey)
    .update(signingString)
    .digest("hex");

  return {
    "X-Public-Key": publicKey,
    "X-Timestamp": timestamp,
    "X-Signature": signature,
  };
}

// Helper: fetch with retry + backoff
async function apiRequest(method, path, body) {
  const url = `https://backend.localbusiness.pro${path}`;
  const rawBody = body ? JSON.stringify(body) : "";
  const headers = {
    ...createSignedHeaders(method, path, rawBody),
    ...(body ? { "Content-Type": "application/json" } : {}),
  };

  for (let attempt = 0; attempt < 3; attempt++) {
    const res = await fetch(url, { method, headers, body: rawBody || undefined });

    if (res.status === 429 || res.status >= 500) {
      await new Promise((r) => setTimeout(r, 1000 * 2 ** attempt));
      continue;
    }

    if (!res.ok) {
      throw new Error(`API error: ${res.status} ${res.statusText}`);
    }

    return res.json();
  }

  throw new Error("Max retries exceeded");
}

// Usage
const events = await apiRequest("GET", "/api/v1/events?count=10");
console.log(events);