Bulk import accounts + locations (CSV upload)
Overview
Bulk-imports accounts from a single CSV — one account + one location (store) per row — designed for up to ~100K rows. The endpoint validates the upload for shape/size, then stores and queues it. Processing runs asynchronously on the bulk queue (prepare, then parallel chunk jobs, then finalize). It returns an import_id immediately — poll Bulk import status for progress.
Per created row the system provisions the full cascade. This covers the account company (bundle_id=3), company_plans, company_options, owner user + person, location-account company (bundle_id=2), store, store_options, store_plans, and store_review_sites (one per the plan's review-site set). The endpoint suppresses welcome/notification emails for bulk imports. It creates every owner with a temporary password, and each owner must set their own at first login.
Prerequisites
- A bearer token in the
Authorizationheader.
Base URL
| Environment | URL |
|---|---|
| Production | https://production-api.shoutaboutus.com |
| Development | https://development-api.shoutaboutus.com |
Endpoint
POST /api/v1/partner/account/bulk-import
Authentication
-
Requires a bearer token in the
Authorization: Bearer <bearer-token>header. -
Partner — authentication. Caller must be a partner company (company
bundle_id= 1). The endpoint scopes the import to that partner. Non-partner callers get 403.
Rate limit
- No rate limit (bulk import — the
bulkworker controls back-pressure).
Request body
| Field | Type | Required | Description |
|---|---|---|---|
file | file (.csv) | Required | CSV only, max 100 MB. Header row must contain every required column below. The endpoint rejects the xlsx (streamed row-by-row for memory safety). |
CSV columns — one account+location per row
| Column | Required | Description |
|---|---|---|
account_name | Required | Account name (max 100, no <>). |
client_account_id | Required | Partner-unique external account id (^[A-Za-z0-9_-]+$, max 100). Duplicate (in-file or existing) → skipped duplicate_client_account_id. Also the re-run idempotency key. |
plan_id | Required | Numeric plan id assigned to the partner (company_plans). Unassigned → skipped plan_unavailable. |
owner_first_name | Required | Account owner first name. |
owner_email | Required | Globally unique on users. Duplicate → skipped duplicate_email. |
location_name | Required | Location/store name (max 100, no <>). |
client_location_id | Required | Partner-unique external location id (same charset). Duplicate → skipped duplicate_client_location_id. |
address | Required | max 255, no <>. |
country | Required | Country code or name; must already exist (unknown falls back to skipped unknown_country). Countries are never auto-created. |
state | Required | State code or name; created under the country when missing. |
city | Required | City name; created under (country, state) when missing. |
postal_code | Required | 3–12 chars. |
website | Optional | URL — account website. |
billing_id | Optional | max 100. |
owner_last_name | Optional | Account owner last name. |
owner_phone | Optional | 9–15 digits, no spaces/symbols. |
storeid | Optional | Store label; defaults to the resolved city name when blank. |
business_phone | Optional | 9–15 digits. |
site_url | Optional | URL — location website. |
companyindustry_id | Optional | Industry id; blank, then defaults to Home Services. Non-existent id, then skipped invalid_row. A partner-level industry still overrides the per-row value when set. |
How it works
- 202 Accepted →
{ data: { import_id, status: "queued" } }— the response returns immediately with animport_id. The import itself runs in the background, so poll the status endpoint rather than treating 202 as "import finished". - Validation, de-dupe (in-file + against the partner's existing accounts/locations) and geo resolve/create all happen in the single-threaded prepare stage. The endpoint records invalid/duplicate rows and skipped while the rest continue (skip-and-report).
- Safe to re-run: re-uploading the same file only creates rows whose
client_account_id/client_location_iddon't already exist for the partner. - 403 if the caller is not a partner; 422 if the file is missing, not a
.csv, or larger than 100 MB.
Response
202 Queued · 202
202{
"data": {
"import_id": 2,
"status": "queued",
"message": "Import received. Processing has been queued."
}
}403 Not a partner · 403
403{
"message": "Only a partner account may run a bulk import.",
"status": "error",
"errors": []
}422 Invalid file · 422
422{
"message": "The import file must be a .csv file.",
"errors": {
"file": [
"The import file must be a .csv file."
]
}
}Errors
| Status | Meaning |
|---|---|
401 | The bearer token is missing, expired or invalid |
403 | Not a partner |
422 | Invalid file |
500 | Unexpected server error |
Example request
curl --request POST \
--url "https://production-api.shoutaboutus.com/api/v1/partner/account/bulk-import" \
--header 'Authorization: Bearer <bearer-token>' \
--header 'Accept: application/json' \
--form 'file=@account_bulk_import_sample.csv'Updated 9 days ago
