Skip to main content

CRM API

Base Path: /api/v2

A trailing slash is optional: /companies and /companies/ reach the same endpoint.

Types​

This section describes the data types used in the CRM API.

Company Type​

Represents a company in the CRM system.

PropertyTypeDescription
idstringCompany ID
namestringCompany name
addressstringCompany address: street number and name
citystringCompany address: city name
postal_codestringCompany address: postal code
statestringCompany address: ISO 3166 province/state code
country_codestringCompany address: ISO 3166 country code
address_unlocodestringUN/LOCODE (5-character code) representing the company's physical address location. When it's empty, Starboard can fill it in from the address on a write or a read (see Update Company and Read Company).
formatted_addressstringRead-only. address, city, state, postal_code and country_code joined with , . Absent when they're all empty.
phonestringCompany contact phone
emailstringCompany contact email address
is_customerboolIs it a customer?
is_origin_agentboolIs an origin agent?
is_destination_agentboolIs a destination agent?
is_carrierboolIs a carrier?
ocean_fclboolProvides Ocean FCL services
ocean_lclboolProvides Ocean LCL services
drayageboolProvides Drayage services
cartageboolProvides Cartage services
airboolProvides Air services
external_idstringExternal identifier for the company (e.g., from a third-party system). Unique among your companies. Update Quote Customer finds a quote's customer by it.
service_countriesstring[]An array of ISO 3166 codes of countries where the company provides their services. When unset, assumed to operate globally. Kept only for a carrier, origin agent or destination agent (see Update Company).
service_provincesstring[]An array of ISO 3166 codes of subdivisions of service_countries where the company provides their services. Relevant for trucking services. When unset, assumed to operate in all provinces of the specified countries.
is_activeboolRead-only. false when the company is archived in Starboard. Archived companies are still listed and readable. The API can't archive or reactivate a company.
created_atstringRead-only. ISO 8601 timestamp of when the company was created.
updated_atstringISO 8601 timestamp of the last time this company or any of its contacts changed. Server-maintained: it advances on every write to the company, and whenever one of its contacts is created, edited, deleted, or moved to or from it (a write that leaves a contact exactly as stored doesn't count). Pair with the updated_since query parameter on List Companies for incremental sync.

Company Roles​

A company can have multiple roles in the system:

  • Customer: Companies that request quotes and book shipments
  • Origin Agent: Companies that handle cargo at the origin location
  • Destination Agent: Companies that handle cargo at the destination location
  • Carrier: Companies that provide transportation services

Company Service Types​

Companies can offer various service types:

  • Ocean FCL: Full Container Load ocean freight services
  • Ocean LCL: Less than Container Load ocean freight services
  • Drayage: Short-distance transportation of cargo, typically from ports to inland locations
  • Cartage: Trucking for LCL and air shipments, at origin or destination
  • Air: Air freight services

Country and Subdivision Codes​

The service_countries array contains ISO 3166 two-letter country codes. Examples:

  • US: United States
  • CA: Canada
  • GB: United Kingdom
  • CN: China
  • DE: Germany

The subdivision (state/province) contains the ISO 3166 two-letter code as well. Examples:

  • ON: Ontario, Canada
  • BC: British Columbia, Canada
  • FL: Florida, USA

Contact Type​

Represents a contact of a company in the CRM system.

PropertyTypeDescription
idstringContact ID
company_idstringCompany ID the contact belongs to
namestringContact's full name
titlestringContact's title if available
emailstringContact's email if available
phonestringContact's phone if available
is_pricing_agentboolContact is a pricing agent and should be contacted for rate inquiries
updated_atstringISO 8601 timestamp of the last time this contact was modified. Server-maintained on every write.

Example Company Object​

{
"id": "5f0c3a52-7d1e-4b8a-9c2f-1e6d4b3a2c10",
"created_at": "2025-11-02T09:00:00Z",
"updated_at": "2026-01-15T12:34:56Z",
"name": "Example Logistics Inc.",
"address": "123 Main Street",
"city": "New York",
"postal_code": "10001",
"state": "NY",
"country_code": "US",
"address_unlocode": "USNYC",
"formatted_address": "123 Main Street, New York, NY, 10001, US",
"phone": "+1-212-555-0100",
"email": "info@example.com",
"external_id": "GL-2024-001",
"is_customer": true,
"is_origin_agent": true,
"is_destination_agent": false,
"is_carrier": false,
"ocean_fcl": true,
"ocean_lcl": true,
"drayage": false,
"cartage": false,
"air": false,
"service_countries": ["US", "CA", "MX"],
"service_provinces": null,
"is_active": true
}

Example Contact Object​

{
"id": "e4b4a8c8-ce25-4df2-8b1d-ec7e5b45f405",
"company_id": "5f0c3a52-7d1e-4b8a-9c2f-1e6d4b3a2c10",
"name": "Jane Doe",
"email": "jane.doe@example.com",
"phone": "+1-212-555-0110",
"title": "CMO",
"is_pricing_agent": false,
"updated_at": "2026-01-15T12:34:56Z"
}

Operations​

Errors from CRM Writes​

Some validation failures come back as 500 internal_server_error with a generic message, instead of 400. These failures aren't transient: the same request fails the same way every time, so don't retry it blindly. Fix the data instead. They include:

  • Companies (Create and Update): name missing; a name that matches another of your companies, ignoring case, punctuation and suffixes such as Inc, Corp or Ltd; an external_id with characters other than letters, numbers, hyphens (-), underscores (_) and slashes (/), or one that another of your companies already has; a company ID that isn't a UUID or belongs to another Starboard account.
  • Contacts (Create and Update): name missing; a company_id that isn't one of your CRM companies; a contact ID that isn't a UUID or belongs to another Starboard account; an email that another contact of the same company already has (an exact match). An omitted email is saved as an empty string, and that counts too: a company can have only one contact without an email.
  • Delete Company: see that operation.

Invalid credentials, or an X-User-Company-ID the key can't act for, return 401. No CRM endpoint returns 403 or 409.

List Companies​

Retrieve a list of all CRM companies, newest first by created_at. Archived companies are included, with is_active: false. Your own company (the one your key acts for) isn't listed.

Endpoint​

GET /companies

Query Parameters​

ParameterTypeDescription
cursorstringA cursor string to continue iterating over the results (pagination)
limitintegerOptional. Maximum companies per page. Server-capped at 50 — larger values are silently clamped. Defaults to 20 when omitted.
updated_sincestringOptional. RFC 3339 timestamp. When set, only companies whose updated_at is greater than or equal to this value are returned. Malformed values return 400 Bad Request. URL-encode it: a + in a time zone offset must be sent as %2B, or use Z.

Response​

Returns a paginated list of companies:

PropertyTypeDescription
dataCompany[]An array of Company objects. null means the page failed in Starboard: the response still has status 200 and "cursor": "", so treat it as an error and retry the page, not as the end of the list.
cursorstringA cursor for retrieving the next "page" of results; "" on the last page

Error Responses​

Status CodeDescription
400Bad Request - Invalid updated_since
401Unauthorized - Invalid credentials, or an X-User-Company-ID the key can't act for
500Internal Server Error

Example​

Request:

curl https://<your-starboard-api-host>/api/v2/companies \
-H 'Content-Type: application/json' \
-H 'Authorization: Basic {base64-encoded-credentials}' \
-H 'X-User-Company-ID: {ID}'

Response:

{
"data": [
{
"id": "5f0c3a52-7d1e-4b8a-9c2f-1e6d4b3a2c10",
"created_at": "2025-11-02T09:00:00Z",
"updated_at": "2026-01-15T12:34:56Z",
"name": "Example Logistics Inc.",
"address": "123 Main Street",
"city": "New York",
"postal_code": "10001",
"state": "NY",
"country_code": "US",
"address_unlocode": "USNYC",
"formatted_address": "123 Main Street, New York, NY, 10001, US",
"phone": "+1-212-555-0100",
"email": "info@example.com",
"external_id": "GL-2024-001",
"is_customer": true,
"is_origin_agent": true,
"is_destination_agent": false,
"is_carrier": false,
"ocean_fcl": true,
"ocean_lcl": true,
"drayage": false,
"cartage": false,
"air": false,
"service_countries": ["US", "CA", "MX"],
"service_provinces": null,
"is_active": true
},
{
"id": "8b2e4d6f-1a3c-4e5f-9b7d-2c4e6a8b0d13",
"created_at": "2025-10-20T14:30:00Z",
"updated_at": "2026-02-01T08:15:00Z",
"name": "Example Shipping Co.",
"address": "456 Harbor Blvd",
"city": "Los Angeles",
"postal_code": "90001",
"state": "CA",
"country_code": "US",
"address_unlocode": "USLAX",
"formatted_address": "456 Harbor Blvd, Los Angeles, CA, 90001, US",
"phone": "+1-310-555-0200",
"email": "contact@example.org",
"external_id": "ESC-2024-002",
"is_customer": false,
"is_origin_agent": true,
"is_destination_agent": true,
"is_carrier": true,
"ocean_fcl": true,
"ocean_lcl": false,
"drayage": true,
"cartage": false,
"air": false,
"service_countries": ["US", "CN", "JP", "KR"],
"service_provinces": null,
"is_active": true
}
],
"cursor": "eyJvIjoyMH0"
}

Pagination​

To retrieve additional results, send the same request again (the same updated_since and limit, if you used them) with the cursor value from the previous response. See Pagination.

curl 'https://<your-starboard-api-host>/api/v2/companies?cursor=eyJvIjoyMH0' \
-H 'Content-Type: application/json' \
-H 'Authorization: Basic {base64-encoded-credentials}' \
-H 'X-User-Company-ID: {ID}'

Incremental Sync with updated_since​

For integrators keeping a local mirror of the CRM in sync, use updated_since to fetch only the companies that have changed since your last poll. It's inclusive: a company whose updated_at equals updated_since is returned. The recommended polling pattern is:

  1. Before the walk starts, note the server's current time, for example from the Date header of the first response. Subtract a small overlap (30–120 seconds): that's your next updated_since. Don't use the highest updated_at you see in the walk instead: a company on a page you've already read can be saved during the walk, earlier than one you read later, and would then fall outside your next window.
  2. Walk every page of the current updated_since window, sending the same updated_since and limit on each page, until cursor is empty. If a page comes back with "data": null, the request failed in Starboard: retry that page instead of treating it as the end.
  3. Deduplicate on id in your local store: pages are positions, so a company can appear twice.

What the window doesn't tell you:

  • Deletions and merges aren't reported. A company deleted in Starboard, or merged into another, simply stops appearing. Run a periodic full walk (no updated_since) and remove local companies whose id is no longer listed. The full walk also catches a company skipped because another one was deleted while you paged.
  • Archived companies stay in the list with is_active: false. Archiving advances updated_at, so it shows up in the window.
  • Reading can move updated_at. Read Company on a company with an empty address_unlocode can fill it in from the address, which advances updated_at.

Contact changes are included. Creating, editing, or deleting a contact advances its company's updated_at, and moving a contact to another company advances both companies — so the company shows up in the next updated_since window even if none of its own fields changed. Deleted contacts are not returned anywhere, so for each company in the window, re-fetch its full contact list and replace your local copy rather than merging into it. A few saves count even when a contact's visible details don't change: the first PUT of an unchanged contact can count once (empty fields are stored as empty strings), and some internal saves re-create a contact under a new id. Replacing rather than merging handles both. Page the contact list to an empty cursor first, and if it comes back empty, confirm the company still exists before clearing your copy: List Contacts returns an empty list for an unknown company too.

Results are ordered by created_at DESC — not by updated_at — so late writes to older rows appear alongside newer ones within the same window. Companies created while you walk push the rest of the results back by one place, which is why step 3 deduplicates.

Example — companies changed since your last poll:

curl 'https://<your-starboard-api-host>/api/v2/companies?updated_since=2026-01-14T12:00:00Z' \
-H 'Content-Type: application/json' \
-H 'Authorization: Basic {base64-encoded-credentials}' \
-H 'X-User-Company-ID: {ID}'

The next page sends the same updated_since again, with the cursor from the previous response:

curl 'https://<your-starboard-api-host>/api/v2/companies?updated_since=2026-01-14T12:00:00Z&cursor=eyJvIjoyMH0' \
-H 'Content-Type: application/json' \
-H 'Authorization: Basic {base64-encoded-credentials}' \
-H 'X-User-Company-ID: {ID}'

An invalid updated_since returns 400 Bad Request:

{
"error": "bad_request",
"message": "Invalid updated_since; expected RFC3339 timestamp (e.g. 2026-01-02T15:04:05Z)"
}

Read Company​

Retrieve details of a specific company.

Endpoint​

GET /companies/{company_id}

Path Parameters​

ParameterTypeDescription
company_idstringThe ID of the company to retrieve

Response​

Returns a single Company object.

If the company has no address_unlocode but has a country, reading it makes Starboard guess one from the address. When it finds one, it saves it on the company, which advances the company's updated_at.

Error Responses​

Status CodeDescription
401Unauthorized - Invalid credentials
404Not Found - Company doesn't exist in your CRM
500Internal Server Error

Example​

Request:

curl https://<your-starboard-api-host>/api/v2/companies/5f0c3a52-7d1e-4b8a-9c2f-1e6d4b3a2c10 \
-H 'Content-Type: application/json' \
-H 'Authorization: Basic {base64-encoded-credentials}' \
-H 'X-User-Company-ID: {ID}'

Response:

{
"id": "5f0c3a52-7d1e-4b8a-9c2f-1e6d4b3a2c10",
"created_at": "2025-11-02T09:00:00Z",
"updated_at": "2026-01-15T12:34:56Z",
"name": "Example Logistics Inc.",
"address": "123 Main Street",
"city": "New York",
"postal_code": "10001",
"state": "NY",
"country_code": "US",
"address_unlocode": "USNYC",
"formatted_address": "123 Main Street, New York, NY, 10001, US",
"phone": "+1-212-555-0100",
"email": "info@example.com",
"external_id": "GL-2024-001",
"is_customer": true,
"is_origin_agent": true,
"is_destination_agent": false,
"is_carrier": false,
"ocean_fcl": true,
"ocean_lcl": true,
"drayage": false,
"cartage": false,
"air": false,
"service_countries": ["US", "CA", "MX"],
"service_provinces": null,
"is_active": true
}

Create Company​

Create a company in your CRM. Starboard assigns its id.

Endpoint​

POST /companies

Request Body​

A Company object without id. name is required. Any id you send is replaced by a new one, and the read-only fields (is_active, created_at, updated_at, formatted_address) are ignored. Fields you omit are saved empty, and if address_unlocode is omitted Starboard guesses one from the address, as in Update Company.

Service coverage needs a coverage role, as in Update Company: without one, service_provinces is saved as null, and service_countries is kept at first but saved as null at the company's next change (an update, or a contact being added, edited or removed).

Response​

200 OK with your request echoed back, plus the new id. It is not the stored record: fields you didn't send come back empty or zero (timestamps as 0001-01-01T00:00:00Z), and fields you sent, including read-only ones, come back as sent. So is_active comes back false unless you sent it, although the new company is active, and address_unlocode comes back as you sent it, even when Starboard filled one in. Read the company with Read Company to see what was stored.

Error Responses​

Status CodeDescription
400bad_request - The body isn't valid JSON, or Content-Type isn't application/json
400invalid_unlocode - address_unlocode isn't in UN/LOCODE format (see Update Company)
401Unauthorized - Invalid credentials
500Internal Server Error, including validation failures such as a missing or duplicate name (see Errors from CRM Writes)

Example​

Request:

curl -X POST https://<your-starboard-api-host>/api/v2/companies \
-H 'Content-Type: application/json' \
-H 'Authorization: Basic {base64-encoded-credentials}' \
-H 'X-User-Company-ID: {ID}' \
-d '{
"name": "Example Freight Partners",
"city": "Rotterdam",
"country_code": "NL",
"email": "ops@example.net",
"is_customer": true,
"external_id": "EFP-001"
}'

Response (the request echoed back with the new id):

{
"id": "0c9d8e7f-6a5b-4c3d-8e2f-1a0b9c8d7e6f",
"created_at": "0001-01-01T00:00:00Z",
"updated_at": "0001-01-01T00:00:00Z",
"name": "Example Freight Partners",
"address": "",
"city": "Rotterdam",
"postal_code": "",
"state": "",
"country_code": "NL",
"address_unlocode": "",
"phone": "",
"email": "ops@example.net",
"external_id": "EFP-001",
"is_customer": true,
"is_origin_agent": false,
"is_destination_agent": false,
"is_carrier": false,
"ocean_fcl": false,
"ocean_lcl": false,
"drayage": false,
"cartage": false,
"air": false,
"service_countries": null,
"service_provinces": null,
"is_active": false
}

Update Company​

Replace a company's information. PUT replaces the whole record: it isn't a partial update.

Endpoint​

PUT /companies/{company_id}

Path Parameters​

ParameterTypeDescription
company_idstringThe ID of the company to update. Must be a UUID.

Request Body​

A full Company object. name is required. The id comes from the path, and the read-only fields (is_active, created_at, updated_at, formatted_address) are ignored.

warning
  • Fields you omit are cleared (saved empty, false or null). Read the company first and send every field back, changing only what you mean to change.
  • Some fields are cleared on every PUT, even if you send back exactly what you read, because the API doesn't expose them: the company's notes, email domain and country name, and its roles other than the ones in the Company type (vendor, prospect, agent, customs agent, insurance, air carrier, ocean carrier, bonded and non-bonded warehouse). If your team keeps these in Starboard, a PUT from the API erases them.
  • A PUT to an ID that doesn't exist creates the company with that ID.
  • If address_unlocode is empty or omitted, Starboard guesses one from city, state and country_code (no guess without country_code).
  • Service coverage needs a coverage role. service_countries and service_provinces are kept only when is_carrier, is_origin_agent or is_destination_agent is true; otherwise both are saved as null. service_provinces is also saved as null when service_countries is empty.
  • is_active can't be changed through the API.

Response​

200 OK with your request echoed back, not the stored record: fields you didn't send come back empty or zero (timestamps as 0001-01-01T00:00:00Z), and fields you sent, including read-only ones, come back as sent. So is_active comes back false unless you sent it, and address_unlocode as you sent it. Read the company with Read Company afterwards to see what was stored.

Error Responses​

Status CodeDescription
400bad_request - The body isn't valid JSON, or Content-Type isn't application/json
400invalid_unlocode - address_unlocode isn't in UN/LOCODE format (see below)
401Unauthorized - Invalid credentials, or an X-User-Company-ID the key can't act for
500Internal Server Error, including validation failures such as a missing name, a duplicate name or external_id, or a company_id that isn't a UUID (see Errors from CRM Writes)

Validation Errors​

When the address_unlocode field is provided with an invalid format, the API returns a 400 Bad Request error:

{
"error": "invalid_unlocode",
"message": "Invalid UNLOCODE format. Expected format: 2-letter country code + 3-letter location code (e.g., USNYC, GBLON)"
}

Example​

Changing the phone, email, external_id, LCL service and service countries of the company from Read Company. Every other field is sent back as read, so it isn't cleared, and the service countries are kept because the company is an origin agent. The read-only fields are left out of the request.

Request:

curl -X PUT https://<your-starboard-api-host>/api/v2/companies/5f0c3a52-7d1e-4b8a-9c2f-1e6d4b3a2c10 \
-H 'Content-Type: application/json' \
-H 'Authorization: Basic {base64-encoded-credentials}' \
-H 'X-User-Company-ID: {ID}' \
-d '{
"name": "Example Logistics Inc.",
"address": "123 Main Street",
"city": "New York",
"postal_code": "10001",
"state": "NY",
"country_code": "US",
"address_unlocode": "USNYC",
"phone": "+1-212-555-0101",
"email": "ops@example.com",
"external_id": "GL-2024-001-UPDATED",
"is_customer": true,
"is_origin_agent": true,
"is_destination_agent": false,
"is_carrier": false,
"ocean_fcl": true,
"ocean_lcl": false,
"drayage": false,
"cartage": false,
"air": false,
"service_countries": ["US", "CA", "MX", "GB"],
"service_provinces": null
}'

Response (the request echoed back):

{
"id": "5f0c3a52-7d1e-4b8a-9c2f-1e6d4b3a2c10",
"created_at": "0001-01-01T00:00:00Z",
"updated_at": "0001-01-01T00:00:00Z",
"name": "Example Logistics Inc.",
"address": "123 Main Street",
"city": "New York",
"postal_code": "10001",
"state": "NY",
"country_code": "US",
"address_unlocode": "USNYC",
"phone": "+1-212-555-0101",
"email": "ops@example.com",
"external_id": "GL-2024-001-UPDATED",
"is_customer": true,
"is_origin_agent": true,
"is_destination_agent": false,
"is_carrier": false,
"ocean_fcl": true,
"ocean_lcl": false,
"drayage": false,
"cartage": false,
"air": false,
"service_countries": ["US", "CA", "MX", "GB"],
"service_provinces": null,
"is_active": false
}

Delete Company​

Permanently delete a company from your CRM.

warning

This is a hard delete, and it can't be undone:

  • Records linked to the company are deleted with it: its contacts and other records that point at it, which can include quotes where it is the customer. Companies it was the parent of are kept.
  • If a linked record can't be removed, the whole delete fails with 500 and nothing is deleted.

Don't delete customers that have quotes. Archive them in Starboard instead.

Endpoint​

DELETE /companies/{company_id}

Path Parameters​

ParameterTypeDescription
company_idstringThe ID of the company to delete

Response​

Returns an empty response with status 204 No Content on success.

Error Responses​

Status CodeDescription
401Unauthorized - Invalid credentials
500Internal Server Error. Also returned when the company doesn't exist in your CRM (there is no 404), and when linked records block the delete. A 500 can also mean the delete took longer than about 30 seconds: it may still complete. Read the company with Read Company before retrying; 404 means it's gone.

Example​

Request:

curl -X DELETE https://<your-starboard-api-host>/api/v2/companies/5f0c3a52-7d1e-4b8a-9c2f-1e6d4b3a2c10 \
-H 'Content-Type: application/json' \
-H 'Authorization: Basic {base64-encoded-credentials}' \
-H 'X-User-Company-ID: {ID}'

Success Response:

HTTP/1.1 204 No Content

List Contacts​

Retrieve the contacts of one CRM company.

Endpoint​

GET /companies/{company_id}/contacts

Query Parameters​

ParameterTypeDescription
cursorstringA cursor string to continue iterating over the results (pagination)
limitintegerOptional. Maximum contacts per page. Defaults to 20; capped at 50 (larger values are treated as 50).

Response​

Returns a paginated list of the company's contacts, newest first by when they were created (then by name):

PropertyTypeDescription
dataContact[]An array of Contact objects
cursorstringA cursor for retrieving the next "page" of results; "" on the last page
caution

A company_id that doesn't exist, or isn't in your CRM, returns 200 with an empty list, not 404. Page until cursor is empty before acting on the result, and don't replace your local copy of a company's contacts with an empty first page until you've confirmed the company exists (for example with Read Company).

Error Responses​

Status CodeDescription
401Unauthorized - Invalid credentials, or an X-User-Company-ID the key can't act for
500Internal Server Error. Also returned when company_id isn't a UUID.

Example​

Request:

curl https://<your-starboard-api-host>/api/v2/companies/5f0c3a52-7d1e-4b8a-9c2f-1e6d4b3a2c10/contacts \
-H 'Content-Type: application/json' \
-H 'Authorization: Basic {base64-encoded-credentials}' \
-H 'X-User-Company-ID: {ID}'

Response:

{
"data": [
{
"id": "e4b4a8c8-ce25-4df2-8b1d-ec7e5b45f405",
"company_id": "5f0c3a52-7d1e-4b8a-9c2f-1e6d4b3a2c10",
"name": "Jane Doe",
"email": "jane.doe@example.com",
"phone": "+1-212-555-0110",
"title": "CMO",
"is_pricing_agent": false,
"updated_at": "2026-01-15T12:34:56Z"
},
{
"id": "e4b4a8c8-ce25-4df2-8b1d-ec7e5b45f406",
"company_id": "5f0c3a52-7d1e-4b8a-9c2f-1e6d4b3a2c10",
"name": "Richard Roe",
"email": "richard.roe@example.com",
"phone": "+1-212-555-0111",
"title": "CCO",
"is_pricing_agent": false,
"updated_at": "2026-01-16T09:00:00Z"
}
],
"cursor": ""
}

Pagination​

When cursor isn't empty, send the same request again (with the same limit, if you used one) plus the cursor value from the previous response:

curl 'https://<your-starboard-api-host>/api/v2/companies/5f0c3a52-7d1e-4b8a-9c2f-1e6d4b3a2c10/contacts?cursor=eyJvIjoyMH0' \
-H 'Content-Type: application/json' \
-H 'Authorization: Basic {base64-encoded-credentials}' \
-H 'X-User-Company-ID: {ID}'

Read Contact​

Retrieve details of a specific company contact.

Endpoint​

GET /companies/{company_id}/contacts/{contact_id}

Path Parameters​

ParameterTypeDescription
company_idstringThe ID of the company
contact_idstringThe ID of the contact to retrieve

Response​

Returns a single Contact object.

Error Responses​

Status CodeDescription
401Unauthorized - Invalid credentials
404Not Found - The contact doesn't exist in your CRM, or belongs to a different company than company_id
500Internal Server Error

Example​

Request:

curl https://<your-starboard-api-host>/api/v2/companies/5f0c3a52-7d1e-4b8a-9c2f-1e6d4b3a2c10/contacts/e4b4a8c8-ce25-4df2-8b1d-ec7e5b45f405 \
-H 'Content-Type: application/json' \
-H 'Authorization: Basic {base64-encoded-credentials}' \
-H 'X-User-Company-ID: {ID}'

Response:

{
"id": "e4b4a8c8-ce25-4df2-8b1d-ec7e5b45f405",
"company_id": "5f0c3a52-7d1e-4b8a-9c2f-1e6d4b3a2c10",
"name": "Jane Doe",
"email": "jane.doe@example.com",
"phone": "+1-212-555-0110",
"title": "CMO",
"is_pricing_agent": false,
"updated_at": "2026-01-15T12:34:56Z"
}

Create Contact​

Create a contact on one of your CRM companies. Starboard assigns its id.

Endpoint​

POST /companies/{company_id}/contacts

Path Parameters​

ParameterTypeDescription
company_idstringThe ID of the company the contact belongs to

Request Body​

A Contact object without id. name is required. company_id comes from the path, any id you send is replaced by a new one, and is_pricing_agent defaults to false. Each contact of a company needs its own email: an omitted one is saved as an empty string, so a company can have only one contact without an email (see Errors from CRM Writes).

Response​

200 OK with your request echoed back, plus the new id and the company_id from the path. It is not the stored record: fields you didn't send come back empty or zero (updated_at as 0001-01-01T00:00:00Z), and fields you sent, including updated_at, come back as sent. Read the contact with Read Contact to see what was stored.

Error Responses​

Status CodeDescription
400bad_request - The body isn't valid JSON, or Content-Type isn't application/json
401Unauthorized - Invalid credentials
500Internal Server Error, including validation failures such as a missing name, a company_id that isn't in your CRM, or an email another contact of the company already has (see Errors from CRM Writes)

Example​

Request:

curl -X POST https://<your-starboard-api-host>/api/v2/companies/5f0c3a52-7d1e-4b8a-9c2f-1e6d4b3a2c10/contacts \
-H 'Content-Type: application/json' \
-H 'Authorization: Basic {base64-encoded-credentials}' \
-H 'X-User-Company-ID: {ID}' \
-d '{
"name": "Mary Major",
"email": "mary.major@example.com",
"phone": "+1-212-555-0112",
"title": "Pricing Manager",
"is_pricing_agent": true
}'

Response:

{
"id": "3c1e5a7b-9d2f-4b6a-8e0c-5f7a9b1d3e2c",
"company_id": "5f0c3a52-7d1e-4b8a-9c2f-1e6d4b3a2c10",
"name": "Mary Major",
"email": "mary.major@example.com",
"phone": "+1-212-555-0112",
"title": "Pricing Manager",
"is_pricing_agent": true,
"updated_at": "0001-01-01T00:00:00Z"
}

Update Contact​

Replace a contact's information. PUT replaces the whole contact: it isn't a partial update.

Endpoint​

PUT /companies/{company_id}/contacts/{contact_id}

Path Parameters​

ParameterTypeDescription
company_idstringThe ID of the company. The contact is moved to this company if it's on another one.
contact_idstringThe ID of the contact to update. Must be a UUID.

Request Body​

A full Contact object. name is required. id and company_id come from the path.

warning
  • Fields you omit are cleared: title, email and phone are saved empty, and is_pricing_agent is saved as false. Read the contact first and send every field back.
  • Every API write turns off auto-sourcing for the contact in Starboard. Turn it back on in Starboard if you rely on it.
  • The contact moves to the company in the path, if it was on another of your companies.
  • A PUT to an ID that doesn't exist creates the contact with that ID.

Response​

200 OK with your request echoed back, with the id and company_id from the path. It is not the stored record: fields you didn't send come back empty or zero (updated_at as 0001-01-01T00:00:00Z), and fields you sent, including updated_at, come back as sent. Read the contact with Read Contact afterwards to see what was stored.

Error Responses​

Status CodeDescription
400bad_request - The body isn't valid JSON, or Content-Type isn't application/json
401Unauthorized - Invalid credentials, or an X-User-Company-ID the key can't act for
500Internal Server Error, including validation failures such as a missing name, a company_id that isn't in your CRM, or an email another contact of the company already has (see Errors from CRM Writes)

Example​

Request:

curl -X PUT https://<your-starboard-api-host>/api/v2/companies/5f0c3a52-7d1e-4b8a-9c2f-1e6d4b3a2c10/contacts/e4b4a8c8-ce25-4df2-8b1d-ec7e5b45f405 \
-H 'Content-Type: application/json' \
-H 'Authorization: Basic {base64-encoded-credentials}' \
-H 'X-User-Company-ID: {ID}' \
-d '{
"name": "Jane Doe",
"title": "CMO",
"email": "jane.doe@example.org",
"phone": "+1-212-555-0101",
"is_pricing_agent": false
}'

Response (the request echoed back):

{
"id": "e4b4a8c8-ce25-4df2-8b1d-ec7e5b45f405",
"company_id": "5f0c3a52-7d1e-4b8a-9c2f-1e6d4b3a2c10",
"name": "Jane Doe",
"email": "jane.doe@example.org",
"phone": "+1-212-555-0101",
"title": "CMO",
"is_pricing_agent": false,
"updated_at": "0001-01-01T00:00:00Z"
}

Delete Contact​

Delete an existing contact from the CRM system.

Endpoint​

DELETE /companies/{company_id}/contacts/{contact_id}

Path Parameters​

ParameterTypeDescription
company_idstringThe ID of the company
contact_idstringThe ID of the contact to delete

Response​

Returns an empty response with status 204 No Content on success. A contact that doesn't exist also returns 204, so repeating a delete is safe.

Error Responses​

Status CodeDescription
401Unauthorized - Invalid credentials
404Not Found - The contact belongs to a different company than company_id
500Internal Server Error

Example​

Request:

curl -X DELETE https://<your-starboard-api-host>/api/v2/companies/5f0c3a52-7d1e-4b8a-9c2f-1e6d4b3a2c10/contacts/e4b4a8c8-ce25-4df2-8b1d-ec7e5b45f405 \
-H 'Content-Type: application/json' \
-H 'Authorization: Basic {base64-encoded-credentials}' \
-H 'X-User-Company-ID: {ID}'

Success Response:

HTTP/1.1 204 No Content