API Guide

Best Practices

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.

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.

Node.js

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);

Python

import hmac, hashlib, time, os, requests
from functools import wraps

def create_signed_headers(method: str, path: str, body: str = "") -> dict:
    """Create HMAC-signed headers for an API request."""
    public_key = os.environ["API_PUBLIC_KEY"]
    private_key = os.environ["API_PRIVATE_KEY"]
    timestamp = str(int(time.time()))

    signing_string = f"{timestamp}\n{method}\n{path}\n{body}"
    signature = hmac.new(
        private_key.encode(), signing_string.encode(), hashlib.sha256
    ).hexdigest()

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


def api_request(method: str, path: str, body: dict | None = None):
    """Make an API request with retry + backoff."""
    url = f"https://backend.localbusiness.pro{path}"
    raw_body = "" if body is None else __import__("json").dumps(body)
    headers = create_signed_headers(method, path, raw_body)
    if body:
        headers["Content-Type"] = "application/json"

    for attempt in range(3):
        resp = requests.request(method, url, headers=headers, data=raw_body or None)

        if resp.status_code in (429, 500, 502, 503):
            time.sleep(2 ** attempt)
            continue

        resp.raise_for_status()
        return resp.json()

    raise Exception("Max retries exceeded")


# Usage
events = api_request("GET", "/api/v1/events?count=10")
print(events)

PHP

<?php
function createSignedHeaders(string $method, string $path, string $body = ''): array {
    $publicKey = getenv('API_PUBLIC_KEY');
    $privateKey = getenv('API_PRIVATE_KEY');
    $timestamp = time();

    $signingString = $timestamp . "\n" . $method . "\n" . $path . "\n" . $body;
    $signature = hash_hmac('sha256', $signingString, $privateKey);

    return [
        "X-Public-Key: $publicKey",
        "X-Timestamp: $timestamp",
        "X-Signature: $signature",
    ];
}

function apiRequest(string $method, string $path, ?array $body = null): array {
    $url = 'https://backend.localbusiness.pro' . $path;
    $rawBody = $body ? json_encode($body) : '';
    $headers = createSignedHeaders($method, $path, $rawBody);

    if ($body) {
        $headers[] = 'Content-Type: application/json';
    }

    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);

    if ($rawBody) {
        curl_setopt($ch, CURLOPT_POSTFIELDS, $rawBody);
    }

    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($httpCode >= 400) {
        throw new Exception("API error: $httpCode");
    }

    return json_decode($response, true);
}

// Usage
$events = apiRequest('GET', '/api/v1/events?count=10');
print_r($events);

cURL

#!/bin/bash
# Reusable helper function for signed API requests

api_request() {
  local METHOD="$1"
  local PATH_URI="$2"
  local BODY="${3:-}"

  TIMESTAMP=$(date +%s)
  SIGNATURE=$(printf '%s
%s
%s
%s' "$TIMESTAMP" "$METHOD" "$PATH_URI" "$BODY" \
    | openssl dgst -sha256 -hmac "$API_PRIVATE_KEY" | awk '{print $2}')

  curl -s -X "$METHOD" \
    -H "X-Public-Key: $API_PUBLIC_KEY" \
    -H "X-Timestamp: $TIMESTAMP" \
    -H "X-Signature: $SIGNATURE" \
    "https://backend.localbusiness.pro$PATH_URI"
}

# Usage
export API_PUBLIC_KEY="your-public-key"
export API_PRIVATE_KEY="your-private-key"

api_request GET /api/v1/me
api_request GET "/api/v1/events?count=10"