Fields and relationships
Learn how fields, relationships, and their value types work in the Lightfield API.
Fields store values such as a name, email or deal amount. Relationships link records, such as an opportunity and its account or contacts.
To create or update a record through the API or SDKs, pass values in the fields object. Each key identifies a field on that object type.
Field keys
Section titled “Field keys”Field and relationship keys use a $ prefix to distinguish system-defined entries from custom ones:
- System fields and relationships: Prefixed with
$, e.g.$name,$stage,$account. - Custom fields and relationships: No prefix, e.g.
priority,partners.
{ "fields": { "$name": "Acme Corp", "$stage": "opt_...", "priority": 1 }}Writes take bare field values. Record responses wrap each value in an object with valueType and value.
Discovering definitions
Section titled “Discovering definitions”Use the definitions endpoint to discover all fields and relationships available on an object type:
GET /v1/{objectType}/definitionsFor example:
curl https://api.lightfield.app/v1/opportunities/definitions \ -H "Authorization: Bearer $LIGHTFIELD_API_KEY" \ -H "Lightfield-Version: 2026-03-01"The response includes the full schema for both fields and relationships:
{ "objectType": "opportunity", "fieldDefinitions": { "$name": { "id": "ad_...", "slug": "name", "label": "Name", "description": null, "valueType": "TEXT", "system": true, "typeConfiguration": {} }, "$stage": { "id": "ad_...", "slug": "stage", "label": "Stage", "description": "What stage is the opportunity in?", "valueType": "SINGLE_SELECT", "system": true, "typeConfiguration": { "options": [ { "id": "opt_...", "label": "Prospecting", "description": null }, { "id": "opt_...", "label": "Closed Won", "description": null } ] } } }, "relationshipDefinitions": { "$account": { "id": null, "slug": "account", "label": "Account", "description": null, "system": true, "cardinality": "HAS_ONE", "objectType": "account" } }}Use definitions to match select option IDs to labels, find the keys in record responses, and look up slugs to filter by relationships.
The API reads definitions but can’t create or change them. To add a field or change a select field’s options, an owner or admin uses Data model settings in the app. Your integration then picks up the change from the next definitions response.
Supported object types
Section titled “Supported object types”| Object type | Path |
|---|---|
| Accounts | GET /v1/accounts/definitions |
| Contacts | GET /v1/contacts/definitions |
| Opportunities | GET /v1/opportunities/definitions |
| Custom objects | GET /v1/objects/{entitySlug}/definitions |
| Meetings | GET /v1/meetings/definitions |
| Notes | GET /v1/notes/definitions |
| Tasks | GET /v1/tasks/definitions |
Field definition object
Section titled “Field definition object”Read fieldDefinitions to find each field’s key, value type, configuration and whether it is read-only. Use the generated account definitions reference for the complete response schema.
Relationship definition object
Section titled “Relationship definition object”Read relationshipDefinitions to find the related object type and cardinality before writing IDs. The definitions reference lists every response property. Self-referential relationships also expose the directions explained below.
Self-referential relationships
Section titled “Self-referential relationships”A self-referential relationship links an object type to itself, such as an account hierarchy between accounts. Each record can sit on either end, so the definition includes a directions object with a label and cardinality for each role:
asSource: the current record is the source endpoint.asDestination: the current record is the destination endpoint.
{ "relationshipDefinitions": { "account-hierarchy": { "id": "rd_...", "slug": "account-hierarchy", "label": "Subsidiaries", "description": null, "system": false, "cardinality": "HAS_MANY", "objectType": "account", "directions": { "asSource": { "label": "Subsidiaries", "cardinality": "HAS_MANY" }, "asDestination": { "label": "Parent account", "cardinality": "HAS_ONE" } } } }}Record responses keep values as every linked record from either role, and add directions with the values for each role:
{ "relationships": { "account-hierarchy": { "cardinality": "ONE_TO_MANY", "objectType": "account", "values": ["acc_sub1", "acc_sub2", "acc_parent"], "directions": { "asSource": { "cardinality": "HAS_MANY", "values": ["acc_sub1", "acc_sub2"] }, "asDestination": { "cardinality": "HAS_ONE", "values": ["acc_parent"] } } } }}To filter by one role, append :asSource or :asDestination to the relationship key. See filtering by relationships.
Writing relationships
Section titled “Writing relationships”For creates, relationship values are entity IDs or arrays of entity IDs. For
updates, use add, remove, or replace as allowed by the relationship’s
cardinality. Writes to a self-referential relationship use the plain key, not
the :asSource or :asDestination form.
For a complete create and update example, see Manage records. After a relationship write, retrieve the record by ID and check its relationships values; list results can lag behind changes.
Field value history
Section titled “Field value history”Use a field history endpoint to retrieve the recorded values of one attribute-backed field over time.
Supported history endpoints
Section titled “Supported history endpoints”| Object type | Endpoint |
|---|---|
| Accounts | /v1/accounts/{id}/fields/{fieldKey}/history |
| Contacts | /v1/contacts/{id}/fields/{fieldKey}/history |
| Opportunities | /v1/opportunities/{id}/fields/{fieldKey}/history |
| Custom objects | /v1/objects/{entitySlug}/values/{id}/fields/{fieldKey}/history |
Use a $-prefixed key for a system field, such as $stage, and a bare slug for a custom field.
Results are returned newest first, with consecutive identical values collapsed. Responses default to 20 entries and accept a limit of up to 100. Use nextCursor as the after parameter to retrieve older pages. This is cursor pagination; do not use the offset parameter from record list endpoints.
For example:
curl 'https://api.lightfield.app/v1/opportunities/opp_.../fields/$stage/history?limit=20' \ -H "Authorization: Bearer $LIGHTFIELD_API_KEY" \ -H "Lightfield-Version: 2026-03-01"The response includes the recorded value, its display value, and when it was recorded:
{ "data": [ { "value": "opt_abc123", "valueType": "SINGLE_SELECT", "displayValue": "Closed Won", "recordedAt": "2026-08-01T17:30:00.000Z", "isCreate": false }, { "value": "opt_def456", "valueType": "SINGLE_SELECT", "displayValue": "Qualified", "recordedAt": "2026-06-20T09:15:00.000Z", "isCreate": true } ], "hasMore": false, "nextCursor": null}When hasMore is true, set NEXT_CURSOR to the returned nextCursor and request the next page:
curl 'https://api.lightfield.app/v1/opportunities/opp_.../fields/$stage/history' \ -G \ -H "Authorization: Bearer $LIGHTFIELD_API_KEY" \ -H "Lightfield-Version: 2026-03-01" \ --data-urlencode 'limit=20' \ --data-urlencode "after=$NEXT_CURSOR"Continue with each new cursor until hasMore is false. If the endpoint rejects the field, check that it is attribute-backed. A 404 can also mean the caller has only name-level access to the record; it does not always mean the ID is missing.
See the API reference for accounts, contacts, opportunities, and custom objects.
Field value types
Section titled “Field value types”Every field has a valueType that determines the shape of its value. Unset values can be null; check the resource’s reference for each field’s accepted values:
| Type | Value format | Description |
|---|---|---|
TEXT | string | Plain text. |
NUMBER | number | A number with up to 2 decimal places, within safe integer range. |
CHECKBOX | boolean | null | A true/false value. |
CURRENCY | number | null | A numeric amount. The currency code (e.g. USD) is set in the field’s type configuration. |
DATETIME | string | null | An ISO 8601 datetime string with timezone offset (e.g. "2026-03-16T12:00:00+00:00"). Stored as UTC. |
EMAIL | string | string[] | null | Email address data (RFC 5322). Most EMAIL fields return arrays, but on the meetings resource meeting.$organizerEmail returns a single string. Fields such as meeting.$attendeeEmails still return arrays. |
TELEPHONE | string[] | An array of phone numbers. See telephone format below. |
URL | string | string[] | null | A URL or array of URLs. |
ADDRESS | object | null | A structured address object. See address format below. |
FULL_NAME | object | An object with firstName and lastName properties. |
SOCIAL_HANDLE | string | null | A social media handle or profile URL. See social handle format below. |
SINGLE_SELECT | string | null | The id of a select option (opt_... for stages and ordinary selects; pd_... for $pipeline). |
MULTI_SELECT | string[] | An array of select option ids. |
MARKDOWN | string | Markdown content. Check the field definition’s readOnly value before writing. |
HTML | string | HTML content, such as an email body. Check the resource’s write contract. |
Address format
Section titled “Address format”Address values are objects with the following optional properties. Pass null to unset the whole address:
| Property | Type | Description |
|---|---|---|
street | string | Street address line 1. |
street2 | string | Street address line 2 (apartment, suite, etc.). |
city | string | City name. |
state | string | State or region. |
postalCode | string | Postal or ZIP code. |
country | string | ISO 3166-1 alpha-2 country code (e.g. "US", "GB"). Must be exactly 2 characters. |
latitude | number | Latitude coordinate. |
longitude | number | Longitude coordinate. |
{ "street": "123 Main St", "city": "San Francisco", "state": "CA", "postalCode": "94105", "country": "US"}Telephone format
Section titled “Telephone format”Phone numbers are validated and normalized on input:
- With
+prefix: Validated as an international number in E.164 format (e.g."+12025551234"). - Without
+prefix: Validated as a US number first, then falls back to accepting 7–15 digit local numbers. - Extensions: Supported via
;ext=notation (e.g."+12025551234;ext=100"). Various input formats likex100,ext:100, and#100are automatically normalized to the;ext=format.
["+12025551234", "+442071234567;ext=100"]Social handle format
Section titled “Social handle format”SOCIAL_HANDLE fields store a social media profile URL. Each social handle field has a handleService in its type configuration that specifies the platform: TWITTER, LINKEDIN, FACEBOOK, or INSTAGRAM.
Values must be a valid profile URL for the configured platform:
| Platform | Accepted URL formats |
|---|---|
TWITTER | https://twitter.com/{username}, https://x.com/{username} |
LINKEDIN | https://linkedin.com/in/{slug}, https://linkedin.com/company/{slug} |
FACEBOOK | https://facebook.com/{username}, https://fb.com/{username} |
INSTAGRAM | https://www.instagram.com/{username} |
Regional subdomains (e.g. https://uk.linkedin.com/in/...) and query parameters are accepted.
"https://x.com/lightfield""https://www.linkedin.com/in/jane-doe""https://www.instagram.com/lightfield"Pass null to clear a social handle field.
Select options
Section titled “Select options”SINGLE_SELECT and MULTI_SELECT fields reference predefined options by their id. Use the definitions endpoint to discover the available options for a field.
Each option has the following shape:
| Property | Type | Description |
|---|---|---|
id | string | Unique identifier (opt_..., or pd_... for $pipeline). |
label | string | Display label. |
description | string | null | Optional description. |
parentId | string | For a dependent select, the owning option’s ID in the parent field. Absent for ordinary selects. |
Use option IDs for stable references: pass a string for SINGLE_SELECT and an array for MULTI_SELECT. Record write endpoints also accept option labels where documented in their reference. Stage labels have the additional pipeline rules below.
{ "fields": { "$stage": "opt_abc123", "tags": ["opt_def456", "opt_ghi789"] }}Pipelines and stages
Section titled “Pipelines and stages”A record belongs to at most one pipeline, determined by its selected stage.
Stage labels may repeat across pipelines, so use option IDs to identify stages.
An ordinary custom field named stage is separate from the system $stage field.
Discover pipelines and their stages
Section titled “Discover pipelines and their stages”The $pipeline entry in fieldDefinitions lists active pipelines
in typeConfiguration.options. Each option’s id is a pd_... pipeline ID and
its label is the pipeline name. The $stage definition lists the stages across those
pipelines, with parentFieldKey: "$pipeline" and a parentId on each option:
{ "fieldDefinitions": { "$pipeline": { "label": "Pipeline", "valueType": "SINGLE_SELECT", "readOnly": false, "typeConfiguration": { "options": [ { "id": "pd_sales", "label": "Sales" }, { "id": "pd_renewals", "label": "Renewals" } ] } }, "$stage": { "label": "Stage", "valueType": "SINGLE_SELECT", "typeConfiguration": { "parentFieldKey": "$pipeline", "options": [ { "id": "opt_sales_qualified", "label": "Qualified", "parentId": "pd_sales" }, { "id": "opt_renewals_qualified", "label": "Qualified", "parentId": "pd_renewals" } ] } } }}This is an excerpt; the IDs are placeholders. Match a stage option’s parentId
to the chosen pipeline’s id before resolving a label. Do not choose the first
matching label across all pipelines.
If $pipeline is absent from definitions, do not send it in writes or filters.
Write a stage or move between pipelines
Section titled “Write a stage or move between pipelines”For an object type with configured pipelines:
| Input | Behavior |
|---|---|
$stage option ID | Select that exact stage. Its owning pipeline is derived automatically. |
$stage label with $pipeline | Resolve the label within the selected pipeline. Use the pipeline’s ID or name as the write hint. |
$stage option ID with $pipeline | Accepted if the stage belongs to the supplied pipeline; a mismatch is rejected. |
$stage label only | For opportunities, resolve within the default opportunity pipeline for backwards compatibility. Accounts, contacts, and custom objects require a pipeline hint for a label, even if that label is unique. |
$stage set to null | Clear the stage and pipeline membership. |
| Stage omitted | Preserve the existing stage on update. On create, leave the record unassigned. Opportunity Stage is optional. |
Stage labels are case-sensitive on write and must match the label from definitions exactly, including whitespace; stage filters ignore case, trim surrounding whitespace, and collapse repeated whitespace.
For example, select the Renewals pipeline’s Qualified stage:
{ "fields": { "$pipeline": "pd_renewals", "$stage": "Qualified" }}You can also send "$stage": "opt_renewals_qualified" without a pipeline hint,
or replace "pd_renewals" with the pipeline name "Renewals" in this example.
To move a record to another pipeline, write a stage belonging to that pipeline.
$pipeline is only a write hint: it is consumed, and its value is not stored
separately. A pipeline hint without a stage, or alongside a null stage, is
rejected. Pipeline slugs are not accepted as a separate form of write hint;
use an ID or name returned by definitions.
On reads, $stage.value is the stage option ID and $pipeline.value is its
pipeline ID, inside the record’s fields object. A configured but unassigned
record returns null values for both. This differs from fields being absent
because the object type has no configured pipelines or does not support them.
Use the individual Retrieve method to
verify a write; list methods may lag behind it.
See pipeline filters and pipeline errors for query syntax and resolving rejected inputs.
Type configuration
Section titled “Type configuration”Some field types have additional settings in typeConfiguration. Read them from the definitions endpoint.
| Type | Configuration | Description |
|---|---|---|
CURRENCY | currency | ISO 4217 currency code (e.g. "USD"). |
EMAIL | unique, multipleValues | Whether values must be unique across objects, and whether the field supports multiple email values. |
TELEPHONE | unique, multipleValues | Whether values must be unique and whether multiple values are supported. |
URL | unique, multipleValues | Whether values must be unique and whether multiple values are supported. |
SOCIAL_HANDLE | handleService | Platform hint: TWITTER, LINKEDIN, FACEBOOK, or INSTAGRAM. |
SINGLE_SELECT | options, parentFieldKey | Available options. For a dependent select, parentFieldKey names the field whose selected option determines which options apply. |
MULTI_SELECT | options | The list of available select options. |
Read the returned typeConfiguration for each field. Fields without additional configuration return an empty object.
System vs custom fields
Section titled “System vs custom fields”Fields and relationships can be either system or custom:
- System entries are built-in, prefixed with
$, and cannot be deleted. Check definitions for the object type’s fields and configuration (e.g.$nameon accounts,$accounton opportunities, and$pipelineon types with configured pipelines). - Custom entries are user-defined. Manage them in Lightfield settings or by asking the Lightfield agent, and read them through the API with the definitions endpoint.
Both system and custom entries follow the same value type rules described above.
ID prefixes
Section titled “ID prefixes”Lightfield uses prefixed IDs to distinguish between entity types:
| Entity | Prefix | Example |
|---|---|---|
| Account | acc_ | acc_abc123 |
| Contact | con_ | con_abc123 |
| Opportunity | opp_ | opp_abc123 |
| Member | mem_ | mem_abc123 |
| Field definition | ad_ | ad_abc123 |
| Relationship definition | rd_ | rd_abc123 |
| Select option | opt_ | opt_abc123 |
| Pipeline | pd_ | pd_abc123 |
| Attribute value | av_ | av_abc123 |
| Relationship value | rv_ | rv_abc123 |
Related documentation
Section titled “Related documentation”- In the app: configure fields and enrichment, relationships and pipelines.
- Endpoint contracts: account definitions and custom-object definitions.