Resources
Custom fields
Custom fields are how a workspace stores its own data on a record: a claim number, a warranty term, a link to the job in another system. When you need to match a Coastline project to a record somewhere else, a custom field is usually the only reliable join, because customer names rarely agree across two systems and addresses get typed differently.
Match on key, not label
label is what a workspace admin sees and can rename whenever they like. key is the slug that stays put. An integration that keys on the label breaks silently the first time somebody fixes a typo in Settings.
/v1/opportunities/{id}/custom-fieldsList custom fields on a project
Returns every custom field defined for opportunities in the workspace, with this record's value on each. A project is an opportunity whose approval_status is project, so one endpoint covers both.
Fields with no value on the record come back with value: null rather than being omitted, so you can discover which fields exist before any record has filled one in. Requires opportunities.view.
The custom field object
| Field | Type | Description |
|---|---|---|
| key | string | Stable, workspace-unique slug for the field. This is the handle to match on. It does not change when the label is edited. |
| label | string | Display name a workspace admin chose. Editable at any time, so never key your integration on it. |
| type | enum | text, long_text, number, currency, date, boolean, single_select, multi_select, contact_reference, vendor_reference, url, email, or phone. |
| value | mixed | null | The resolved value: a string, number, boolean, or array of strings. Null when the field has no value on this record. |
| option_ids | string[] | Present on select fields only. The stable option ids behind the labels in value. |
| reference_id | string | Present on reference fields only. The id of the referenced contact or vendor. |
/v1/opportunities/{id}/custom-fieldsSet custom field values
The body is a flat object keyed by field key. Only the keys you send are touched: omitting a key leaves it alone, and sending null clears it. Requires opportunities.edit.
A key that does not exist in the workspace is rejected with 400 validation_error rather than ignored, so a typo in your integration surfaces immediately instead of quietly writing nothing.
Value formats on write
| Field type | Send | Notes |
|---|---|---|
| text, long_text, url, email, phone | string | Sent as a plain string. |
| number, currency | number | Sent as a JSON number. |
| date | string | YYYY-MM-DD. |
| boolean | boolean | true or false. |
| single_select | string | The option id, not the label. Read the field first to get the ids from option_ids. |
| multi_select | string[] | An array of option ids. |
| contact_reference, vendor_reference | string | The id of the contact or vendor to reference. |
Where else custom fields appear
You rarely need to call this endpoint on its own. A project-linked expenseembeds its project's custom fields on the payload, and every opportunity webhook carries them in data.custom_fields, in this same shape. Both mean a consumer can resolve the join key without a follow-up round trip.