API documentation
A small REST API: start a scrape, poll it, read the rows. Everything the dashboard does, your code can do. Available on every plan, including the free 100 credits — no separate API tier, no sales call.
Authentication
Generate a key in Settings. It starts with hub_live_ and is shown once — we only store a hash, so if you lose it you rotate rather than recover it. Pass it as a Bearer token on every request.
curl https://hubertino.com/api/v1/scrapes \
-H "Authorization: Bearer hub_live_your_key_here"Base URL: https://hubertino.com/api/v1. All request and response bodies are JSON. Missing or invalid keys return 401.
Credits & billing
One credit is one business delivered. When a scrape starts we reserve its worst case (categories × locations × maxResults, capped at your balance); when it finishes you're charged only for rows actually delivered and the rest is refunded automatically. A search that finds nothing costs nothing.
The reservation is why a job can report creditsReserved much larger than creditsCharged— that's expected, not a bug.
Quickstart
Start a scrape, poll until it's done, then read the rows.
# 1. start
ID=$(curl -s https://hubertino.com/api/v1/scrapes \
-H "Authorization: Bearer $HUBERTINO_KEY" \
-H "content-type: application/json" \
-d '{"categories":["dentist"],"locations":["Austin, TX"],"maxResults":50}' \
| jq -r .scrape.id)
# 2. poll — status goes queued -> running -> done
curl -s "https://hubertino.com/api/v1/scrapes/$ID" \
-H "Authorization: Bearer $HUBERTINO_KEY" | jq .scrape.status
# 3. read rows, a page at a time
curl -s "https://hubertino.com/api/v1/scrapes/$ID/results?limit=1000&offset=0" \
-H "Authorization: Bearer $HUBERTINO_KEY" | jq '.count, .hasMore'Results are readable while the job is still running — rows stream in as they're found, and partial tells you whether more are coming.
Start a scrape
| Field | Type | Notes |
|---|---|---|
| categories | string[] | Required. What to search for — “dentist”, “HVAC contractor”. Up to 1,000. |
| locations | string[] | Required. Cities, regions or zip codes. Up to 10,000. |
| maxResults | integer | Required. Per category × location pair, 1–500. |
| country | string | Optional ISO code picking the Google edition. Inferred from the locations when omitted. |
| enrichWebsite | boolean | Default true. Visits each business's site for an email. No extra credit cost. |
| enrichLinks | boolean | Default true. Collects social profiles and ordering links. |
One scrape runs up to 2,000 searches (categories × locations) — split anything larger into several scrapes. Returns 201 with the created scrape.
{
"scrape": {
"id": "6f1c…", "kind": "scrape", "status": "queued",
"categories": ["dentist"], "locations": ["Austin, TX"],
"maxResults": 50, "creditsReserved": 50, "creditsCharged": 0,
"resultCount": 0, "createdAt": "2026-07-22T18:04:11.000Z"
}
}List scrapes
Your 200 most recent scrapes, newest first, as { "scrapes": [...] }.
Get one scrape
Returns the scrape with live status. status is one of queued, running, done, error or cancelled. Poll every few seconds; there are no webhooks yet.
Fetch results
Paginated. limit defaults to 1,000 and caps at 5,000; offset defaults to 0. Page until hasMore is false.
{
"status": "done",
"partial": false,
"count": 4820, // total available, not this page
"offset": 0,
"limit": 1000,
"hasMore": true,
"columns": ["name", "phone", "email", "website", …],
"rows": [ { "name": "…", "phone": "…", "email": "…" }, … ]
}Each row carries a _status of found → extracted → enriched, so you can tell a fully-detailed row from one still being filled in. When a job is capped at your credit allowance, detailed rows are delivered first.
Export XLSX or CSV
Returns the file itself, not JSON — format is xlsx (default) or csv. Only available once the scrape is done; earlier calls return 409.
curl -L "https://hubertino.com/api/v1/scrapes/$ID/export?format=csv" \
-H "Authorization: Bearer $HUBERTINO_KEY" -o leads.csvRate limits
Creating scrapes is limited to 12 per 5 minutes per account; exceeding it returns 429. Reads (status, results, export) aren't rate limited, but poll at a sensible interval — a few seconds is plenty, since scrapes take minutes.
Errors
Errors are JSON with an error string and a meaningful status code.
| 400 | Invalid body or query parameter — the message says which field. |
| 401 | Missing or invalid API key. |
| 402 | Not enough credits to reserve the scrape. |
| 404 | No such scrape under your account. |
| 409 | Not ready yet — e.g. exporting a scrape that hasn't finished. |
| 410 | Results are no longer stored for this scrape. |
| 429 | Rate limit exceeded. |
| 503 | Results temporarily unavailable — retry. |
Get an API key
Start free with 100 credits — no card. The API is on every plan.
Start free with 100 credits