Skip to main content
All HoopAI API requests follow REST conventions. This guide covers the patterns you’ll use in every integration.

Base URL

https://services.leadconnectorhq.com
The base URL uses the LeadConnector domain. This is the API infrastructure that powers HoopAI — all data belongs to your HoopAI account.

Request structure

Every request needs the Authorization and Version headers:
curl -X GET "https://services.leadconnectorhq.com/{endpoint}" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Version: 2021-07-28" \
  -H "Content-Type: application/json"

Common parameters

ParameterDescriptionUsed in
locationIdAccount ID (required for most endpoints)Query string or body
limitNumber of results per page (default varies by endpoint)Query string
offset / pagePagination cursorQuery string

Pagination

Most list endpoints return paginated results. The response includes metadata about total results and how to fetch the next page.
{
  "contacts": [...],
  "meta": {
    "total": 342,
    "currentPage": 1,
    "nextPage": 2,
    "startAfter": 20,
    "startAfterId": "abc123"
  }
}

Offset-based pagination

# Page 1
GET /contacts/?locationId=LOC_ID&limit=20

# Page 2
GET /contacts/?locationId=LOC_ID&limit=20&startAfter=20

Cursor-based pagination

Some endpoints use cursor-based pagination with startAfterId:
# First page
GET /contacts/?locationId=LOC_ID&limit=20

# Next page (use the last item's ID)
GET /contacts/?locationId=LOC_ID&limit=20&startAfterId=abc123
Always check the meta object in the response for pagination details. If nextPage is null, you’ve reached the last page.

Filtering and searching

Many list endpoints support filtering via query parameters or a search body:
# Filter contacts by tag
GET /contacts/?locationId=LOC_ID&query=tag:lead

# Search contacts (POST)
POST /contacts/search
{
  "locationId": "LOC_ID",
  "filters": [
    { "field": "tags", "operator": "contains", "value": "lead" }
  ],
  "page": 1,
  "pageLimit": 20
}

Creating and updating resources

Use POST to create and PUT to update. Always send JSON bodies with the Content-Type: application/json header.
# Create a contact
curl -X POST "https://services.leadconnectorhq.com/contacts/" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Version: 2021-07-28" \
  -H "Content-Type: application/json" \
  -d '{
    "locationId": "LOC_ID",
    "firstName": "Jane",
    "lastName": "Smith",
    "email": "jane@example.com",
    "phone": "+15551234567",
    "tags": ["lead", "website"]
  }'

Error handling

All errors return a consistent JSON structure:
{
  "statusCode": 422,
  "message": "Validation failed",
  "errors": [
    { "field": "email", "message": "Invalid email format" }
  ]
}
StatusMeaningWhat to do
400Bad requestCheck request body and parameters
401UnauthorizedToken missing, expired, or invalid — refresh it
403ForbiddenToken lacks required scope
404Not foundResource doesn’t exist or wrong locationId
422Validation errorCheck the errors array for field-level details
429Rate limitedWait and retry with exponential backoff
500Server errorRetry after a short delay

Rate limits

ScopeLimit
Per account100 requests / 10 seconds
Token refresh5 requests / minute
Rate limit headers are included in every response:
HeaderDescription
X-RateLimit-RemainingRequests remaining in the current window
X-RateLimit-ResetUnix timestamp when the limit resets
When you receive a 429 response, use exponential backoff:
async function fetchWithRetry(url, options, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    const response = await fetch(url, options);
    if (response.status !== 429) return response;

    const resetAt = response.headers.get('X-RateLimit-Reset');
    const waitMs = resetAt
      ? (parseInt(resetAt) * 1000) - Date.now()
      : Math.pow(2, i) * 1000;

    await new Promise(resolve => setTimeout(resolve, Math.max(waitMs, 1000)));
  }
  throw new Error('Max retries exceeded');
}

Next steps

Last modified on March 5, 2026