LocalBusinessAudit

API reference

Start audits, run batches, search for prospects and read the results from your own systems. API access is part of the Scale plan.

All addresses below start with:

https://localbusinessaudit.com/api/v1

Requests and responses are JSON. Send content-type: application/json with any request that has a body.

Authentication

Create a key on the Integrations page. It is shown once, so store it somewhere safe. Send it with every request:

curl https://localbusinessaudit.com/api/v1/me \
  -H "Authorization: Bearer lba_your_key"

Limits and errors

Requests made with a key count against your plan's monthly allowances exactly as if you had made them on the site: full audits, and prospects audited in batches. GET /me reports what you have used under user.usage.

Some endpoints are also limited by how often they can be called. Each API key has its own count, separate from other keys and from anyone using the site:

EndpointLimit per key
POST /audits120 an hour. A batch audits up to 100 in one request.
POST /me/prospects/search60 an hour
GET /audits/:id/pdf120 an hour

Responses from these endpoints carry x-ratelimit-limit, x-ratelimit-remaining and x-ratelimit-reset (seconds until the count starts again). Over the limit, the answer is 429 with a retry-after header.

An error has a status code and a body with a message written for a person:

{ "error": "You've used all 300 audits on your plan this month. Upgrade your plan to run more.", "upgrade": true }
StatusMeaning
400Something in the request is missing or not valid.
401The key is missing, wrong or revoked, or the plan no longer includes API access.
402A monthly allowance is used up, or the plan does not include this. The body has "upgrade": true.
403Not something a key may do.
404Not found, or not in your workspace.
429Too many requests. Wait and try again.
502, 503A service we depend on did not answer. Try again shortly.

Audits

An audit runs in the background. Start it, then ask for it until its status is completed or failed, or let a webhook tell you.

POST/audits

Starts an audit in your workspace. It counts as one full audit.

curl -X POST https://localbusinessaudit.com/api/v1/audits \
  -H "Authorization: Bearer lba_your_key" \
  -H "content-type: application/json" \
  -d '{
    "business_name": "Riverside Plumbing",
    "website_url": "riversideplumbing.example",
    "city": "Dallas",
    "category": "Plumber"
  }'
  • business_name, website_url and city are required. category is optional and improves the competitor comparison.

Answers 202 with the audit's ID:

{ "id": "8a0a3b7c-1d25-4d58-86b8-e24f4671233f", "status": "queued" }

GET/audits/:id

The audit and, once it has finished, its report. Most audits finish in well under a minute; asking every five seconds is plenty.

{
  "id": "8a0a3b7c-1d25-4d58-86b8-e24f4671233f",
  "status": "completed",
  "business_name": "Riverside Plumbing",
  "website_url": "https://riversideplumbing.example/",
  "city": "Dallas",
  "created_at": "2026-10-07T15:04:05.000Z",
  "error_message": null,
  "report": {
    "overall_score": 74,
    "audited_at": "2026-10-07T15:04:31.000Z",
    "opportunity": { "checks_run": 41, "issues_found": 12, "high_priority": 3, "projected_score_top3": 83 },
    "pillars": [
      {
        "key": "search_visibility", "name": "Search Visibility", "score": 68, "summary": "...",
        "findings": [
          { "id": "review_replies", "status": "warn", "priority": "high",
            "title": "Many of your Google reviews go unanswered", "detail": "...", "recommendation": "..." }
        ]
      }
    ],
    "fix_list": [ { "id": "...", "pillar": "...", "priority": "high", "title": "...", "detail": "...", "recommendation": "..." } ],
    "pages": [ { "url": "https://riversideplumbing.example/contact", "title": "Contact us", "word_count": 240 } ],
    "caveats": [ "This audit covers your homepage and 6 other pages of your site, and what is publicly visible on them." ]
  }
}
  • status is queued, running, completed or failed. A failed audit has the reason in error_message and no report, and does not count against your allowance.
  • There are seven pillars, each scored out of 100. A finding's status is pass, warn or fail.
  • fix_list is every finding that needs attention, most important first.
  • pages lists the pages read besides the homepage, up to six.
  • The report may carry more than is shown here, such as the Google listing and competitors under google. Ignore fields you do not use; new ones may be added.

GET/audits/:id/pdf

The finished report as a PDF file. Add ?client=1 for the version with your own branding, if you have set it up.

GET/me/audits

Your workspace's audits, newest first, up to 1,000.

{
  "audits": [
    { "id": "...", "business_name": "Riverside Plumbing", "website_url": "https://riversideplumbing.example/",
      "city": "Dallas", "status": "completed", "overall_score": 74, "created_at": "2026-10-07T15:04:05.000Z",
      "batch_id": null, "client_id": null, "competitor_of": null }
  ]
}

Batches and leads

A batch audits up to 100 businesses in one request. Each one counts as a prospect, not as a full audit, and they are ranked together as leads.

POST/me/batches

curl -X POST https://localbusinessaudit.com/api/v1/me/batches \
  -H "Authorization: Bearer lba_your_key" \
  -H "content-type: application/json" \
  -d '{
    "name": "Dentists in Austin",
    "rows": [
      { "business_name": "Bright Smile Dental", "website_url": "brightsmile.example", "city": "Austin", "category": "Dentist" },
      { "business_name": "Lakeside Dental", "website_url": "lakesidedental.example", "city": "Austin", "category": "Dentist" }
    ]
  }'

Answers 202. Lines that could not be used are listed, and the rest go ahead:

{ "id": "1f49ea06-cf8a-4499-88f9-c8cb9c78aa55", "created": 2, "skipped": [] }
  • A line is skipped if it lacks a name, a valid website or a city, or repeats an earlier website. Each skipped entry has its line number and a reason.
  • If the batch is larger than the prospects you have left this month, nothing is started and the answer is 402.
  • Pass the search_id from a prospect search (below) to reuse what that search already found about each business.

GET/me/batches/:id

The batch and the state of each audit in it. It has finished when no audit is queued or running.

{
  "id": "1f49ea06-cf8a-4499-88f9-c8cb9c78aa55", "name": "Dentists in Austin", "created_at": "2026-10-07T15:10:00.000Z",
  "audits": [
    { "id": "...", "business_name": "Bright Smile Dental", "status": "completed", "overall_score": 61, "error_message": null }
  ]
}

GET /me/batches lists your batches with total, completed and failed counts for each.

GET/me/leads

Every business you have audited in a batch, ranked by how much there is to fix.

{
  "leads": [
    {
      "audit_id": "...", "domain": "brightsmile.example", "business_name": "Bright Smile Dental",
      "website_url": "https://brightsmile.example/", "city": "Austin", "batch_name": "Dentists in Austin",
      "overall_score": 61, "opportunity_score": 39, "high_priority": 4,
      "top_issue": "Slow mobile loading is likely costing you customers", "weakest_pillar": "Website Performance",
      "phone": "(512) 555-0100", "emails": ["[email protected]"], "contact_page_url": "https://brightsmile.example/contact",
      "rating": 4.6, "review_count": 212, "outreach_status": "not_contacted"
    }
  ]
}
  • opportunity_score runs from 0 to 100; higher means more to fix.
  • phone, emails and contact_page_url are what the business publishes on its Google listing and homepage, and may be empty.

Prospect search

POST/me/prospects/search

Finds businesses of one kind in one city from Google's listings.

curl -X POST https://localbusinessaudit.com/api/v1/me/prospects/search \
  -H "Authorization: Bearer lba_your_key" \
  -H "content-type: application/json" \
  -d '{ "category": "Dentist", "city": "Austin", "limit": 20 }'

limit is 20, 40 or 60.

{
  "category": "Dentist", "city": "Austin", "preview": false,
  "search_id": "5d0c3c5e-7b0e-4f43-9a55-2f0f6d6c1a11",
  "results": [
    { "name": "Bright Smile Dental", "website": "https://brightsmile.example", "address": "100 Main St, Austin, TX 78701",
      "phone": "(512) 555-0100", "rating": 4.6, "review_count": 212, "already_audited": false }
  ]
}
  • website is null for a business with none listed; such a business cannot be audited.
  • To audit the ones you want, send them to POST /me/batches with this search_id, the search's city and category on each row, within a day of the search.

Webhooks

Set an address on the Integrations page and we will send it a POST each time an audit in your workspace completes. The address must be public and start with https://.

{
  "type": "audit.completed",
  "audit": {
    "id": "8a0a3b7c-1d25-4d58-86b8-e24f4671233f",
    "business_name": "Riverside Plumbing",
    "website_url": "https://riversideplumbing.example/",
    "city": "Dallas",
    "overall_score": 74,
    "pillars": [ { "key": "search_visibility", "name": "Search Visibility", "score": 68 } ],
    "high_priority_issues": 3,
    "top_issue": "Slow mobile loading is likely costing you customers",
    "report_url": "https://localbusinessaudit.com/r/8a0a3b7c-1d25-4d58-86b8-e24f4671233f"
  }
}

Checking a delivery came from us

Every delivery has an x-lba-signature header: sha256= followed by the HMAC-SHA256 of the exact request body, keyed with your signing secret (shown on the Integrations page, starting whsec_). Compute the same value and compare:

import { createHmac, timingSafeEqual } from "node:crypto";

// rawBody must be the bytes exactly as received, before any JSON parsing.
function isFromLocalBusinessAudit(rawBody, signatureHeader, signingSecret) {
  const expected = "sha256=" + createHmac("sha256", signingSecret).update(rawBody).digest("hex");
  const a = Buffer.from(expected);
  const b = Buffer.from(signatureHeader ?? "");
  return a.length === b.length && timingSafeEqual(a, b);
}

Help

Questions about the API, or something here that does not match what you see: [email protected].