These docs are a work in progress and may not be fully up to date. Some pages may contain internal notes for our team.
Skip to Content

API — AI Location Imports

The AI Location Import feature uses Google Gemini (gemini-2.5-flash-lite) to generate a draft set of locations from guided answers. The workflow is:

  1. POST /ai-location-imports — create a batch and queue the generate job
  2. Poll GET /processes/{id} (or stream GET /processes/{id}/stream) until status = waiting_review
  3. GET /ai-location-imports/{id} — review the draft items
  4. PATCH /ai-location-imports/{batchId}/items/{itemId} — approve, reject, or edit individual items
  5. POST /ai-location-imports/{id}/import — apply approved items as real locations

See also: Processes API, AI Services, Integration Points: Next.js to Backend.

Middleware for all routes: auth:api, auth_session, audit

Batches are scoped to the requesting user’s own workspace. The user must own the workspace.


Batch statuses

StatusMeaning
draftGenerate complete, items pending review
importedAll approvable items have been imported
discardedSoft-deleted via DELETE

Item statuses

StatusMeaning
pending_reviewNot yet acted on
approvedApproved for import
rejectedManually rejected
importedSuccessfully written to the locations table
failedImport attempted but failed validation

GET /ai-location-imports

List recent import batches for the authenticated user. Returns up to 20 batches ordered by updated_at descending. Excludes discarded batches.

Query parameters:

ParamTypeNotes
workspace_idintFilter by workspace

Response 200: Array of batch resource objects (without items).


POST /ai-location-imports

Creates a new batch and dispatches GenerateAiLocationImportJob on the aigen queue. Returns immediately — the caller should watch the returned process ID.

Request body:

FieldTypeRequiredNotes
workspace_idintyesMust be a workspace owned by the user
finder_idintnoIf provided, imported locations are added to the finder’s backing map
answersobjectyesGuided Q&A answers (see below)
answers.brand_namestringnomax 160
answers.business_typestringyesmax 120
answers.approximate_location_countstringyese.g. "10-20", parsed for min/max target
answers.geographic_scopestringyesmax 1000
answers.customer_descriptionstringyesmax 2000
answers.common_tagsstringnomax 1000
answers.important_fieldsstringnomax 1000
answers.example_locationsstringnomax 2000
answers.notes_to_avoidstringnomax 1000
answers.source_notesstringyesmax 4000; primary signal for Gemini

Response 200:

{ "process": { "id": 42, "type": "ai_location_import_generate", "status": "queued" }, "batch": { "id": 7, "status": "draft", "total_items": 0 }, "next_action": "watch_process" }

⚠️ Warning: The generate job runs on the aigen queue. If the queue is not running or Gemini times out (90 s limit), the process transitions to failed. Retry via POST /processes/{id}/retry.


GET /ai-location-imports/{id}

Returns a single batch with all items included.

Response 200: Batch resource with a nested items array.

Item shape:

{ "id": 101, "batch_id": 7, "status": "pending_review", "confidence": "high", "warnings": [], "validation_errors": [], "normalized_payload": { "name": "Example Location", "address": "123 Main St", "city": "Canton", "state": "OH", "zip": "44720", "country": "United States", "phone": "(330) 555-1234", "website": "https://example.com", "lat": 40.798, "lng": -81.376, "tags": ["ice cream"], "custom_fields": {} }, "user_payload": null, "raw_model_output": {} }

Confidence levels:

  • high — 6+ core fields filled, ≤1 warning
  • medium — some fields filled
  • low — validation errors present

PATCH /ai-location-imports/{batchId}/items/{itemId}

Updates the status and/or payload of a single draft item. Recalculates validation errors and syncs batch summary counts.

Request body:

FieldTypeRequiredNotes
statusstringnopending_review, approved, rejected
payloadobjectnoMerged over normalized_payload
payload.namestringnomax 255
payload.addressstringnomax 255
payload.citystringnomax 120
payload.statestringnomax 120
payload.zipstringnomax 40
payload.countrystringnomax 120
payload.phonestringnomax 120
payload.websitestringnomax 255
payload.tagsarray of stringnoEach max 80 chars
payload.custom_fieldsobjectno
payload.latfloatno
payload.lngfloatno

Setting status: approved while validation errors exist returns 422.

Response 200: Updated batch resource with all items.


POST /ai-location-imports/{id}/import

Triggers the apply phase. Any pending_review items that pass validation are auto-approved first. Then dispatches ApplyAiLocationImportJob synchronously on the ai_apply queue.

Returns 422 if there are no valid items to import.

If the batch has a finder_id, approved locations are added to that finder’s backing map. Geocoding (Google Maps Geocode API) is attempted for items missing coordinates; only ROOFTOP or RANGE_INTERPOLATED results are accepted.

Response 200:

{ "process": { "id": 43, "type": "ai_location_import_apply", "status": "completed" }, "batch": { "id": 7, "status": "imported", "imported_items": 9, "failed_items": 1 }, "next_action": "watch_process" }

⚠️ Warning: The apply job uses dispatchSync, running in the web process and blocking the HTTP response. Expect 5–60 s for large batches. The execution budget is extended to 300 s via set_time_limit.


DELETE /ai-location-imports/{id}

Soft-deletes the batch by setting status = discarded. Items remain in the database but the batch is excluded from all future queries.

Response 204: Empty body.


Batch resource shape

{ "id": 7, "workspace_id": 3, "finder_id": null, "user_id": 1, "status": "draft", "model_name": "gemini-2.5-flash-lite", "prompt_version": "v4", "total_items": 10, "approved_items": 3, "rejected_items": 1, "imported_items": 0, "failed_items": 0, "source_answers": {}, "usage_metadata": { "prompt_token_count": 4120, "candidates_token_count": 890, "total_token_count": 5010 }, "created_at": "2026-04-27T09:00:00Z", "updated_at": "2026-04-27T09:00:18Z" }