Common Details
Base URLs
Starboard gives you the API host for your account. A key works only with the host it was issued for. The examples in these docs write the host as https://<your-starboard-api-host>.
API Versions
Each part of the API lives under one version prefix:
| API | Prefix | Example |
|---|---|---|
| Quotes | /api/v3 | GET /api/v3/quotes |
| CRM (companies and contacts) | /api/v2 | GET /api/v2/companies |
| Test Auth | /api/v2 | POST /api/v2/test/auth |
CRM and Test Auth exist only under /api/v2. The /api/v2/quotes endpoints are legacy: they read an older quote model, return a different shape, and are not for new integrations. Use /api/v3/quotes.
Required HTTP Headers
API requests take the following headers:
| Header | Description | Required |
|---|---|---|
Authorization | Authentication credentials (see Security) | Yes |
Content-Type | Must be set to application/json | On requests with a body (POST, PUT). Optional on others, but when sent it must be application/json. |
X-User-Company-ID | Your company ID (provided by Starboard) | Yes |
The X-User-Company-ID header must be set to the company ID value provided by Starboard, exactly as given (the match is case-sensitive). Any other value returns 401 Unauthorized. See Security.
Example Request
curl -X GET https://<your-starboard-api-host>/api/v3/quotes \
-H "Authorization: Basic {base64-encoded-credentials}" \
-H "Content-Type: application/json" \
-H "X-User-Company-ID: {your-company-id}"
Payload Format
All API requests must be sent in JSON format.
All API responses are returned in JSON format.
Error Handling
Standard HTTP status codes are used to indicate the success or failure of requests:
- 2xx: Success
- 4xx: Client errors (bad request, authentication issues, etc.)
- 5xx: Server errors
Every error the API itself returns has the same JSON body: a short error code to branch on, and a message for a person.
{
"error": "bad_request",
"message": "Missing X-User-Company-ID header"
}
Errors from the infrastructure in front of the API, such as 429, 502, 504 and some 503s, may not have a JSON body. Treat them like 503: retry with backoff (see Calling the API).
Credentials and X-User-Company-ID are checked before anything else, so on any /api/v2 or /api/v3 path an authentication error comes first.
Error Codes
error | Status | Meaning |
|---|---|---|
unauthorized | 401 | The Authorization header is missing, malformed or holds invalid credentials, or X-User-Company-ID is a company this key can't act for. |
bad_request | 400 | The request is malformed: X-User-Company-ID is missing, Content-Type is set to something other than application/json, a parameter or the body is invalid. The message says which. |
invalid_unlocode | 400 | address_unlocode on Create Company or Update Company isn't 2 uppercase letters followed by 3 uppercase letters or digits (for example USNYC). |
not_found | 404 | The resource doesn't exist for your company. |
integration_disabled | 403 | Record Push Status named an integration that isn't enabled for your company. |
method_not_allowed | 405 | The HTTP method isn't one of GET, POST, PUT, PATCH or DELETE (for example HEAD or OPTIONS). |
unavailable | 503 | Authentication is temporarily unavailable, so your credentials couldn't be checked. Retry with backoff. |
internal_server_error | 500 | The request failed in Starboard. Some validation failures also come back this way; see CRM errors. |
error | 404, 405 or 500 | The path doesn't exist ("Cannot GET /api/v3/…"), the path exists but not for this method ("Method Not Allowed"), or an unexpected failure. Here the code is the word error itself. |
The two kinds of 405 have different bodies. A method other than GET, POST, PUT, PATCH or DELETE:
{
"error": "method_not_allowed",
"message": "Method not allowed"
}
A supported method on a path that doesn't accept it (for example, DELETE /api/v3/quotes):
{
"error": "error",
"message": "Method Not Allowed"
}
Update Quote Customer also returns its own codes (409, 422 and 503), listed with that operation.
Response Codes
| Code | Description |
|---|---|
| 200 OK | Request successful |
| 204 No Content | Delete successful (empty body) |
| 400 Bad Request | Malformed request, missing X-User-Company-ID, or invalid parameter or body |
| 401 Unauthorized | Missing or invalid credentials, or an X-User-Company-ID the key may not act for |
| 403 Forbidden | Only integration_disabled, from Record Push Status |
| 404 Not Found | Resource or path not found |
| 405 Method Not Allowed | The method isn't supported on that path |
| 409 Conflict, 422 Unprocessable Entity | Refusals from Update Quote Customer |
| 500 Internal Server Error | Server error, and some validation failures (see CRM errors) |
| 503 Service Unavailable | unavailable: authentication is temporarily unavailable; retry with backoff. Only a 401 means the credentials or company header are wrong. Update Quote Customer also returns 503 match_unavailable. |
Pagination
List endpoints return one page at a time, together with a cursor for the next page:
{
"data": [ ... ],
"cursor": "eyJvIjoyMH0"
}
- Resend everything with the cursor. The cursor marks only a position in the results. To get the next page, send the same request again, with the same filters and the same
limit, and addcursor. A request with onlycursorloses your filters. - Stop on an empty cursor. The last page returns
"cursor": "". The key is always present, so check for the empty string. "data": nullis not the end of the list. On List Companies, a page that failed in Starboard can come back with status200,"data": nulland"cursor": "". Retry that page.limitdefaults to 20 and is capped at 50: larger values are treated as 50, and0, negative or non-numeric values as 20.- The cursor is opaque. Pass it back exactly as you received it, URL-encoded in the query string. An invalid cursor isn't an error: the API silently starts again from the first page. The exception is List Quotes with
updated_since, which returns400instead. - Guard against loops. Stop if a cursor repeats, or if you've read clearly more pages than
totaldivided bylimit, rounded up (totalis returned by List Quotes). Either means the cursor was garbled and the walk started over. List Quotes withupdated_sinceis the exception, where this guard doesn't apply: an invalid cursor returns400, and a walk can run pasttotalwhen quotes change while you page. - Pages are positions, not a snapshot. Records created, deleted or changed while you page shift the rest of the results, so a record can appear on two pages or be skipped. De-duplicate on
id. List Quotes withupdated_sincepages inupdated_atorder instead: a quote that changes while you page moves to the end, so it's listed again instead of being skipped.
# First page
GET /api/v3/quotes?status=win&limit=50
# Next page: the same filters and limit, plus the cursor from the previous response
GET /api/v3/quotes?status=win&limit=50&cursor=eyJvIjo1MH0