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/v1Requests 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"- A key acts as a member of your workspace. It can run and read audits, batches, leads and prospect searches.
- It cannot change billing, the team, settings, integrations or other keys. Those need a person signed in to the site.
- A workspace can have up to 10 active keys. Revoke a key on the Integrations page and it stops working at once.
- If the workspace leaves the Scale plan, its keys stop working until it returns.
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:
| Endpoint | Limit per key |
|---|---|
| POST /audits | 120 an hour. A batch audits up to 100 in one request. |
| POST /me/prospects/search | 60 an hour |
| GET /audits/:id/pdf | 120 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 }| Status | Meaning |
|---|---|
| 400 | Something in the request is missing or not valid. |
| 401 | The key is missing, wrong or revoked, or the plan no longer includes API access. |
| 402 | A monthly allowance is used up, or the plan does not include this. The body has "upgrade": true. |
| 403 | Not something a key may do. |
| 404 | Not found, or not in your workspace. |
| 429 | Too many requests. Wait and try again. |
| 502, 503 | A 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_urlandcityare required.categoryis 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." ]
}
}statusisqueued,running,completedorfailed. A failed audit has the reason inerror_messageand no report, and does not count against your allowance.- There are seven pillars, each scored out of 100. A finding's
statusispass,warnorfail. fix_listis every finding that needs attention, most important first.pageslists 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
linenumber and areason. - If the batch is larger than the prospects you have left this month, nothing is started and the answer is
402. - Pass the
search_idfrom 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_scoreruns from 0 to 100; higher means more to fix.phone,emailsandcontact_page_urlare 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 }
]
}websiteisnullfor a business with none listed; such a business cannot be audited.- To audit the ones you want, send them to
POST /me/batcheswith thissearch_id, the search'scityandcategoryon 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"
}
}- The “Send a test event” button sends the same shape with
"type": "test"and a made-up business. - Answer with any
2xxstatus within 8 seconds. Anything else, or no answer, counts as a failed delivery. - A failed delivery is tried again after 1 minute, then 5 minutes, 30 minutes, 2 hours and 6 hours: six attempts over about nine hours. After that it is given up, and we email the workspace's owner (at most once a day); you can still fetch the audit by its ID.
- Every attempt carries the same
x-lba-event-idheader, andx-lba-attemptcounts from 1. If your endpoint handled an event but answered too late, it will arrive again, so use the event ID to ignore repeats. - Each attempt goes to the address set at that moment, so correcting the address fixes deliveries still waiting to be retried.
- Redirects are not followed.
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].