API reference · v1.0.0
PRESHos API reference
Every endpoint, generated from the same registry that serves the API: parameters, request and response schemas, examples, and errors. 23 endpoints in 6 groups.
- Base URL
https://api.dev.preshos.com/v1- Authentication
Authorization: Bearer <key>Guide- Format
- JSON,
snake_case, UTC ISO-8601 timestamps, string ids - Pagination
- Cursor-based,
limit1–100. Guide
The API key's identity, workspace members, and teams.
Identify the API key
getMe/v1/meReturns the user the key acts as, its workspace, the key's scopes and expiry, and the rate limit. Use it to verify a key is working. Needs no scope.
- Scopes
- None — any valid key
- Behavior
- Read-onlyIdempotent
Headers
| Name | Type | Description |
|---|---|---|
Authorizationrequired | string | Bearer <api key>. The key acts as the user who created it. |
Response200
Key identity.
| Field | Type | Description |
|---|---|---|
data | object | — |
data.user | object | — |
data.user.id | integer | — |
data.user.name | string | null | — |
data.user.email | string | null | — |
data.user.title | string | null | — |
data.workspace | object | — |
data.workspace.id | integer | — |
data.workspace.slug | string | — |
data.workspace.name | string | — |
data.api_key | object | — |
data.api_key.id | string | — |
data.api_key.name | string | — |
data.api_key.type | "personal" | "operational" | — |
data.api_key.prefix | string | — |
data.api_key.scopes | string[] | — |
data.api_key.expires_at | string | null | — |
data.rate_limit | object | — |
data.rate_limit.requests_per_minute | integer | — |
Examples
curl https://api.dev.preshos.com/v1/me \
-H "Authorization: Bearer $PRESHOS_API_KEY"{
"data": {
"user": {
"id": 23,
"name": "Sam Rivera",
"email": "sam@example.com",
"title": "Operations Lead"
},
"workspace": {
"id": 9,
"slug": "acme",
"name": "Acme Corp"
},
"api_key": {
"id": "6f1c…",
"name": "CRM sync",
"type": "personal",
"prefix": "preshos_v1_Ab12Cd34Ef56",
"scopes": [
"records:read",
"records:write"
],
"expires_at": "2026-12-30T00:00:00.000Z"
},
"rate_limit": {
"requests_per_minute": 300
}
}
}Errors
Also 401 (key), 429 (rate limit), and 500, like every endpoint. All errors use the error envelope; see every error code.
List workspace members
listUsers/v1/usersLists people in this workspace (agents excluded) with their teams. Use user ids for owner fields and mentions. Users who may not read the whole member directory see only themselves.
- Scopes
users:read- Behavior
- Read-onlyIdempotent
Query parameters
| Name | Type | Description |
|---|---|---|
limit | integer1–100 · default: 50 | — |
cursor | stringmax 500 chars | — |
q | stringmax 200 chars | Match on name or email. |
include_inactive | "true" | "false"default: "false" | — |
Headers
| Name | Type | Description |
|---|---|---|
Authorizationrequired | string | Bearer <api key>. The key acts as the user who created it. |
Response200
Members.
| Field | Type | Description |
|---|---|---|
data | — | |
pagination | — |
Examples
curl https://api.dev.preshos.com/v1/users \
-H "Authorization: Bearer $PRESHOS_API_KEY"{
"data": [
{
"id": 17,
"name": "Jordan Lee",
"email": "jordan@example.com",
"title": "Account Director",
"active": true,
"teams": [
{
"id": 3,
"name": "Sales"
}
]
}
],
"pagination": {
"limit": 50,
"has_more": false,
"next_cursor": null
}
}Errors
| Status | Meaning | Codes |
|---|---|---|
| 400 | Invalid request: malformed JSON, failed validation, or an invalid filter/cursor. | validation_failed, invalid_cursor |
| 403 | Insufficient key scope, developer access revoked, or missing object/record/field permission. | developer_access_revoked, tenant_host_mismatch, insufficient_scope, permission_denied, search_not_supported |
Also 401 (key), 429 (rate limit), and 500, like every endpoint. All errors use the error envelope; see every error code.
Retrieve a member
getUser/v1/users/{user_id}Returns one workspace member by user id.
- Scopes
users:read- Behavior
- Read-onlyIdempotent
Path parameters
| Name | Type | Description |
|---|---|---|
user_idrequired | integer | Workspace user id. |
Headers
| Name | Type | Description |
|---|---|---|
Authorizationrequired | string | Bearer <api key>. The key acts as the user who created it. |
Examples
curl https://api.dev.preshos.com/v1/users/17 \
-H "Authorization: Bearer $PRESHOS_API_KEY"{
"data": {
"id": 17,
"name": "Jordan Lee",
"email": "jordan@example.com",
"title": "Account Director",
"active": true,
"teams": [
{
"id": 3,
"name": "Sales"
}
]
}
}Errors
| Status | Meaning | Codes |
|---|---|---|
| 403 | Insufficient key scope, developer access revoked, or missing object/record/field permission. | developer_access_revoked, tenant_host_mismatch, insufficient_scope, permission_denied, search_not_supported |
| 404 | Object type, record, or route not found (or not readable by this user). | user_not_found, workflow_not_found, comments_not_supported, user_not_found |
Also 401 (key), 429 (rate limit), and 500, like every endpoint. All errors use the error envelope; see every error code.
List teams
listTeams/v1/teamsLists the workspace's teams and their member ids (or only your own teams if you may not read all teams). Team membership determines "team records" access.
- Scopes
users:read- Behavior
- Read-onlyIdempotent
Headers
| Name | Type | Description |
|---|---|---|
Authorizationrequired | string | Bearer <api key>. The key acts as the user who created it. |
Examples
curl https://api.dev.preshos.com/v1/teams \
-H "Authorization: Bearer $PRESHOS_API_KEY"{
"data": [
{
"id": 3,
"name": "Sales",
"description": "New business and renewals",
"member_ids": [
17,
23
]
}
]
}Errors
| Status | Meaning | Codes |
|---|---|---|
| 403 | Insufficient key scope, developer access revoked, or missing object/record/field permission. | developer_access_revoked, tenant_host_mismatch, insufficient_scope, permission_denied, search_not_supported |
Also 401 (key), 429 (rate limit), and 500, like every endpoint. All errors use the error envelope; see every error code.
Discover object types, fields, relationships, and workflows for this workspace.
List object types
listObjects/v1/objectsLists the object types available in this workspace that the key's user can read: CRM objects, work items, business catalogs, and every custom object. capabilities says what the object supports through the API; permissions is advisory and says what this user may do.
- Scopes
records:read- Behavior
- Read-onlyIdempotent
Query parameters
| Name | Type | Description |
|---|---|---|
include_unreadable | "true" | "false" | Also return objects the user cannot read (default false). |
Headers
| Name | Type | Description |
|---|---|---|
Authorizationrequired | string | Bearer <api key>. The key acts as the user who created it. |
Examples
curl https://api.dev.preshos.com/v1/objects \
-H "Authorization: Bearer $PRESHOS_API_KEY"{
"data": [
{
"object_type": "companies",
"name": "Companies",
"singular_name": "Company",
"description": "Customer and prospect organizations.",
"kind": "built_in",
"category": "crm",
"display_field": "name",
"capabilities": {
"search": true,
"create": true,
"update": true,
"delete": false
},
"permissions": {
"create": {
"allowed": true,
"scopes": [
"owned_by_user"
]
},
"read": {
"allowed": true,
"scopes": [
"org_wide"
]
},
"update": {
"allowed": true,
"scopes": [
"owned_by_team"
]
},
"delete": {
"allowed": false,
"scopes": []
}
}
}
]
}Errors
| Status | Meaning | Codes |
|---|---|---|
| 400 | Invalid request: malformed JSON, failed validation, or an invalid filter/cursor. | validation_failed |
| 403 | Insufficient key scope, developer access revoked, or missing object/record/field permission. | developer_access_revoked, tenant_host_mismatch, insufficient_scope, permission_denied, search_not_supported |
Also 401 (key), 429 (rate limit), and 500, like every endpoint. All errors use the error envelope; see every error code.
Describe an object type
getObject/v1/objects/{object_type}Returns the full contract for one object type in this workspace: every field the user may read (key, type, required, editable, options, validation), relationships, the status workflow, and — for work items — the active work-item types. For work items pass work_item_type_id to include that type's configured fields.
- Scopes
records:read- Behavior
- Read-onlyIdempotent
Path parameters
| Name | Type | Description |
|---|---|---|
object_typerequired | string | Object type id from GET /v1/objects, e.g. companies, work-items, or a custom object id. |
Query parameters
| Name | Type | Description |
|---|---|---|
work_item_type_id | stringmax 100 chars | Work items only: include fields placed on this type. |
Headers
| Name | Type | Description |
|---|---|---|
Authorizationrequired | string | Bearer <api key>. The key acts as the user who created it. |
Response200
The object definition.
| Field | Type | Description |
|---|---|---|
data | — |
Examples
curl https://api.dev.preshos.com/v1/objects/companies \
-H "Authorization: Bearer $PRESHOS_API_KEY"{
"data": {
"object_type": "companies",
"name": "Companies",
"singular_name": "Company",
"description": "Customer and prospect organizations.",
"kind": "built_in",
"category": "crm",
"display_field": "name",
"capabilities": {
"search": true,
"create": true,
"update": true,
"delete": false
},
"permissions": {
"create": {
"allowed": true,
"scopes": [
"owned_by_user"
]
},
"read": {
"allowed": true,
"scopes": [
"org_wide"
]
},
"update": {
"allowed": true,
"scopes": [
"owned_by_team"
]
},
"delete": {
"allowed": false,
"scopes": []
}
},
"fields": [
{
"key": "name",
"name": "Company name",
"type": "text",
"required": true,
"editable": true,
"storage": "physical",
"storage_key": "name"
},
{
"key": "industry",
"name": "Industry",
"type": "select",
"required": false,
"editable": true,
"storage": "physical",
"storage_key": "industry",
"options": [
{
"value": "Manufacturing",
"label": "Manufacturing"
}
]
}
],
"relationships": [
{
"object": {
"key": "contacts",
"name": "Contacts"
},
"cardinality": "one_to_many",
"labels": []
}
],
"workflow": {
"id": "lifecycle-company",
"key": "company",
"name": "Company lifecycle"
}
}
}Errors
| Status | Meaning | Codes |
|---|---|---|
| 400 | Invalid request: malformed JSON, failed validation, or an invalid filter/cursor. | validation_failed |
| 403 | Insufficient key scope, developer access revoked, or missing object/record/field permission. | developer_access_revoked, tenant_host_mismatch, insufficient_scope, permission_denied, search_not_supported |
| 404 | Object type, record, or route not found (or not readable by this user). | object_not_available, user_not_found, workflow_not_found, comments_not_supported |
Also 401 (key), 429 (rate limit), and 500, like every endpoint. All errors use the error envelope; see every error code.
Retrieve a status workflow
getWorkflow/v1/workflows/{workflow_id}Returns a workflow's statuses and the transitions allowed from each. Use the status ids with POST …/records/{record_id}/status.
- Scopes
records:read- Behavior
- Read-onlyIdempotent
Path parameters
| Name | Type | Description |
|---|---|---|
workflow_idrequired | string1–200 chars | Workflow id from an object definition. |
Headers
| Name | Type | Description |
|---|---|---|
Authorizationrequired | string | Bearer <api key>. The key acts as the user who created it. |
Examples
curl https://api.dev.preshos.com/v1/workflows/wf-deliverable \
-H "Authorization: Bearer $PRESHOS_API_KEY"{
"data": {
"id": "wf-deliverable",
"key": "deliverable",
"name": "Deliverable",
"statuses": [
{
"id": "st-draft",
"name": "Draft",
"transitions": [
{
"id": "st-in-review",
"name": "In review"
}
]
},
{
"id": "st-in-review",
"name": "In review",
"transitions": [
{
"id": "st-approved",
"name": "Approved"
}
]
}
]
}
}Errors
| Status | Meaning | Codes |
|---|---|---|
| 403 | Insufficient key scope, developer access revoked, or missing object/record/field permission. | developer_access_revoked, tenant_host_mismatch, insufficient_scope, permission_denied, search_not_supported |
| 404 | Object type, record, or route not found (or not readable by this user). | user_not_found, workflow_not_found, comments_not_supported, workflow_not_found |
Also 401 (key), 429 (rate limit), and 500, like every endpoint. All errors use the error envelope; see every error code.
Create, read, update, delete, search, aggregate, and batch-edit records of any object type.
List records
listRecords/v1/objects/{object_type}/recordsLists records of one object type that the key's user may read. Results are scoped to the user's read grant: their own records, their teams' records, or the whole workspace. Use query parameters for simple filters; use POST …/records/search for richer queries.
- Scopes
records:read- Behavior
- Read-onlyIdempotent
Path parameters
| Name | Type | Description |
|---|---|---|
object_typerequired | string | Object type id from GET /v1/objects, e.g. companies, work-items, or a custom object id. |
Query parameters
| Name | Type | Description |
|---|---|---|
limit | integer1–100 · default: 25 | Page size (1–100). |
cursor | stringmax 500 chars | next_cursor from the previous page. |
q | stringmax 200 chars | Case-insensitive match on the display field. |
fields | stringmax 2000 chars | Comma-separated field keys to include in each record's fields (max 20). Omit for identity fields only. |
sort | stringmax 200 chars | Comma-separated field keys; prefix - for descending. Max 3. |
filter[<field>] | string or operator map | filter[field]=value or filter[field][op]=value (eq, ne, lt, lte, gt, gte, in, is_null). |
Headers
| Name | Type | Description |
|---|---|---|
Authorizationrequired | string | Bearer <api key>. The key acts as the user who created it. |
Response200
A page of records. Returns RecordList.
| Field | Type | Description |
|---|---|---|
data | — | |
pagination | — |
Examples
curl -G https://api.dev.preshos.com/v1/objects/companies/records \
-H "Authorization: Bearer $PRESHOS_API_KEY" \
--data-urlencode "limit=2" \
--data-urlencode "fields=name,domain" \
--data-urlencode "sort=-updated_at" \
--data-urlencode "filter[industry]=Manufacturing"{
"data": [
{
"object_type": "companies",
"id": "48213",
"display": "Acme Corporation",
"status": "customer",
"owner_user_id": 17,
"created_at": "2026-09-02T14:11:08.000Z",
"updated_at": "2026-09-30T09:45:51.000Z",
"version": "9a1b2c3d4e5f60718293a4b5c6d7e8f9",
"fields": {
"name": "Acme Corporation",
"domain": "acme.com",
"industry": "Manufacturing"
}
}
],
"pagination": {
"limit": 2,
"has_more": true,
"next_cursor": "eyJ2IjoxLCJvIjoyLCJxIjoiLi4uIn0"
}
}Errors
| Status | Meaning | Codes |
|---|---|---|
| 400 | Invalid request: malformed JSON, failed validation, or an invalid filter/cursor. | validation_failed, invalid_filter, invalid_cursor |
| 403 | Insufficient key scope, developer access revoked, or missing object/record/field permission. | developer_access_revoked, tenant_host_mismatch, insufficient_scope, permission_denied, search_not_supported, search_not_supported |
| 404 | Object type, record, or route not found (or not readable by this user). | object_not_available, user_not_found, workflow_not_found, comments_not_supported |
Also 401 (key), 429 (rate limit), and 500, like every endpoint. All errors use the error envelope; see every error code.
Search records
searchRecords/v1/objects/{object_type}/records/searchStructured search with operator filters, a text query, sorting, and field selection. Read-only: this POST never changes data and needs no Idempotency-Key.
- Scopes
records:read- Behavior
- Read-onlyIdempotent
Path parameters
| Name | Type | Description |
|---|---|---|
object_typerequired | string | Object type id from GET /v1/objects, e.g. companies, work-items, or a custom object id. |
Headers
| Name | Type | Description |
|---|---|---|
Authorizationrequired | string | Bearer <api key>. The key acts as the user who created it. |
Content-Type | string | application/json. |
Request bodyapplication/json · optional
| Field | Type | Description |
|---|---|---|
query | stringmax 200 chars | Case-insensitive match on the display field. |
filters | map of string | number | boolean | null | object | Map of field key → value or operator object. All filters are ANDed. A null value matches empty fields. |
fields | string[]max 20 items | Field keys to include in each record (max 20). |
sort | object[]max 3 items | — |
sort[].fieldrequired | string | — |
sort[].direction | "asc" | "desc"default: "asc" | — |
limit | integer1–100 · default: 25 | — |
cursor | stringmax 500 chars | — |
JSON Schema
{
"type": "object",
"properties": {
"query": {
"type": "string",
"maxLength": 200,
"description": "Case-insensitive match on the display field."
},
"filters": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"anyOf": [
{
"anyOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
}
]
},
{
"type": "null"
},
{
"type": "object",
"properties": {
"eq": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
}
]
},
"ne": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
}
]
},
"lt": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
}
]
},
"lte": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
}
]
},
"gt": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
}
]
},
"gte": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
}
]
},
"in": {
"minItems": 1,
"maxItems": 100,
"type": "array",
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
}
]
}
},
"is_null": {
"type": "boolean"
}
},
"additionalProperties": false
}
]
},
"description": "Map of field key → value or operator object. All filters are ANDed. A null value matches empty fields."
},
"fields": {
"maxItems": 20,
"type": "array",
"items": {
"type": "string"
},
"description": "Field keys to include in each record (max 20)."
},
"sort": {
"maxItems": 3,
"type": "array",
"items": {
"type": "object",
"properties": {
"field": {
"type": "string"
},
"direction": {
"default": "asc",
"type": "string",
"enum": [
"asc",
"desc"
]
}
},
"required": [
"field"
],
"additionalProperties": false
}
},
"limit": {
"default": 25,
"type": "integer",
"minimum": 1,
"maximum": 100
},
"cursor": {
"type": "string",
"maxLength": 500
}
},
"additionalProperties": false
}Response200
A page of matching records. Returns RecordList.
| Field | Type | Description |
|---|---|---|
data | — | |
pagination | — |
Examples
curl -X POST https://api.dev.preshos.com/v1/objects/companies/records/search \
-H "Authorization: Bearer $PRESHOS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"filters": {
"lifecycle_stage": {
"in": [
"customer",
"opportunity"
]
},
"created_at": {
"gte": "2026-01-01T00:00:00Z"
}
},
"fields": [
"name",
"domain",
"lifecycle_stage"
],
"sort": [
{
"field": "created_at",
"direction": "desc"
}
],
"limit": 50
}'{
"filters": {
"lifecycle_stage": {
"in": [
"customer",
"opportunity"
]
},
"created_at": {
"gte": "2026-01-01T00:00:00Z"
}
},
"fields": [
"name",
"domain",
"lifecycle_stage"
],
"sort": [
{
"field": "created_at",
"direction": "desc"
}
],
"limit": 50
}{
"data": [
{
"object_type": "companies",
"id": "48213",
"display": "Acme Corporation",
"status": "customer",
"owner_user_id": 17,
"created_at": "2026-09-02T14:11:08.000Z",
"updated_at": "2026-09-30T09:45:51.000Z",
"version": "9a1b2c3d4e5f60718293a4b5c6d7e8f9",
"fields": {
"name": "Acme Corporation",
"domain": "acme.com",
"industry": "Manufacturing"
}
}
],
"pagination": {
"limit": 50,
"has_more": false,
"next_cursor": null
}
}Errors
| Status | Meaning | Codes |
|---|---|---|
| 400 | Invalid request: malformed JSON, failed validation, or an invalid filter/cursor. | invalid_json, validation_failed, invalid_filter, invalid_cursor |
| 403 | Insufficient key scope, developer access revoked, or missing object/record/field permission. | developer_access_revoked, tenant_host_mismatch, insufficient_scope, permission_denied, search_not_supported, search_not_supported |
| 404 | Object type, record, or route not found (or not readable by this user). | object_not_available, user_not_found, workflow_not_found, comments_not_supported |
Also 401 (key), 429 (rate limit), and 500, like every endpoint. All errors use the error envelope; see every error code.
Aggregate records
aggregateRecords/v1/objects/{object_type}/records/aggregateGroups the filtered records (up to 3 group_by fields) and computes 1–8 metrics (count, count_distinct, sum, avg, min, max) in the database over the complete filtered set. Omit field for count(*).
- Scopes
records:read- Behavior
- Read-onlyIdempotent
Path parameters
| Name | Type | Description |
|---|---|---|
object_typerequired | string | Object type id from GET /v1/objects, e.g. companies, work-items, or a custom object id. |
Headers
| Name | Type | Description |
|---|---|---|
Authorizationrequired | string | Bearer <api key>. The key acts as the user who created it. |
Content-Typerequired | string | application/json. |
Request bodyapplication/json · required
| Field | Type | Description |
|---|---|---|
query | stringmax 200 chars | — |
filters | map of string | number | boolean | null | object | Map of field key → value or operator object. All filters are ANDed. A null value matches empty fields. |
group_by | string[]max 3 items | — |
metricsrequired | object[]1–8 items | — |
metrics[].namerequired | string | Result column name. |
metrics[].oprequired | "count" | "count_distinct" | "sum" | "avg" | "min" | "max" | — |
metrics[].field | string | — |
order_by | object | — |
order_by.namerequired | string | — |
order_by.directionrequired | "asc" | "desc" | — |
limit | integer1–100 · default: 25 | Maximum groups per page. |
cursor | stringmax 500 chars | — |
JSON Schema
{
"type": "object",
"properties": {
"query": {
"type": "string",
"maxLength": 200
},
"filters": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"anyOf": [
{
"anyOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
}
]
},
{
"type": "null"
},
{
"type": "object",
"properties": {
"eq": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
}
]
},
"ne": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
}
]
},
"lt": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
}
]
},
"lte": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
}
]
},
"gt": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
}
]
},
"gte": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
}
]
},
"in": {
"minItems": 1,
"maxItems": 100,
"type": "array",
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
}
]
}
},
"is_null": {
"type": "boolean"
}
},
"additionalProperties": false
}
]
},
"description": "Map of field key → value or operator object. All filters are ANDed. A null value matches empty fields."
},
"group_by": {
"maxItems": 3,
"type": "array",
"items": {
"type": "string"
}
},
"metrics": {
"minItems": 1,
"maxItems": 8,
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"pattern": "^[a-z][a-z0-9_]{0,39}$",
"description": "Result column name."
},
"op": {
"type": "string",
"enum": [
"count",
"count_distinct",
"sum",
"avg",
"min",
"max"
]
},
"field": {
"type": "string"
}
},
"required": [
"name",
"op"
],
"additionalProperties": false
}
},
"order_by": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"direction": {
"type": "string",
"enum": [
"asc",
"desc"
]
}
},
"required": [
"name",
"direction"
],
"additionalProperties": false
},
"limit": {
"default": 25,
"type": "integer",
"minimum": 1,
"maximum": 100,
"description": "Maximum groups per page."
},
"cursor": {
"type": "string",
"maxLength": 500
}
},
"required": [
"metrics"
],
"additionalProperties": false
}Response200
Aggregated groups.
| Field | Type | Description |
|---|---|---|
data | object[] | — |
pagination | — |
Examples
curl -X POST https://api.dev.preshos.com/v1/objects/work-items/records/aggregate \
-H "Authorization: Bearer $PRESHOS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"filters": {
"work_item_type_id": "deliverable"
},
"group_by": [
"custom_status_id"
],
"metrics": [
{
"name": "total",
"op": "count"
}
],
"order_by": {
"name": "total",
"direction": "desc"
}
}'{
"filters": {
"work_item_type_id": "deliverable"
},
"group_by": [
"custom_status_id"
],
"metrics": [
{
"name": "total",
"op": "count"
}
],
"order_by": {
"name": "total",
"direction": "desc"
}
}{
"data": [
{
"custom_status_id": "st_in_progress",
"total": 42
}
],
"pagination": {
"limit": 25,
"has_more": false,
"next_cursor": null
}
}Errors
| Status | Meaning | Codes |
|---|---|---|
| 400 | Invalid request: malformed JSON, failed validation, or an invalid filter/cursor. | invalid_json, validation_failed, invalid_filter, invalid_cursor |
| 403 | Insufficient key scope, developer access revoked, or missing object/record/field permission. | developer_access_revoked, tenant_host_mismatch, insufficient_scope, permission_denied, search_not_supported, search_not_supported |
| 404 | Object type, record, or route not found (or not readable by this user). | object_not_available, user_not_found, workflow_not_found, comments_not_supported |
Also 401 (key), 429 (rate limit), and 500, like every endpoint. All errors use the error envelope; see every error code.
Apply a batch of changes atomically
batchRecords/v1/objects/{object_type}/records/batchApplies 1–100 operations to one object type in a single transaction: create, update, status, delete, upsert (match-and-update or create), link, and unlink. Every operation is authorized as the key's user before anything is written; if any operation fails, nothing is applied. Each existing record may appear once. delete operations also require the records:delete scope.
- Scopes
records:write- Behavior
- Changes dataDestructiveNot idempotentIdempotency-Key
Path parameters
| Name | Type | Description |
|---|---|---|
object_typerequired | string | Object type id from GET /v1/objects, e.g. companies, work-items, or a custom object id. |
Headers
| Name | Type | Description |
|---|---|---|
Authorizationrequired | string | Bearer <api key>. The key acts as the user who created it. |
Content-Typerequired | string | application/json. |
Idempotency-Key | stringmax 255 chars | Unique key (1–255 visible ASCII characters, e.g. a UUID). Retrying with the same key and body replays the first successful response for 24 hours. |
Request bodyapplication/json · required
| Field | Type | Description |
|---|---|---|
operationsrequired | object[]1–100 items | — |
Each item of operations[] is one of these shapes, selected by action:
| Variant | Fields |
|---|---|
action: "create" | fieldsobject |
action: "update" | idstringfieldsobject |
action: "status" | idstringstatus_idstring |
action: "delete" | idstring |
action: "upsert" | targetstringmatchobjectfields?object |
action: "link" | idstringassociation_idstringtarget_idstring |
action: "unlink" | idstringassociation_idstringtarget_idstring |
JSON Schema
{
"type": "object",
"properties": {
"operations": {
"minItems": 1,
"maxItems": 100,
"type": "array",
"items": {
"oneOf": [
{
"type": "object",
"properties": {
"action": {
"type": "string",
"const": "create"
},
"fields": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {},
"description": "Field values keyed by field key (see `GET /v1/objects/{object_type}`). Custom fields go under `custom_fields`."
}
},
"required": [
"action",
"fields"
],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"action": {
"type": "string",
"const": "update"
},
"id": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Canonical record `id`."
},
"fields": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {},
"description": "Field values keyed by field key (see `GET /v1/objects/{object_type}`). Custom fields go under `custom_fields`."
}
},
"required": [
"action",
"id",
"fields"
],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"action": {
"type": "string",
"const": "status"
},
"id": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Canonical record `id`."
},
"status_id": {
"type": "string",
"minLength": 1,
"maxLength": 200
}
},
"required": [
"action",
"id",
"status_id"
],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"action": {
"type": "string",
"const": "delete"
},
"id": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Canonical record `id`."
}
},
"required": [
"action",
"id"
],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"action": {
"type": "string",
"const": "upsert"
},
"target": {
"type": "string",
"minLength": 1,
"maxLength": 100,
"description": "Import target: the object type, or the work-item type slug for work items."
},
"match": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {},
"description": "Match keys, e.g. `{ \"domain\": \"acme.com\" }`."
},
"fields": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
}
},
"required": [
"action",
"target",
"match"
],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"action": {
"type": "string",
"const": "link"
},
"id": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Canonical record `id`."
},
"association_id": {
"type": "string",
"minLength": 1
},
"target_id": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Canonical record `id`."
}
},
"required": [
"action",
"id",
"association_id",
"target_id"
],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"action": {
"type": "string",
"const": "unlink"
},
"id": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Canonical record `id`."
},
"association_id": {
"type": "string",
"minLength": 1
},
"target_id": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Canonical record `id`."
}
},
"required": [
"action",
"id",
"association_id",
"target_id"
],
"additionalProperties": false
}
]
}
}
},
"required": [
"operations"
],
"additionalProperties": false
}Response200
Per-operation results; all operations succeeded.
| Field | Type | Description |
|---|---|---|
data | object | — |
data.status | "completed" | — |
data.object_type | string | — |
data.count | integer | — |
data.atomic | true | — |
data.results | object[] | — |
data.results[].action | string | — |
data.results[].id | string | — |
data.results[].status | "succeeded" | — |
Examples
curl -X POST https://api.dev.preshos.com/v1/objects/companies/records/batch \
-H "Authorization: Bearer $PRESHOS_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"operations": [
{
"action": "upsert",
"target": "companies",
"match": {
"domain": "acme.com"
},
"fields": {
"name": "Acme Corporation"
}
},
{
"action": "update",
"id": "48213",
"fields": {
"industry": "Aerospace"
}
},
{
"action": "status",
"id": "48214",
"status_id": "customer"
}
]
}'{
"operations": [
{
"action": "upsert",
"target": "companies",
"match": {
"domain": "acme.com"
},
"fields": {
"name": "Acme Corporation"
}
},
{
"action": "update",
"id": "48213",
"fields": {
"industry": "Aerospace"
}
},
{
"action": "status",
"id": "48214",
"status_id": "customer"
}
]
}{
"data": {
"status": "completed",
"object_type": "companies",
"count": 3,
"atomic": true,
"results": [
{
"action": "create",
"id": "48300",
"status": "succeeded"
},
{
"action": "update",
"id": "48213",
"status": "succeeded"
},
{
"action": "status",
"id": "48214",
"status": "succeeded"
}
]
}
}Errors
| Status | Meaning | Codes |
|---|---|---|
| 400 | Invalid request: malformed JSON, failed validation, or an invalid filter/cursor. | invalid_json, validation_failed |
| 403 | Insufficient key scope, developer access revoked, or missing object/record/field permission. | developer_access_revoked, tenant_host_mismatch, insufficient_scope, permission_denied, search_not_supported, write_not_supported |
| 404 | Object type, record, or route not found (or not readable by this user). | object_not_available, user_not_found, workflow_not_found, comments_not_supported |
| 409 | Conflict, e.g. an Idempotency-Key request still in progress or a uniqueness violation. | conflict, idempotency_key_in_use, conflict |
| 412 | If-Match / expected_version does not match the current record version. | stale_record |
| 422 | The write was rejected by validation or workflow rules. | invalid_write, idempotency_key_reused |
Also 401 (key), 429 (rate limit), and 500, like every endpoint. All errors use the error envelope; see every error code.
Create a record
createRecord/v1/objects/{object_type}/recordsCreates a record as the key's user. Field rules from the object definition apply: required fields, select options, validation, and field permissions. Status is set to the workflow's initial status. Owner defaults to the key's user. Work items require fields.work_item_type_id. Send an Idempotency-Key to retry safely.
- Scopes
records:write- Behavior
- Changes dataNot idempotentIdempotency-Key
Path parameters
| Name | Type | Description |
|---|---|---|
object_typerequired | string | Object type id from GET /v1/objects, e.g. companies, work-items, or a custom object id. |
Headers
| Name | Type | Description |
|---|---|---|
Authorizationrequired | string | Bearer <api key>. The key acts as the user who created it. |
Content-Typerequired | string | application/json. |
Idempotency-Key | stringmax 255 chars | Unique key (1–255 visible ASCII characters, e.g. a UUID). Retrying with the same key and body replays the first successful response for 24 hours. |
Request bodyapplication/json · required
| Field | Type | Description |
|---|---|---|
fieldsrequired | object | Field values keyed by field key (see GET /v1/objects/{object_type}). Custom fields go under custom_fields. |
JSON Schema
{
"type": "object",
"properties": {
"fields": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {},
"description": "Field values keyed by field key (see `GET /v1/objects/{object_type}`). Custom fields go under `custom_fields`."
}
},
"required": [
"fields"
],
"additionalProperties": false
}Response201
The created record. Returns RecordResponse.
| Field | Type | Description |
|---|---|---|
data | — |
Examples
curl -X POST https://api.dev.preshos.com/v1/objects/companies/records \
-H "Authorization: Bearer $PRESHOS_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"fields": {
"name": "Acme Corporation",
"domain": "acme.com",
"industry": "Manufacturing"
}
}'{
"fields": {
"name": "Acme Corporation",
"domain": "acme.com",
"industry": "Manufacturing"
}
}{
"data": {
"object_type": "companies",
"id": "48213",
"display": "Acme Corporation",
"status": "customer",
"owner_user_id": 17,
"created_at": "2026-09-02T14:11:08.000Z",
"updated_at": "2026-09-30T09:45:51.000Z",
"version": "9a1b2c3d4e5f60718293a4b5c6d7e8f9",
"fields": {
"name": "Acme Corporation",
"domain": "acme.com",
"industry": "Manufacturing"
}
}
}Errors
| Status | Meaning | Codes |
|---|---|---|
| 400 | Invalid request: malformed JSON, failed validation, or an invalid filter/cursor. | invalid_json, validation_failed, invalid_filter |
| 403 | Insufficient key scope, developer access revoked, or missing object/record/field permission. | developer_access_revoked, tenant_host_mismatch, insufficient_scope, permission_denied, search_not_supported, write_not_supported |
| 404 | Object type, record, or route not found (or not readable by this user). | object_not_available, user_not_found, workflow_not_found, comments_not_supported |
| 409 | Conflict, e.g. an Idempotency-Key request still in progress or a uniqueness violation. | conflict, idempotency_key_in_use, conflict |
| 422 | The write was rejected by validation or workflow rules. | invalid_write, idempotency_key_reused |
Also 401 (key), 429 (rate limit), and 500, like every endpoint. All errors use the error envelope; see every error code.
Retrieve a record
getRecord/v1/objects/{object_type}/records/{record_id}Returns one record with every field the key's user may read. The ETag header carries the record version for conditional updates. Records the user may not read return 404.
- Scopes
records:read- Behavior
- Read-onlyIdempotent
Path parameters
| Name | Type | Description |
|---|---|---|
object_typerequired | string | Object type id from GET /v1/objects, e.g. companies, work-items, or a custom object id. |
record_idrequired | string1–200 chars | Canonical record id. |
Query parameters
| Name | Type | Description |
|---|---|---|
fields | stringmax 2000 chars | Comma-separated field keys to return (max 20). Omit to return every field you may read. |
Headers
| Name | Type | Description |
|---|---|---|
Authorizationrequired | string | Bearer <api key>. The key acts as the user who created it. |
Examples
curl https://api.dev.preshos.com/v1/objects/companies/records/48213 \
-H "Authorization: Bearer $PRESHOS_API_KEY"{
"data": {
"object_type": "companies",
"id": "48213",
"display": "Acme Corporation",
"status": "customer",
"owner_user_id": 17,
"created_at": "2026-09-02T14:11:08.000Z",
"updated_at": "2026-09-30T09:45:51.000Z",
"version": "9a1b2c3d4e5f60718293a4b5c6d7e8f9",
"fields": {
"name": "Acme Corporation",
"domain": "acme.com",
"industry": "Manufacturing"
}
}
}Errors
| Status | Meaning | Codes |
|---|---|---|
| 400 | Invalid request: malformed JSON, failed validation, or an invalid filter/cursor. | validation_failed, invalid_filter |
| 403 | Insufficient key scope, developer access revoked, or missing object/record/field permission. | developer_access_revoked, tenant_host_mismatch, insufficient_scope, permission_denied, search_not_supported |
| 404 | Object type, record, or route not found (or not readable by this user). | object_not_available, record_not_found, user_not_found, workflow_not_found, comments_not_supported |
Also 401 (key), 429 (rate limit), and 500, like every endpoint. All errors use the error envelope; see every error code.
Update a record
updateRecord/v1/objects/{object_type}/records/{record_id}Partially updates a record: only the fields you send change. Unknown, read-only, or status fields reject the whole request (change status with the status endpoint). Send If-Match: "<version>" to fail with 412 if the record changed since you read it.
- Scopes
records:write- Behavior
- Changes dataIdempotentIdempotency-KeyIf-Match
Path parameters
| Name | Type | Description |
|---|---|---|
object_typerequired | string | Object type id from GET /v1/objects, e.g. companies, work-items, or a custom object id. |
record_idrequired | string1–200 chars | Canonical record id. |
Headers
| Name | Type | Description |
|---|---|---|
Authorizationrequired | string | Bearer <api key>. The key acts as the user who created it. |
Content-Typerequired | string | application/json. |
Idempotency-Key | stringmax 255 chars | Unique key (1–255 visible ASCII characters, e.g. a UUID). Retrying with the same key and body replays the first successful response for 24 hours. |
If-Match | string | The record version (or its ETag). The update fails with 412 if the record changed. |
Request bodyapplication/json · required
| Field | Type | Description |
|---|---|---|
fieldsrequired | object | Field values keyed by field key (see GET /v1/objects/{object_type}). Custom fields go under custom_fields. |
expected_version | stringmax 200 chars | Alternative to the If-Match header. |
JSON Schema
{
"type": "object",
"properties": {
"fields": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {},
"description": "Field values keyed by field key (see `GET /v1/objects/{object_type}`). Custom fields go under `custom_fields`."
},
"expected_version": {
"type": "string",
"maxLength": 200,
"description": "Alternative to the If-Match header."
}
},
"required": [
"fields"
],
"additionalProperties": false
}Response200
The updated record. Returns RecordResponse.
| Field | Type | Description |
|---|---|---|
data | — |
Examples
curl -X PATCH https://api.dev.preshos.com/v1/objects/companies/records/48213 \
-H "Authorization: Bearer $PRESHOS_API_KEY" \
-H "Content-Type: application/json" \
-H 'If-Match: "9a1b2c3d4e5f60718293a4b5c6d7e8f9"' \
-d '{
"fields": {
"industry": "Aerospace",
"custom_fields": {
"tier": "enterprise"
}
}
}'{
"fields": {
"industry": "Aerospace",
"custom_fields": {
"tier": "enterprise"
}
}
}{
"data": {
"object_type": "companies",
"id": "48213",
"display": "Acme Corporation",
"status": "customer",
"owner_user_id": 17,
"created_at": "2026-09-02T14:11:08.000Z",
"updated_at": "2026-09-30T09:45:51.000Z",
"version": "9a1b2c3d4e5f60718293a4b5c6d7e8f9",
"fields": {
"name": "Acme Corporation",
"domain": "acme.com",
"industry": "Aerospace"
}
}
}Errors
| Status | Meaning | Codes |
|---|---|---|
| 400 | Invalid request: malformed JSON, failed validation, or an invalid filter/cursor. | invalid_json, validation_failed, invalid_filter |
| 403 | Insufficient key scope, developer access revoked, or missing object/record/field permission. | developer_access_revoked, tenant_host_mismatch, insufficient_scope, permission_denied, search_not_supported, write_not_supported |
| 404 | Object type, record, or route not found (or not readable by this user). | object_not_available, record_not_found, user_not_found, workflow_not_found, comments_not_supported |
| 412 | If-Match / expected_version does not match the current record version. | stale_record |
| 422 | The write was rejected by validation or workflow rules. | invalid_write, idempotency_key_reused |
Also 401 (key), 429 (rate limit), and 500, like every endpoint. All errors use the error envelope; see every error code.
Delete a record
deleteRecord/v1/objects/{object_type}/records/{record_id}Permanently deletes a record (CRM association rows are removed with it). Requires the records:delete scope, delete permission, and an object that allows deletion (capabilities.delete).
- Scopes
records:delete- Behavior
- Changes dataDestructiveNot idempotent
Path parameters
| Name | Type | Description |
|---|---|---|
object_typerequired | string | Object type id from GET /v1/objects, e.g. companies, work-items, or a custom object id. |
record_idrequired | string1–200 chars | Canonical record id. |
Headers
| Name | Type | Description |
|---|---|---|
Authorizationrequired | string | Bearer <api key>. The key acts as the user who created it. |
Response200
Deletion confirmation.
| Field | Type | Description |
|---|---|---|
data | object | — |
data.id | string | — |
data.object_type | string | — |
data.deleted | true | — |
Examples
curl -X DELETE https://api.dev.preshos.com/v1/objects/companies/records/48213 \
-H "Authorization: Bearer $PRESHOS_API_KEY"{
"data": {
"id": "48213",
"object_type": "companies",
"deleted": true
}
}Errors
| Status | Meaning | Codes |
|---|---|---|
| 403 | Insufficient key scope, developer access revoked, or missing object/record/field permission. | developer_access_revoked, tenant_host_mismatch, insufficient_scope, permission_denied, search_not_supported, write_not_supported |
| 404 | Object type, record, or route not found (or not readable by this user). | object_not_available, record_not_found, user_not_found, workflow_not_found, comments_not_supported |
| 409 | Conflict, e.g. an Idempotency-Key request still in progress or a uniqueness violation. | conflict, conflict |
| 422 | The write was rejected by validation or workflow rules. | invalid_write |
Also 401 (key), 429 (rate limit), and 500, like every endpoint. All errors use the error envelope; see every error code.
Change a record's status
setRecordStatus/v1/objects/{object_type}/records/{record_id}/statusMoves a record to another status of its workflow. The transition must be allowed by the workflow (see GET /v1/workflows/{workflow_id}); approval-gated statuses follow the same rules as the app.
- Scopes
records:write- Behavior
- Changes dataIdempotentIdempotency-Key
Path parameters
| Name | Type | Description |
|---|---|---|
object_typerequired | string | Object type id from GET /v1/objects, e.g. companies, work-items, or a custom object id. |
record_idrequired | string1–200 chars | Canonical record id. |
Headers
| Name | Type | Description |
|---|---|---|
Authorizationrequired | string | Bearer <api key>. The key acts as the user who created it. |
Content-Typerequired | string | application/json. |
Idempotency-Key | stringmax 255 chars | Unique key (1–255 visible ASCII characters, e.g. a UUID). Retrying with the same key and body replays the first successful response for 24 hours. |
Request bodyapplication/json · required
| Field | Type | Description |
|---|---|---|
status_idrequired | string1–200 chars | Target workflow status id. |
JSON Schema
{
"type": "object",
"properties": {
"status_id": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Target workflow status id."
}
},
"required": [
"status_id"
],
"additionalProperties": false
}Response200
The record after the transition. Returns RecordResponse.
| Field | Type | Description |
|---|---|---|
data | — |
Examples
curl -X POST https://api.dev.preshos.com/v1/objects/companies/records/48213/status \
-H "Authorization: Bearer $PRESHOS_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"status_id": "customer"
}'{
"status_id": "customer"
}{
"data": {
"object_type": "companies",
"id": "48213",
"display": "Acme Corporation",
"status": "customer",
"owner_user_id": 17,
"created_at": "2026-09-02T14:11:08.000Z",
"updated_at": "2026-09-30T09:45:51.000Z",
"version": "9a1b2c3d4e5f60718293a4b5c6d7e8f9",
"fields": {
"name": "Acme Corporation",
"domain": "acme.com",
"industry": "Manufacturing"
}
}
}Errors
| Status | Meaning | Codes |
|---|---|---|
| 400 | Invalid request: malformed JSON, failed validation, or an invalid filter/cursor. | invalid_json, validation_failed |
| 403 | Insufficient key scope, developer access revoked, or missing object/record/field permission. | developer_access_revoked, tenant_host_mismatch, insufficient_scope, permission_denied, search_not_supported, write_not_supported |
| 404 | Object type, record, or route not found (or not readable by this user). | object_not_available, record_not_found, user_not_found, workflow_not_found, comments_not_supported |
| 422 | The write was rejected by validation or workflow rules. | invalid_write, idempotency_key_reused |
Also 401 (key), 429 (rate limit), and 500, like every endpoint. All errors use the error envelope; see every error code.
Discover and traverse relationships; link and unlink records.
Discover a record's relationships
listRelationships/v1/objects/{object_type}/records/{record_id}/relationshipsLists the relationships registered for this record's object type, including which ones support link/unlink. Use an id from this list to read linked records.
- Scopes
records:read- Behavior
- Read-onlyIdempotent
Path parameters
| Name | Type | Description |
|---|---|---|
object_typerequired | string | Object type id from GET /v1/objects, e.g. companies, work-items, or a custom object id. |
record_idrequired | string1–200 chars | Canonical record id. |
Headers
| Name | Type | Description |
|---|---|---|
Authorizationrequired | string | Bearer <api key>. The key acts as the user who created it. |
Response200
Relationship definitions.
| Field | Type | Description |
|---|---|---|
data | object[] | — |
data[].id | string | Association id. |
data[].label | string | null | — |
data[].object_type | string | Related object type. |
data[].cardinality | string | one_to_one, many_to_one, one_to_many, or many_to_many. |
data[].can_link | boolean | Whether link/unlink is supported through the API. |
data[].source_field | string | null | Reference field on this record, when the relationship is a field. |
Examples
curl https://api.dev.preshos.com/v1/objects/companies/records/48213/relationships \
-H "Authorization: Bearer $PRESHOS_API_KEY"{
"data": [
{
"id": "214",
"label": "Contacts",
"object_type": "contacts",
"cardinality": "one_to_many",
"can_link": false,
"source_field": null
},
{
"id": "219",
"label": "Parent company",
"object_type": "companies",
"cardinality": "many_to_one",
"can_link": true,
"source_field": "parent_company_id"
}
]
}Errors
| Status | Meaning | Codes |
|---|---|---|
| 403 | Insufficient key scope, developer access revoked, or missing object/record/field permission. | developer_access_revoked, tenant_host_mismatch, insufficient_scope, permission_denied, search_not_supported |
| 404 | Object type, record, or route not found (or not readable by this user). | object_not_available, record_not_found, user_not_found, workflow_not_found, comments_not_supported |
Also 401 (key), 429 (rate limit), and 500, like every endpoint. All errors use the error envelope; see every error code.
List linked records
listRelatedRecords/v1/objects/{object_type}/records/{record_id}/relationships/{association_id}Returns the records linked to this record through one relationship (field references, CRM associations, and custom links). Requires workspace-wide read on the related object type.
- Scopes
records:read- Behavior
- Read-onlyIdempotent
Path parameters
| Name | Type | Description |
|---|---|---|
object_typerequired | string | Object type id from GET /v1/objects, e.g. companies, work-items, or a custom object id. |
record_idrequired | string1–200 chars | Canonical record id. |
association_idrequired | string1–100 chars | Relationship id from the discovery endpoint. |
Query parameters
| Name | Type | Description |
|---|---|---|
limit | integer1–100 · default: 25 | — |
cursor | stringmax 500 chars | — |
Headers
| Name | Type | Description |
|---|---|---|
Authorizationrequired | string | Bearer <api key>. The key acts as the user who created it. |
Response200
Linked records.
| Field | Type | Description |
|---|---|---|
object_type | string | — |
data | — | |
pagination | — |
Examples
curl https://api.dev.preshos.com/v1/objects/companies/records/48213/relationships/214 \
-H "Authorization: Bearer $PRESHOS_API_KEY"{
"object_type": "contacts",
"data": [
{
"object_type": "contacts",
"id": "9001",
"display": "Ada Lovelace",
"status": null,
"owner_user_id": 17,
"created_at": "2026-08-01T10:00:00.000Z",
"updated_at": "2026-09-01T10:00:00.000Z"
}
],
"pagination": {
"limit": 25,
"has_more": false,
"next_cursor": null
}
}Errors
| Status | Meaning | Codes |
|---|---|---|
| 400 | Invalid request: malformed JSON, failed validation, or an invalid filter/cursor. | validation_failed, invalid_cursor |
| 403 | Insufficient key scope, developer access revoked, or missing object/record/field permission. | developer_access_revoked, tenant_host_mismatch, insufficient_scope, permission_denied, search_not_supported |
| 404 | Object type, record, or route not found (or not readable by this user). | object_not_available, record_not_found, user_not_found, workflow_not_found, comments_not_supported |
Also 401 (key), 429 (rate limit), and 500, like every endpoint. All errors use the error envelope; see every error code.
Link a record
linkRecord/v1/objects/{object_type}/records/{record_id}/relationships/{association_id}Links a target record through a relationship whose can_link is true. Reference relationships set the reference field (replacing the previous target); custom many-to-many relationships add a link. Requires update permission on this record and read permission on the target.
- Scopes
records:write- Behavior
- Changes dataIdempotentIdempotency-Key
Path parameters
| Name | Type | Description |
|---|---|---|
object_typerequired | string | Object type id from GET /v1/objects, e.g. companies, work-items, or a custom object id. |
record_idrequired | string1–200 chars | Canonical record id. |
association_idrequired | string1–100 chars | Relationship id from the discovery endpoint. |
Headers
| Name | Type | Description |
|---|---|---|
Authorizationrequired | string | Bearer <api key>. The key acts as the user who created it. |
Content-Typerequired | string | application/json. |
Idempotency-Key | stringmax 255 chars | Unique key (1–255 visible ASCII characters, e.g. a UUID). Retrying with the same key and body replays the first successful response for 24 hours. |
Request bodyapplication/json · required
| Field | Type | Description |
|---|---|---|
target_idrequired | string1–200 chars | Id of the record to link. |
JSON Schema
{
"type": "object",
"properties": {
"target_id": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Id of the record to link."
}
},
"required": [
"target_id"
],
"additionalProperties": false
}Response200
Link result.
| Field | Type | Description |
|---|---|---|
data | object | — |
data.id | string | — |
data.association_id | string | — |
data.target_id | string | — |
data.linked | boolean | — |
Examples
curl -X POST https://api.dev.preshos.com/v1/objects/companies/records/48213/relationships/219 \
-H "Authorization: Bearer $PRESHOS_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"target_id": "48001"
}'{
"target_id": "48001"
}{
"data": {
"id": "48213",
"association_id": "219",
"target_id": "48001",
"linked": true
}
}Errors
| Status | Meaning | Codes |
|---|---|---|
| 400 | Invalid request: malformed JSON, failed validation, or an invalid filter/cursor. | invalid_json, validation_failed |
| 403 | Insufficient key scope, developer access revoked, or missing object/record/field permission. | developer_access_revoked, tenant_host_mismatch, insufficient_scope, permission_denied, search_not_supported, write_not_supported |
| 404 | Object type, record, or route not found (or not readable by this user). | object_not_available, record_not_found, user_not_found, workflow_not_found, comments_not_supported |
| 412 | If-Match / expected_version does not match the current record version. | stale_record |
| 422 | The write was rejected by validation or workflow rules. | invalid_write, idempotency_key_reused |
Also 401 (key), 429 (rate limit), and 500, like every endpoint. All errors use the error envelope; see every error code.
Unlink a record
unlinkRecord/v1/objects/{object_type}/records/{record_id}/relationships/{association_id}/{target_id}Removes a link created through a can_link relationship. A reference field is cleared only if it still points to target_id (otherwise 412).
- Scopes
records:write- Behavior
- Changes dataIdempotent
Path parameters
| Name | Type | Description |
|---|---|---|
object_typerequired | string | Object type id from GET /v1/objects, e.g. companies, work-items, or a custom object id. |
record_idrequired | string1–200 chars | Canonical record id. |
association_idrequired | string1–100 chars | Relationship id from the discovery endpoint. |
target_idrequired | string1–200 chars | Canonical record id. |
Headers
| Name | Type | Description |
|---|---|---|
Authorizationrequired | string | Bearer <api key>. The key acts as the user who created it. |
Response200
Unlink result.
| Field | Type | Description |
|---|---|---|
data | object | — |
data.id | string | — |
data.association_id | string | — |
data.target_id | string | — |
data.linked | boolean | — |
Examples
curl -X DELETE https://api.dev.preshos.com/v1/objects/companies/records/48213/relationships/219/48001 \
-H "Authorization: Bearer $PRESHOS_API_KEY"{
"data": {
"id": "48213",
"association_id": "219",
"target_id": "48001",
"linked": false
}
}Errors
| Status | Meaning | Codes |
|---|---|---|
| 400 | Invalid request: malformed JSON, failed validation, or an invalid filter/cursor. | validation_failed |
| 403 | Insufficient key scope, developer access revoked, or missing object/record/field permission. | developer_access_revoked, tenant_host_mismatch, insufficient_scope, permission_denied, search_not_supported, write_not_supported |
| 404 | Object type, record, or route not found (or not readable by this user). | object_not_available, record_not_found, user_not_found, workflow_not_found, comments_not_supported |
| 412 | If-Match / expected_version does not match the current record version. | stale_record |
| 422 | The write was rejected by validation or workflow rules. | invalid_write |
Also 401 (key), 429 (rate limit), and 500, like every endpoint. All errors use the error envelope; see every error code.
Read and post record comments.
List a record's comments
listComments/v1/objects/{object_type}/records/{record_id}/commentsReturns the record's comment thread as plain text, newest first by default. Available for work items, companies, contacts, deals, meetings, and events.
- Scopes
comments:read- Behavior
- Read-onlyIdempotent
Path parameters
| Name | Type | Description |
|---|---|---|
object_typerequired | string | Object type id from GET /v1/objects, e.g. companies, work-items, or a custom object id. |
record_idrequired | string1–200 chars | Canonical record id. |
Query parameters
| Name | Type | Description |
|---|---|---|
limit | integer1–100 · default: 50 | — |
cursor | stringmax 200 chars | next_cursor from the previous page. |
order | "desc" | "asc"default: "desc" | desc = newest first. |
Headers
| Name | Type | Description |
|---|---|---|
Authorizationrequired | string | Bearer <api key>. The key acts as the user who created it. |
Response200
A page of comments.
| Field | Type | Description |
|---|---|---|
data | — | |
pagination | — |
Examples
curl https://api.dev.preshos.com/v1/objects/companies/records/48213/comments \
-H "Authorization: Bearer $PRESHOS_API_KEY"{
"data": [
{
"id": "c_8f2d",
"body": "Shared the revised proposal with the client.",
"author": {
"kind": "user",
"id": 17,
"name": "Jordan Lee"
},
"created_at": "2026-09-30T15:02:11.000Z",
"updated_at": "2026-09-30T15:02:11.000Z"
}
],
"pagination": {
"limit": 50,
"has_more": false,
"next_cursor": null
}
}Errors
| Status | Meaning | Codes |
|---|---|---|
| 400 | Invalid request: malformed JSON, failed validation, or an invalid filter/cursor. | validation_failed, invalid_cursor |
| 403 | Insufficient key scope, developer access revoked, or missing object/record/field permission. | developer_access_revoked, tenant_host_mismatch, insufficient_scope, permission_denied, search_not_supported |
| 404 | Object type, record, or route not found (or not readable by this user). | object_not_available, record_not_found, user_not_found, workflow_not_found, comments_not_supported, comments_not_supported |
Also 401 (key), 429 (rate limit), and 500, like every endpoint. All errors use the error envelope; see every error code.
Comment on a record
createComment/v1/objects/{object_type}/records/{record_id}/commentsPosts a comment as the key's user. Mentioned users (by user id) are notified when they can read the record. The comment appears in the app in real time, exactly like one written in the UI.
- Scopes
comments:write- Behavior
- Changes dataNot idempotentIdempotency-Key
Path parameters
| Name | Type | Description |
|---|---|---|
object_typerequired | string | Object type id from GET /v1/objects, e.g. companies, work-items, or a custom object id. |
record_idrequired | string1–200 chars | Canonical record id. |
Headers
| Name | Type | Description |
|---|---|---|
Authorizationrequired | string | Bearer <api key>. The key acts as the user who created it. |
Content-Typerequired | string | application/json. |
Idempotency-Key | stringmax 255 chars | Unique key (1–255 visible ASCII characters, e.g. a UUID). Retrying with the same key and body replays the first successful response for 24 hours. |
Request bodyapplication/json · required
| Field | Type | Description |
|---|---|---|
bodyrequired | string1–10000 chars | Plain-text comment. |
mention_user_ids | integer[]max 20 items | Workspace user ids to @mention. |
JSON Schema
{
"type": "object",
"properties": {
"body": {
"type": "string",
"minLength": 1,
"maxLength": 10000,
"description": "Plain-text comment."
},
"mention_user_ids": {
"maxItems": 20,
"type": "array",
"items": {
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 9007199254740991
},
"description": "Workspace user ids to @mention."
}
},
"required": [
"body"
],
"additionalProperties": false
}Examples
curl -X POST https://api.dev.preshos.com/v1/objects/companies/records/48213/comments \
-H "Authorization: Bearer $PRESHOS_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"body": "Kickoff is confirmed for Monday.",
"mention_user_ids": [
17
]
}'{
"body": "Kickoff is confirmed for Monday.",
"mention_user_ids": [
17
]
}{
"data": {
"id": "c_9a01",
"body": "@Jordan Lee Kickoff is confirmed for Monday.",
"author": {
"kind": "user",
"id": 23,
"name": "Sam Rivera"
},
"created_at": "2026-10-01T12:00:00.000Z",
"updated_at": "2026-10-01T12:00:00.000Z"
}
}Errors
| Status | Meaning | Codes |
|---|---|---|
| 400 | Invalid request: malformed JSON, failed validation, or an invalid filter/cursor. | invalid_json, validation_failed |
| 403 | Insufficient key scope, developer access revoked, or missing object/record/field permission. | developer_access_revoked, tenant_host_mismatch, insufficient_scope, permission_denied, search_not_supported, write_not_supported |
| 404 | Object type, record, or route not found (or not readable by this user). | object_not_available, record_not_found, user_not_found, workflow_not_found, comments_not_supported, comments_not_supported |
Also 401 (key), 429 (rate limit), and 500, like every endpoint. All errors use the error envelope; see every error code.
Search across object types.
Search across objects
search/v1/searchFull-text style search across work items, companies, contacts, meetings, events, and custom objects, filtered to what the key's user may read. Each hit carries object_type and id for the records endpoints.
- Scopes
records:read- Behavior
- Read-onlyIdempotent
Query parameters
| Name | Type | Description |
|---|---|---|
qrequired | string2–200 chars | Search text (2+ characters). |
limit | integer1–30 · default: 20 | — |
Headers
| Name | Type | Description |
|---|---|---|
Authorizationrequired | string | Bearer <api key>. The key acts as the user who created it. |
Response200
Search hits, best first.
| Field | Type | Description |
|---|---|---|
data | — |
Examples
curl -G https://api.dev.preshos.com/v1/search \
-H "Authorization: Bearer $PRESHOS_API_KEY" \
--data-urlencode "q=acme"{
"data": [
{
"object_type": "companies",
"id": "48213",
"display": "Acme Corporation",
"subtitle": "acme.com",
"url": "/companies/48213"
}
]
}Errors
| Status | Meaning | Codes |
|---|---|---|
| 400 | Invalid request: malformed JSON, failed validation, or an invalid filter/cursor. | validation_failed |
| 403 | Insufficient key scope, developer access revoked, or missing object/record/field permission. | developer_access_revoked, tenant_host_mismatch, insufficient_scope, permission_denied, search_not_supported |
Also 401 (key), 429 (rate limit), and 500, like every endpoint. All errors use the error envelope; see every error code.
Shared schemas referenced by request and response bodies. Responses may gain new properties at any time; ignore the ones you do not use.
A record of any object type. fields holds every readable business field keyed by field key; fields you may not read are omitted.
| Field | Type | Description |
|---|---|---|
object_type | string | Registry object type, e.g. companies, work-items, or a custom object id. |
id | string | Canonical record id. Use it for every record endpoint. |
public_idoptional | string | Work items only: the legacy/public id used in app deep links. |
work_item_type_idoptional | string | Work items only: the tenant work-item type (e.g. task). |
display | string | null | Primary display value (name/title). |
status | string | null | Current status value or workflow status id. |
owner_user_id | integer | null | Owning user id, when the object has an owner. |
created_at | string | null | ISO-8601 UTC. |
updated_at | string | null | ISO-8601 UTC. |
versionoptional | string | Opaque concurrency token. Send as If-Match on PATCH. |
fieldsoptional | object | — |
A page of records.
| Field | Type | Description |
|---|---|---|
data | — | |
pagination | — |
A single record.
| Field | Type | Description |
|---|---|---|
data | — |
Cursor pagination. Pass next_cursor as cursor with the same query to fetch the next page.
| Field | Type | Description |
|---|---|---|
limit | integer | Page size used for this response. |
has_more | boolean | Whether another page exists. |
next_cursor | string | null | Opaque cursor for the next page, or null. |
Every non-2xx response uses this envelope.
| Field | Type | Description |
|---|---|---|
error | object | — |
error.type | "invalid_request_error" | "authentication_error" | "permission_error" | "not_found_error" | "conflict_error" | "idempotency_error" | "rate_limit_error" | "api_error" | — |
error.code | string | Stable, machine-readable error code. |
error.message | string | Human-readable explanation. May change; do not parse. |
error.paramoptional | string | The parameter or field that caused the error, when known. |
error.detailsoptional | object | — |
error.request_id | string | Include this when contacting support. |
An object type available in this workspace.
| Field | Type | Description |
|---|---|---|
object_type | string | — |
name | string | null | — |
singular_name | string | null | — |
description | string | null | — |
kind | "built_in" | "work_item" | "default" | string | default = tenant custom object. |
category | string | — |
display_field | string | null | — |
capabilities | — | |
permissions | — |
Full description of an object type for this workspace and caller.
| Field | Type | Description |
|---|---|---|
object_type | string | — |
name | string | null | — |
singular_name | string | null | — |
description | string | null | — |
kind | string | — |
category | string | — |
display_field | string | null | — |
capabilities | — | |
permissions | — | |
fields | — | |
relationships | — | |
workflow | — | |
work_item_typesoptional | object[] | Work items only: active work-item types and their workflows. |
work_item_types[].id | string | — |
work_item_types[].name | string | null | — |
work_item_types[].workflow | — |
Operations the object supports through the API, independent of the caller's permissions.
| Field | Type | Description |
|---|---|---|
search | boolean | — |
create | boolean | — |
update | boolean | — |
delete | boolean | — |
What the calling user may do with this object type.
| Field | Type | Description |
|---|---|---|
create | — | |
read | — | |
update | — | |
delete | — |
Advisory access for the calling user. Every request is still authorized individually.
| Field | Type | Description |
|---|---|---|
allowed | boolean | — |
scopes | ("owned_by_user" | "owned_by_team" | "org_wide")[] | Record scopes granted: your own records, your teams' records, or the whole workspace. |
A field of an object type.
| Field | Type | Description |
|---|---|---|
key | string | Key used in fields for reads, writes, filters, and sorting. |
name | string | null | — |
type | string | Data type, e.g. text, number, date, select, boolean, reference. |
required | boolean | — |
editable | boolean | — |
storage | string | physical columns are filterable/sortable; jsonb values live under custom_fields. |
storage_key | string | — |
optionsoptional | — | |
constraintsoptional | object | — |
An allowed value for a select field.
| Field | Type | Description |
|---|---|---|
value | any | — |
labeloptional | string | — |
A relationship from this object to another.
| Field | Type | Description |
|---|---|---|
object | object | — |
object.key | string | — |
object.name | string | null | — |
cardinality | string | — |
labels | string[] | — |
A status workflow with its statuses and allowed transitions.
| Field | Type | Description |
|---|---|---|
id | string | — |
key | string | null | — |
name | string | null | — |
statuses | object[] | — |
statuses[].id | string | Status id. Send it to the status endpoint. |
statuses[].name | string | null | — |
statuses[].transitions | object[] | Statuses this status may move to. |
A status workflow reference. Fetch statuses with GET /v1/workflows/{workflow_id}.
| Field | Type | Description |
|---|---|---|
id | string | — |
key | string | null | — |
name | string | null | — |
A workspace member.
| Field | Type | Description |
|---|---|---|
id | integer | — |
name | string | null | — |
email | string | null | — |
title | string | null | — |
active | boolean | — |
teams | object[] | — |
teams[].id | integer | — |
teams[].name | string | null | — |
A team of workspace members.
| Field | Type | Description |
|---|---|---|
id | integer | — |
name | string | null | — |
description | string | null | — |
member_ids | integer[] | — |
A comment on a record.
| Field | Type | Description |
|---|---|---|
id | string | — |
body | string | Plain-text body. |
author | object | — |
author.kind | "user" | "agent" | string | — |
author.id | integer | null | — |
author.name | string | null | — |
created_at | string | null | — |
updated_at | string | null | — |
A cross-object search hit.
| Field | Type | Description |
|---|---|---|
object_type | string | — |
id | string | — |
display | string | null | — |
subtitle | string | null | — |
url | string | null | Deep link into the PRESHos app. |