Skip to content

Hadar API Documentation

Integrate your systems with Hadar using our REST API. Manage leads, properties, and appointments programmatically.

Authentication

All API endpoints require an API key passed in the X-API-Key header. Create and manage API keys from Settings → API Keys.

Example request with API key

curl -X GET "https://crm.hadar-ai.com/api/v1/leads" \
  -H "X-API-Key: ak_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxx"
Security: API keys are scoped to your tenant. Never expose keys in client-side code or public repositories. Rotate keys regularly from the settings page.

Missing or invalid key returns 401

{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Missing or invalid API key"
  }
}

Base URL

https://crm.hadar-ai.com/api/v1

All endpoints below are relative to this base URL. Responses are JSON-encoded with Content-Type: application/json.

Response Format

All responses follow a consistent envelope. Successful responses contain a data field. List endpoints include a meta field for pagination.

Successful list response

{
  "data": [ ... ],
  "meta": {
    "total": 1250,
    "page": 1,
    "limit": 20,
    "pages": 63,
    "has_next": true,
    "has_prev": false
  }
}

Successful single-resource response

{
  "data": {
    "id": "lead-abc123",
    "full_name": "Ahmed Al Mansouri",
    "email": "ahmed@example.com",
    "status": "new",
    "created_at": "2026-04-03T10:30:00Z"
  }
}

Error Responses

Errors return a structured error object with a machine-readable code and a human-readable message.

Validation error (400)

{
  "error": {
    "code": "VALIDATION_FAILED",
    "message": "Validation failed"
  },
  "details": {
    "fieldErrors": {
      "email": ["Invalid email"]
    }
  }
}
StatusCodeDescription
400VALIDATION_FAILEDInvalid request body or query parameters
401UNAUTHORIZEDMissing or invalid X-API-Key header
403FORBIDDENAPI key lacks permission for this action
404NOT_FOUNDResource does not exist
429RATE_LIMITEDToo many requests. Check Retry-After header.
500INTERNAL_ERRORServer-side error. Retry or contact support.

Resources

Leads

GETPOST

Create and retrieve leads. Filter by status, score, and channel. Supports pagination.

/api/v1/leads

Properties

GETPOST

List and create property listings. Filter by type, status, price range, bedrooms, and community.

/api/v1/properties

Appointments

GETPOST

List and schedule appointments including viewings, calls, meetings, and follow-ups.

/api/v1/appointments

Webhooks

GET

Manage webhook subscriptions for real-time event notifications.

/api/v1/webhooks

Practical Examples

GET

List leads with filters

Request

curl -X GET "https://crm.hadar-ai.com/api/v1/leads?page=1&limit=10&status=qualified" \
  -H "X-API-Key: ak_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Response (200 OK)

{
  "data": [
    {
      "id": "lead-abc123",
      "full_name": "Ahmed Al Mansouri",
      "email": "ahmed@example.com",
      "phone": "+971501234567",
      "nationality": "AE",
      "status": "qualified",
      "score": 85,
      "budget_min_aed": 500000,
      "budget_max_aed": 2000000,
      "purpose": "Investment",
      "timeline": "3 months",
      "channel": "website",
      "assigned_to": "agent-456",
      "created_at": "2026-02-22T10:30:00Z"
    }
  ],
  "meta": {
    "total": 342,
    "page": 1,
    "limit": 10,
    "pages": 35,
    "has_next": true,
    "has_prev": false
  }
}

Filter parameters: status, channel, search. Pagination via page + limit (max 100).

POST

Create a new lead

Request

curl -X POST "https://crm.hadar-ai.com/api/v1/leads" \
  -H "X-API-Key: ak_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "full_name": "Ahmed Al Mansouri",
    "email": "ahmed@example.com",
    "phone": "+971501234567",
    "nationality": "AE",
    "budget_min_aed": 500000,
    "budget_max_aed": 2000000,
    "purpose": "Investment",
    "timeline": "3 months",
    "notes": "Interested in Downtown villas",
    "channel": "api"
  }'

Response (201 Created)

{
  "data": {
    "id": "lead-xyz789",
    "full_name": "Ahmed Al Mansouri",
    "email": "ahmed@example.com",
    "phone": "+971501234567",
    "nationality": "AE",
    "status": "new",
    "score": 0,
    "budget_min_aed": 500000,
    "budget_max_aed": 2000000,
    "purpose": "Investment",
    "timeline": "3 months",
    "channel": "api",
    "created_at": "2026-04-03T14:22:00Z"
  }
}

At least one identifier (email or phone) is required. The lead is automatically assigned a new status and a score of 0.

JavaScript / Node.js

Fetch wrapper for API calls

const HADAR_API_KEY = process.env.HADAR_API_KEY;
const BASE_URL = "https://crm.hadar-ai.com/api/v1";

async function hadarApi(path, options = {}) {
  const response = await fetch(`${BASE_URL}${path}`, {
    ...options,
    headers: {
      "X-API-Key": HADAR_API_KEY,
      "Content-Type": "application/json",
      ...options.headers,
    },
  });

  const json = await response.json();

  if (!response.ok) {
    throw new Error(json.error?.message || `API error: ${response.status}`);
  }

  return json;
}

// Usage
const leads = await hadarApi("/leads?status=qualified&limit=50");
console.log(leads.data);        // Lead[]
console.log(leads.meta.total);  // Total count

const newLead = await hadarApi("/leads", {
  method: "POST",
  body: JSON.stringify({
    full_name: "Ahmed Al Mansouri",
    email: "ahmed@example.com",
    phone: "+971501234567",
    channel: "api",
  }),
});
console.log(newLead.data.id);   // "lead-xyz789"

Python

Using requests library

import os
import requests

API_KEY = os.environ["HADAR_API_KEY"]
BASE_URL = "https://crm.hadar-ai.com/api/v1"
HEADERS = {"X-API-Key": API_KEY}

# List qualified leads
response = requests.get(
    f"{BASE_URL}/leads",
    headers=HEADERS,
    params={"status": "qualified", "limit": 50},
)
response.raise_for_status()
leads = response.json()
print(f"Found {leads['meta']['total']} qualified leads")

# Create a lead
new_lead = requests.post(
    f"{BASE_URL}/leads",
    headers={**HEADERS, "Content-Type": "application/json"},
    json={
        "full_name": "Ahmed Al Mansouri",
        "email": "ahmed@example.com",
        "phone": "+971501234567",
        "channel": "api",
    },
)
new_lead.raise_for_status()
print(f"Created lead: {new_lead.json()['data']['id']}")

Webhooks

Configure webhooks to receive real-time notifications when events occur in your CRM. Set up webhook endpoints in Dashboard → Developers → Webhooks.

Available Events

lead.created
lead.updated
property.created
property.updated
appointment.scheduled
appointment.completed

Webhook payload (POST to your endpoint)

{
  "event": "lead.created",
  "timestamp": "2026-04-03T14:22:00Z",
  "data": {
    "id": "lead-xyz789",
    "full_name": "Ahmed Al Mansouri",
    "email": "ahmed@example.com",
    "phone": "+971501234567",
    "status": "new",
    "channel": "website",
    "created_at": "2026-04-03T14:22:00Z"
  }
}

Verifying webhook signatures (Node.js)

import crypto from "crypto";

function verifyWebhookSignature(payload, signature, secret) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(payload, "utf-8")
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

// In your webhook handler
app.post("/webhooks/hadar", (req, res) => {
  const signature = req.headers["x-webhook-signature"];
  const isValid = verifyWebhookSignature(
    JSON.stringify(req.body),
    signature,
    process.env.HADAR_WEBHOOK_SECRET
  );

  if (!isValid) {
    return res.status(401).json({ error: "Invalid signature" });
  }

  const { event, data } = req.body;

  switch (event) {
    case "lead.created":
      console.log("New lead:", data.full_name);
      break;
    case "appointment.scheduled":
      console.log("New appointment:", data.title);
      break;
  }

  res.status(200).json({ received: true });
});
Tip: Your endpoint must respond with a 2xx status within 30 seconds. Failed deliveries are retried up to 3 times with exponential backoff.

Rate Limiting

TierRequests / minDescription
Standard60Default tier for all API keys
Professional300For high-volume integrations
Enterprise1000Custom limits available

Rate limit headers are included in every response: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset. When rate-limited, wait until the Retry-After time before retrying.

Need Help?

For integration support or questions about the API: