{
  "openapi": "3.0.0",
  "info": {
    "title": "CRM Backend API",
    "version": "1.0.0",
    "description": "RESTful API for CRM system with multi-tenant support"
  },
  "servers": [
    {
      "url": "https://corteksa.info",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "Crm",
      "description": "Objects: the record types of a workspace, such as Contacts or Deals.\n\nEvery object is addressed by its `slug`, and its records live under\n`/object/data/{objectSlug}`. Use these routes to discover the objects a\ncredential can use and the fields each one has."
    },
    {
      "name": "Developers",
      "description": "Admin-facing management of API keys (create / list / revoke). These\nendpoints are ALWAYS human-guarded (`AdminAuthGuard`) — you cannot mint or\nrevoke keys with a key. The keys themselves are used against the records\nAPI via {@link ApiAuthGuard}."
    },
    {
      "name": "Fields",
      "description": "Fields related endpoints"
    },
    {
      "name": "Object - Fields - Tag",
      "description": "The workspace tag list, shared by every object.\n\nA workspace has one list of tags, and any record of any object can hold\nthem. A tag is addressed by its `slug`, which never changes: records store\nit, so renaming or recoloring a tag updates every record that holds it.\nReads need the `object.read` scope; most changes need `object.update`."
    },
    {
      "name": "Records",
      "description": "Read and change the records stored in an object.\n\nEvery object in the workspace, such as `contacts` or `deals`, keeps its\nrecords under `/object/data/{objectSlug}`. These routes list, search and read\nrecords, page through a record's relations and history, and create, update,\nassign and delete records.\n\nRecord values are keyed by field slug, never by field name or label. List\nand record reads also return `fields`, which gives each slug its label and\ntype.\n\nFile and photo values come back with a `url` and their `filePath`. A URL can\nexpire; get a fresh one from `GET /files/signed-url` or\n`POST /files/signed-urls`.\n\nReads need `read.<object slug>`. Writes need `create.<object slug>`,\n`update.<object slug>`, `assign.<object slug>` or `delete.<object slug>`."
    },
    {
      "name": "Webhook",
      "description": "Webhook related endpoints"
    }
  ],
  "paths": {
    "/api/v1/me": {
      "get": {
        "summary": "Identify the calling credential.",
        "description": "Identify the calling credential.\n\nReturns the workspace the API key or OAuth token is bound to and the\nscopes it holds. Use it to check a key a user pasted: a valid key answers\n`200`, a missing or revoked one answers `401`. It needs no scope.\n\n`tenant_type` is `hyper` for a workspace on the shared app and\n`dedicated` for a workspace with its own subdomain. `workspace_id` is set\nonly for `hyper`. `workspace_name` is `null` when the name cannot be\nresolved. A wildcard scope such as `read.*` comes back expanded: one\n`read.<object slug>` entry for each object the key's owner can read.\n\nErrors:\n- `401` — the credential is missing, invalid, expired, or not bound to a\n  workspace.\n- `429` — the API key exceeded its per-minute request limit.\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Success\",\n  \"data\": {\n    \"workspace_name\": \"Acme Sales\",\n    \"workspace_id\": 4821,\n    \"tenant_type\": \"hyper\",\n    \"scopes\": [\"read.contacts\", \"create.contacts\", \"update.contacts\"]\n  }\n}\n```",
        "tags": [
          "Developers"
        ],
        "operationId": "MeController_me",
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "workspace_name": {
                      "description": "Human-readable workspace/tenant name; `null` if it can't be resolved."
                    },
                    "workspace_id": {
                      "description": "The hyper workspace id; `null` for a dedicated tenant."
                    },
                    "tenant_type": {},
                    "scopes": {
                      "type": "string",
                      "description": "The credential's granted route-name scopes (e.g. `['read.contacts']`)."
                    }
                  },
                  "required": [
                    "workspace_name",
                    "workspace_id",
                    "tenant_type",
                    "scopes"
                  ],
                  "description": "Wire shape for `GET /api/v1/me` — \"who is this credential?\" for an API key or\nOAuth token. Carries the workspace identity and granted scopes so an external\napp can validate a pasted key and greet the workspace by name. NEVER exposes\na secret."
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/webhooks/events": {
      "get": {
        "summary": "List the webhook events you can subscribe to.",
        "description": "List the webhook events you can subscribe to.\n\nReturns every event name that `POST /webhooks/subscriptions` accepts in\n`events`, with a one-line description. Today these are the record events\n`record.created`, `record.updated` and `record.deleted`. It needs no\nscope.\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Success\",\n  \"data\": [\n    {\n      \"event\": \"record.created\",\n      \"description\": \"A record was created in a CRM object.\"\n    },\n    {\n      \"event\": \"record.updated\",\n      \"description\": \"A record was updated in a CRM object.\"\n    },\n    {\n      \"event\": \"record.deleted\",\n      \"description\": \"A record was deleted from a CRM object.\"\n    }\n  ]\n}\n```",
        "tags": [
          "Webhook"
        ],
        "operationId": "PublicWebhookController_listEvents",
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "data": {
                      "type": "array",
                      "items": {}
                    }
                  },
                  "required": [
                    "message",
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/webhooks/subscriptions": {
      "get": {
        "summary": "List the webhook subscriptions this credential owns.",
        "description": "List the webhook subscriptions this credential owns.\n\nReturns the subscriptions created by the calling API key or OAuth app,\nnewest first. Subscriptions made by other keys or apps are not included.\nThe list is not paginated. `signing_secret` is never included.\n\nErrors:\n- `403` — the caller is a user session rather than an API key or OAuth\n  token.\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Success\",\n  \"data\": [\n    {\n      \"slug\": \"b7e41c9a2f06\",\n      \"name\": \"Contacts sync\",\n      \"target_url\": \"https://hooks.example.com/corteksa\",\n      \"object_slug\": \"contacts\",\n      \"events\": [\"record.created\", \"record.updated\"],\n      \"is_active\": true,\n      \"created_at\": \"2026-09-14T10:00:00.000Z\"\n    }\n  ]\n}\n```",
        "tags": [
          "Webhook"
        ],
        "operationId": "PublicWebhookController_list",
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "slug": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "target_url": {},
                    "object_slug": {},
                    "events": {
                      "type": "string"
                    },
                    "is_active": {
                      "type": "boolean"
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  },
                  "required": [
                    "slug",
                    "name",
                    "target_url",
                    "object_slug",
                    "events",
                    "is_active",
                    "created_at"
                  ],
                  "description": "Wire shape for a public webhook subscription. NEVER exposes `signing_secret`\n(returned once, only by the create endpoint) or the internal numeric ids.\n`events` is projected back to the public catalog names."
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Subscribe an HTTPS endpoint to record events on one object.",
        "description": "Subscribe an HTTPS endpoint to record events on one object.\n\nAfter this call, each event in `events` on a record of `object_slug`\nsends a signed `POST` to `target_url`. The response carries\n`signing_secret`, which you use to verify deliveries. It is returned only\nhere, so store it before you do anything else.\n\nThe subscription belongs to the API key or OAuth app that created it, and\nonly that credential can list, read or delete it. `name` is optional and\ndefaults to the object name and the target host.\n\nScope: `read.<object_slug>` on the watched object.\n\nErrors:\n- `400` — an entry in `events` is not a catalog event name.\n- `403` — the credential lacks `read.<object_slug>`, or the caller is a\n  user session rather than an API key or OAuth token.\n- `404` — no object has that slug.\n- `422` — the body is invalid, for example `target_url` is not an HTTPS\n  URL or `events` is empty.\n\nRequest:\n\n```json\n{\n  \"object_slug\": \"contacts\",\n  \"events\": [\"record.created\", \"record.updated\"],\n  \"target_url\": \"https://hooks.example.com/corteksa\",\n  \"name\": \"Contacts sync\"\n}\n```\n\nResponse `201`:\n\n```json\n{\n  \"status\": 201,\n  \"message\": \"Webhook subscription created. Copy the signing secret now — it will not be shown again.\",\n  \"data\": {\n    \"signing_secret\": \"whsec_4f9b2c7d1e8a3b6c5d0e9f8a7b6c5d4e3f2a1b0c9d8e7f6a\",\n    \"subscription\": {\n      \"slug\": \"b7e41c9a2f06\",\n      \"name\": \"Contacts sync\",\n      \"target_url\": \"https://hooks.example.com/corteksa\",\n      \"object_slug\": \"contacts\",\n      \"events\": [\"record.created\", \"record.updated\"],\n      \"is_active\": true,\n      \"created_at\": \"2026-09-14T10:00:00.000Z\"\n    }\n  }\n}\n```",
        "tags": [
          "Webhook"
        ],
        "operationId": "PublicWebhookController_create",
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "slug": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "target_url": {},
                    "object_slug": {},
                    "events": {
                      "type": "string"
                    },
                    "is_active": {
                      "type": "boolean"
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  },
                  "required": [
                    "slug",
                    "name",
                    "target_url",
                    "object_slug",
                    "events",
                    "is_active",
                    "created_at"
                  ],
                  "description": "Wire shape for a public webhook subscription. NEVER exposes `signing_secret`\n(returned once, only by the create endpoint) or the internal numeric ids.\n`events` is projected back to the public catalog names."
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "description": "Optional label; defaults to `<object> → <host>` when omitted."
                  },
                  "object_slug": {
                    "type": "string",
                    "description": "The CRM object to watch, e.g. `tasks`."
                  },
                  "target_url": {
                    "type": "string",
                    "description": "HTTPS endpoint that receives the signed deliveries."
                  },
                  "events": {
                    "type": "string",
                    "description": "Public event names to subscribe to, e.g. `[\"record.created\",\"record.updated\"]`."
                  }
                },
                "required": [
                  "object_slug",
                  "target_url",
                  "events"
                ],
                "description": "Body for `POST /webhooks/subscriptions` — an external app self-registers a\npublic webhook with its API key. `events` are public catalog names\n(`record.created` …); `target_url` must be HTTPS."
              }
            }
          }
        }
      }
    },
    "/api/v1/webhooks/subscriptions/{slug}": {
      "get": {
        "summary": "Get one webhook subscription by slug.",
        "description": "Get one webhook subscription by slug.\n\nReturns the subscription only when the calling API key or OAuth app owns\nit. `signing_secret` is never included.\n\nErrors:\n- `403` — the caller is a user session rather than an API key or OAuth\n  token.\n- `404` — this credential owns no subscription with that slug.\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Success\",\n  \"data\": {\n    \"slug\": \"b7e41c9a2f06\",\n    \"name\": \"Contacts sync\",\n    \"target_url\": \"https://hooks.example.com/corteksa\",\n    \"object_slug\": \"contacts\",\n    \"events\": [\"record.created\", \"record.updated\"],\n    \"is_active\": true,\n    \"created_at\": \"2026-09-14T10:00:00.000Z\"\n  }\n}\n```",
        "tags": [
          "Webhook"
        ],
        "operationId": "PublicWebhookController_get",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "slug": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "target_url": {},
                    "object_slug": {},
                    "events": {
                      "type": "string"
                    },
                    "is_active": {
                      "type": "boolean"
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  },
                  "required": [
                    "slug",
                    "name",
                    "target_url",
                    "object_slug",
                    "events",
                    "is_active",
                    "created_at"
                  ],
                  "description": "Wire shape for a public webhook subscription. NEVER exposes `signing_secret`\n(returned once, only by the create endpoint) or the internal numeric ids.\n`events` is projected back to the public catalog names."
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete a webhook subscription.",
        "description": "Delete a webhook subscription.\n\nStops deliveries to the subscription's `target_url` and removes it. Only\nthe API key or OAuth app that owns the subscription can delete it.\n\nErrors:\n- `403` — the caller is a user session rather than an API key or OAuth\n  token.\n- `404` — this credential owns no subscription with that slug.\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Webhook subscription deleted\",\n  \"data\": null\n}\n```",
        "tags": [
          "Webhook"
        ],
        "operationId": "PublicWebhookController_remove",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "message"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/object/field/types": {
      "get": {
        "summary": "List the field types you can create.",
        "description": "List the field types you can create.\n\nReturns one entry per field type. `id` is the value to send as `type`\nwhen you create a field. `is_available` is `false` for types the system\ncreates itself (`status`, `tag`, `provider_id`); creating them yourself is\nrejected. `can_column_relation` tells whether a field of this type can be\nshown as an extra column of a relation. The list is not paginated.\n\nScope: `object.read`.\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Field types retrieved successfully\",\n  \"data\": [\n    {\n      \"id\": \"text\",\n      \"name\": \"Text\",\n      \"icon\": \"tabler-letter-a\",\n      \"category\": \"basic\",\n      \"is_available\": true,\n      \"can_column_relation\": true\n    }\n  ]\n}\n```",
        "tags": [
          "Fields"
        ],
        "operationId": "FieldController_getFieldTypes",
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "icon": {
                      "type": "string"
                    },
                    "category": {},
                    "is_available": {
                      "type": "boolean"
                    },
                    "can_column_relation": {
                      "type": "boolean",
                      "description": "May a field of this type be picked as one of the fields a relation shows\nbehind the linked record's name (`display_columns`, up to five)?\n\nPublished so that picker filters its options from the server's own rule\ninstead of a hard-coded copy — a copy is a 422 waiting to happen the first\ntime the list changes. Both gates the write path applies are folded in\n(allowed type AND not PII), so the flag is exactly what a save will accept."
                    }
                  },
                  "required": [
                    "id",
                    "name",
                    "icon",
                    "category",
                    "is_available",
                    "can_column_relation"
                  ],
                  "description": "Single entry in the field-type picker catalog (response shape for\n`GET /object/field/types`). One per `FieldType` enum value.\n\n- `id` matches the existing `FieldType` enum value on the wire — same\n  string the FE already uses in dropdowns and on `field.type`.\n- `name` / `icon` / `category` move from the FE registry to the BE,\n  so the FE no longer has to ship a registry entry per type.\n- `is_available` is the per-tenant gate. Today it is `false` only for\n  the system-provisioned types (see `SYSTEM_PROVISIONED_FIELD_TYPES`);\n  when plan-based gating ships, only the service flips this — the\n  wire contract does not change."
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/object/field/file-formats": {
      "get": {
        "summary": "List the file formats a file field can accept.",
        "description": "List the file formats a file field can accept.\n\nReturns the formats grouped by kind. Use these extensions in a file\nfield's `acceptedExtensions` option. Leaving that option empty accepts\nevery format listed here. The list is not paginated.\n\nScope: `object.read`.\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"File formats retrieved successfully\",\n  \"data\": [\n    {\n      \"key\": \"document\",\n      \"label\": \"Documents\",\n      \"formats\": [\".pdf\", \".docx\", \".xlsx\"]\n    }\n  ]\n}\n```",
        "tags": [
          "Fields"
        ],
        "operationId": "FieldController_getFileFormats",
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "key": {
                      "type": "string"
                    },
                    "label": {
                      "type": "string"
                    },
                    "formats": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "key",
                    "label",
                    "formats"
                  ],
                  "description": "One group in the FILE field's accepted-formats picker (response shape for\n`GET /object/field/file-formats`).\n\nThe picker is a multi-select over `formats`; selecting everything is stored\nas an **empty** `acceptedExtensions` (or the key omitted), which the backend\nreads as \"all formats\". Sending the full list explicitly is equivalent — a\nfield that later gains a new platform format keeps accepting only what was\npinned, which is the point of an explicit selection."
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/object/field/tags": {
      "get": {
        "summary": "List the active tag fields of all objects, one page at a time.",
        "description": "List the active tag fields of all objects, one page at a time.\n\nReturns each active field of type `tag` with its options and the object\nit belongs to. Each option's id is returned as `slug`. To manage the\nworkspace tag list itself, use `GET /object/field/tag`.\n\nQuery parameters:\n- `page` — page number, from 1. Default `1`.\n- `limit` — page size, 1 to 200. Default `50`.\n\nOther query parameters, including `search`, are rejected with `422`.\n\nScope: `object.read`.\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"TAG fields retrieved successfully\",\n  \"data\": [\n    {\n      \"slug\": \"tag-r4t6y8u0\",\n      \"name\": \"tag\",\n      \"label\": \"Tags\",\n      \"options\": {\n        \"values\": [\n          {\n            \"slug\": \"3f1c9a2e-7b4d-4e0a-9c61-2d8f5b0e41a7\",\n            \"value\": \"VIP\",\n            \"label\": \"VIP\",\n            \"color\": \"#F59E0B\"\n          }\n        ]\n      },\n      \"object\": {\n        \"slug\": \"task\",\n        \"name\": \"Tasks\",\n        \"icon\": \"tabler-checklist\"\n      }\n    }\n  ],\n  \"pagination\": {\n    \"total\": 1,\n    \"page\": 1,\n    \"limit\": 50,\n    \"totalPages\": 1,\n    \"nextPage\": null,\n    \"prevPage\": null\n  }\n}\n```",
        "tags": [
          "Fields"
        ],
        "operationId": "FieldController_getAllTagFields",
        "parameters": [
          {
            "name": "paginationQuery",
            "in": "query",
            "required": false,
            "schema": {}
          },
          {
            "name": "search",
            "in": "query",
            "required": false,
            "schema": {}
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "slug": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "label": {
                      "type": "string"
                    },
                    "options": {
                      "description": "Archived (deleted-but-still-referenced) options are stripped — see FieldResponseDto."
                    },
                    "object": {}
                  },
                  "required": [
                    "slug",
                    "name",
                    "label",
                    "options",
                    "object"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/object/field/manager": {
      "get": {
        "summary": "List every field in the workspace, one page at a time.",
        "description": "List every field in the workspace, one page at a time.\n\nReturns one row per field definition across all objects, ordered by\nobject name, then field order. `object_slug` is the object that owns the\nfield. `shared_with` lists the other objects it is shared into, and\n`usage_count` is 1 plus that number. Rows carry flags such as\n`has_default_value` instead of the field's settings; read one field for\nits full settings.\n\nQuery parameters:\n- `page` — page number, from 1. Default `1`.\n- `limit` — page size, 1 to 200. Default `50`.\n- `search` — keep fields whose name or label contains this text,\n  ignoring letter case.\n- `types` — keep these field types: `types=select,status` or repeated\n  `types=select&types=status`.\n- `object_slug` — keep the fields of one object, including fields shared\n  into it.\n- `shared_only` — `true` keeps only fields shared into other objects.\n- `system` — `false` leaves out system fields. Default `true`.\n\nScope: `object.read`.\n\nErrors:\n- `400` — `object_slug` names no active object.\n- `422` — a query parameter is invalid, for example an unknown type.\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Workspace fields retrieved successfully\",\n  \"data\": [\n    {\n      \"slug\": \"phone-q8w2e4r6\",\n      \"name\": \"phone\",\n      \"label\": \"Phone\",\n      \"icon\": null,\n      \"type\": \"phone\",\n      \"required\": false,\n      \"is_active\": true,\n      \"is_system\": false,\n      \"sort_order\": 3,\n      \"api_only\": false,\n      \"object_slug\": \"contacts\",\n      \"object_name\": \"Contacts\",\n      \"has_default_value\": false,\n      \"has_conditional_requirement\": false,\n      \"is_shared\": false,\n      \"shared_with\": [\n        { \"slug\": \"deals\", \"name\": \"Deals\" }\n      ],\n      \"usage_count\": 2,\n      \"created_at\": \"2026-08-02T08:30:00.000Z\",\n      \"updated_at\": \"2026-09-01T11:15:00.000Z\"\n    }\n  ],\n  \"pagination\": {\n    \"total\": 1,\n    \"page\": 1,\n    \"limit\": 50,\n    \"totalPages\": 1,\n    \"nextPage\": null,\n    \"prevPage\": null\n  }\n}\n```",
        "tags": [
          "Fields"
        ],
        "operationId": "FieldController_getManager",
        "parameters": [
          {
            "name": "query",
            "in": "query",
            "required": false,
            "schema": {}
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "slug": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "label": {
                      "type": "string"
                    },
                    "icon": {},
                    "type": {
                      "type": "string"
                    },
                    "required": {
                      "type": "boolean"
                    },
                    "is_active": {
                      "type": "boolean"
                    },
                    "is_system": {
                      "type": "boolean"
                    },
                    "sort_order": {
                      "type": "number"
                    },
                    "api_only": {
                      "type": "boolean",
                      "description": "Value-write lock — renders the \"API only\" chip beside the \"System\" one."
                    },
                    "object_slug": {
                      "description": "Owning Unit (the object the field definition belongs to)."
                    },
                    "object_name": {},
                    "has_default_value": {
                      "type": "boolean",
                      "description": "Presence flag only — the value itself is not a governance concern."
                    },
                    "has_conditional_requirement": {
                      "type": "boolean"
                    },
                    "is_shared": {
                      "type": "boolean"
                    },
                    "shared_with": {
                      "type": "array",
                      "items": {}
                    },
                    "usage_count": {
                      "type": "number",
                      "description": "1 (owning Unit) + number of placements — \"used in N locations\"."
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updated_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  },
                  "required": [
                    "slug",
                    "name",
                    "label",
                    "icon",
                    "type",
                    "required",
                    "is_active",
                    "is_system",
                    "sort_order",
                    "api_only",
                    "object_slug",
                    "object_name",
                    "has_default_value",
                    "has_conditional_requirement",
                    "is_shared",
                    "shared_with",
                    "usage_count",
                    "created_at",
                    "updated_at"
                  ],
                  "description": "One row of the Custom Field Manager governance table\n(`GET /object/field/manager`, FRD 06 FR-101/111).\n\nDeliberately excludes: numeric `id`, `workspace_id`, `column_name`,\n`column_type`, and raw `default_value`/`options` (SEC-03/14) — the Manager\nsurfaces governance facts, not schema internals. Full config is fetched\nper-field via the existing detail endpoints."
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/object/field/{objectSlug}": {
      "get": {
        "summary": "List the fields of an object, one page at a time.",
        "description": "List the fields of an object, one page at a time.\n\nReturns the object's own fields, active first, then by `sort_order`.\nEach page also includes the fields shared into this object from other\nobjects (`is_shared: true`); those are not counted in `pagination`.\n`options` holds the field type's settings; deleted choices of a select\nfield are left out. Relations are not listed here; use\n`GET /object/field/active/{objectSlug}` to get them as fields.\n\nQuery parameters:\n- `page` — page number, from 1. Default `1`.\n- `limit` — page size, 1 to 200. Default `50`.\n- `search` — keep fields whose name, label or slug contains this text,\n  ignoring letter case.\n- `types` — keep these field types: `types=number,currency` or repeated\n  `types=number&types=currency`.\n- `is_active` — `true` or `false` to keep only active or inactive fields.\n\nScope: `read.<object slug>`, for example `read.contacts`.\n\nErrors:\n- `400` — no object has that slug, or the object is inactive.\n- `422` — a query parameter is invalid, for example an unknown type.\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Fields retrieved successfully\",\n  \"data\": [\n    {\n      \"id\": 912,\n      \"name\": \"stage\",\n      \"slug\": \"stage-k2j9h4g7\",\n      \"label\": \"Stage\",\n      \"icon\": null,\n      \"type\": \"select\",\n      \"required\": false,\n      \"default_value\": null,\n      \"options\": {\n        \"values\": [\n          {\n            \"id\": \"6a1f3c9e-2b7d-4e8a-9f0c-1d2e3f4a5b6c\",\n            \"value\": \"lead\",\n            \"label\": \"Lead\",\n            \"color\": \"#3B82F6\"\n          }\n        ]\n      },\n      \"is_active\": true,\n      \"sort_order\": 4,\n      \"group_slug\": null,\n      \"group_ineligible\": false,\n      \"is_system\": false,\n      \"api_only\": false,\n      \"is_shared\": false,\n      \"shared_with\": [],\n      \"is_visible\": true,\n      \"created_at\": \"2026-08-02T08:30:00.000Z\",\n      \"updated_at\": \"2026-09-01T11:15:00.000Z\"\n    }\n  ],\n  \"pagination\": {\n    \"total\": 1,\n    \"page\": 1,\n    \"limit\": 50,\n    \"totalPages\": 1,\n    \"nextPage\": null,\n    \"prevPage\": null\n  }\n}\n```",
        "tags": [
          "Fields"
        ],
        "operationId": "FieldController_get",
        "parameters": [
          {
            "name": "objectSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "query",
            "in": "query",
            "required": false,
            "schema": {}
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "number"
                    },
                    "name": {
                      "type": "string"
                    },
                    "slug": {
                      "type": "string"
                    },
                    "label": {
                      "type": "string"
                    },
                    "icon": {},
                    "type": {
                      "type": "string"
                    },
                    "required": {
                      "type": "boolean"
                    },
                    "default_value": {},
                    "options": {
                      "description": "Archived (deleted-but-still-referenced) options are stripped — see FieldResponseDto."
                    },
                    "is_active": {
                      "type": "boolean"
                    },
                    "sort_order": {
                      "type": "number",
                      "description": "Order within the field's section — and, for a section-less field, within\nthe flat list. For a placed (shared) field this is the placement's\n`position` on THIS Unit, not the home definition's order. Same currency as\n`PUT /object/field/order` and as `positions[]` on the section assign call."
                    },
                    "group_slug": {
                      "description": "The field's section on THIS Unit (FRD-34); `null` ⇒ the implicit\n\"Ungrouped\" bucket, which renders last. `null` on paths that skip\n`stampSections` (nothing grouped, or a write-echo response), so the key\nis always present on the wire."
                    },
                    "group_ineligible": {
                      "type": "boolean",
                      "description": "FRD-34 — this field can never be moved into a section: the record shell\nrenders it (title, assignee, meta block, status/tag chrome) or it is\nsystem-defined. Derived on read rather than stored, so it is right for\nobjects seeded before sections existed and on the write-echo responses."
                    },
                    "is_system": {
                      "type": "boolean"
                    },
                    "api_only": {
                      "type": "boolean",
                      "description": "Value-write lock — drives the \"API only\" toggle's state in field settings."
                    },
                    "is_shared": {
                      "type": "boolean",
                      "description": "CFM-2: `is_shared`/`shared_with` are computed by\nFieldQueryService.mergeWithPlacedFields but were stripped here.\n`false`/`[]` on paths that skip the merge (e.g. findBySlug)."
                    },
                    "shared_with": {},
                    "is_visible": {
                      "type": "boolean",
                      "description": "Per-Unit visibility of a placed (shared) field on THIS Unit — set by\nmergeWithPlacedFields from the placement row. Own fields (and paths\nthat skip the merge) default to `true`; their on/off switch is\n`is_active`."
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updated_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  },
                  "required": [
                    "id",
                    "name",
                    "slug",
                    "label",
                    "icon",
                    "type",
                    "required",
                    "default_value",
                    "options",
                    "is_active",
                    "sort_order",
                    "group_slug",
                    "group_ineligible",
                    "is_system",
                    "api_only",
                    "is_shared",
                    "shared_with",
                    "is_visible",
                    "created_at",
                    "updated_at"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Add a field to an object.",
        "description": "Add a field to an object.\n\nCreates the field and its storage, and returns the saved field. The new\nfield gets a generated `slug`; use that slug, not `name` or `label`, as\nthe key in record `data`. `name` must be unique on the object, ignoring\nletter case. `options` holds the settings of the field type, for example\nthe choices of a `select` field, and is checked against that type.\n`default_value` is checked against the type too. `api_only: true` lets\nonly API keys and OAuth apps write the field's value.\n\nTypes `relation`, `status` and `tag` cannot be created here. The response\nis the stored field, including storage details you should not rely on;\nread `GET /object/field/show/{objectSlug}/{fieldSlug}` for the stable\nshape.\n\nScope: `object.create`.\n\nErrors:\n- `400` — a field with this name exists on the object, the type cannot\n  be created here, `options` or `default_value` do not fit the type,\n  `api_only` is set on a computed type, or no active object has that slug.\n- `422` — the body is invalid, for example an unknown `type`.\n\nRequest:\n\n```json\n{\n  \"name\": \"stage\",\n  \"label\": \"Stage\",\n  \"type\": \"select\",\n  \"required\": false,\n  \"options\": {\n    \"values\": [\n      { \"label\": \"Lead\", \"value\": \"lead\", \"color\": \"#3B82F6\" },\n      { \"label\": \"Customer\", \"value\": \"customer\", \"color\": \"#10B981\" }\n    ]\n  }\n}\n```\n\nResponse `201` (abridged):\n\n```json\n{\n  \"status\": 201,\n  \"message\": \"Field created successfully\",\n  \"data\": {\n    \"id\": 912,\n    \"name\": \"stage\",\n    \"slug\": \"730418526901\",\n    \"label\": \"Stage\",\n    \"type\": \"select\",\n    \"required\": false,\n    \"default_value\": null,\n    \"options\": {\n      \"values\": [\n        {\n          \"label\": \"Lead\",\n          \"value\": \"lead\",\n          \"color\": \"#3B82F6\",\n          \"id\": \"6a1f3c9e-2b7d-4e8a-9f0c-1d2e3f4a5b6c\"\n        },\n        {\n          \"label\": \"Customer\",\n          \"value\": \"customer\",\n          \"color\": \"#10B981\",\n          \"id\": \"b2c4d6e8-1a3b-4c5d-8e7f-9a0b1c2d3e4f\"\n        }\n      ]\n    },\n    \"is_active\": true,\n    \"api_only\": false,\n    \"sort_order\": 9,\n    \"is_system\": false,\n    \"created_at\": \"2026-09-14T10:00:00.000Z\",\n    \"updated_at\": \"2026-09-14T10:00:00.000Z\"\n  }\n}\n```",
        "tags": [
          "Fields"
        ],
        "operationId": "FieldController_create",
        "parameters": [
          {
            "name": "objectSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "number"
                    },
                    "name": {
                      "type": "string"
                    },
                    "slug": {
                      "type": "string"
                    },
                    "label": {
                      "type": "string"
                    },
                    "icon": {
                      "type": "string"
                    },
                    "type": {
                      "type": "string",
                      "enum": [
                        "text",
                        "long_text",
                        "number",
                        "boolean",
                        "datetime",
                        "date",
                        "select",
                        "multi_select",
                        "rating",
                        "currency",
                        "email",
                        "link",
                        "phone",
                        "location",
                        "relation",
                        "file",
                        "whatsapp_number",
                        "tag",
                        "priority",
                        "status",
                        "start_date",
                        "due_date",
                        "source_name",
                        "session_name",
                        "provider_id",
                        "smart_catalog",
                        "serial_number",
                        "calculation",
                        "photo",
                        "lookup",
                        "rollup"
                      ]
                    },
                    "required": {
                      "type": "boolean"
                    },
                    "default_value": {},
                    "options": {},
                    "is_active": {
                      "type": "boolean"
                    },
                    "sort_order": {
                      "type": "number"
                    },
                    "column_name": {
                      "type": "string",
                      "description": "Physical PostgreSQL column name.\n\nINVARIANT: this column is guaranteed to be a safe PostgreSQL identifier\n— every write path passes the value through `sanitizePgIdentifier()`\nbefore storing (see FieldCreateService, ObjectTemplateApplicator,\nDataMigrationService, seeders). Callers that interpolate this value into\nraw SQL do NOT need to re-sanitize it.\n\nNULL for a NOT-STORED type (`lookup`), which owns no physical column.\nThe TS type stays `string` rather than `string | null` only because\nwidening it breaks ~40 call sites across unrelated modules that declare\ntheir own `column_name: string` structural types; every read site that can\nmeet a lookup already guards with `if (!field.column_name)`. Widening this\nrepo-wide is a worthwhile standalone refactor — do NOT assume non-null in\nnew code."
                    },
                    "column_type": {
                      "type": "string"
                    },
                    "is_system": {
                      "type": "boolean"
                    },
                    "api_only": {
                      "type": "boolean",
                      "description": "Value-write lock: when true only a write whose resolved origin is `'api'`\nmay set this field's value; every other origin has it silently stripped\n(FRD API-only FR-100/104). Orthogonal to `is_system` (which protects the\nDEFINITION) and to `required`/`default_value`."
                    },
                    "relation_id": {
                      "description": "FK to the companion `relations` row for `type='relation'` fields.\nNULL on non-relation fields. Backfilled by migration 1788000000007 from\nthe implicit `field.name == relation.name` join. NOT NULL is enforced\non relation-type fields by migration 1788000000008."
                    },
                    "object": {},
                    "group_id": {
                      "description": "The field's section (FRD-34) on its HOME object. NULL ⇒ the implicit\n\"Ungrouped\" section. Placed (shared) fields carry their own per-object\nsection on `field_placements.group_id` instead — mirroring how home\nordering lives on `field.sort_order` and placed ordering on\n`field_placements.position`. FK `ON DELETE SET NULL`: deleting a group\nonly un-groups the field, never deletes it (FR-3412)."
                    },
                    "workspace_id": {},
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updated_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  },
                  "required": [
                    "id",
                    "name",
                    "slug",
                    "label",
                    "icon",
                    "type",
                    "required",
                    "default_value",
                    "options",
                    "is_active",
                    "sort_order",
                    "column_name",
                    "column_type",
                    "is_system",
                    "api_only",
                    "relation_id",
                    "object",
                    "created_at",
                    "updated_at"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "label": {
                    "type": "string"
                  },
                  "icon": {},
                  "type": {
                    "type": "string",
                    "enum": [
                      "text",
                      "long_text",
                      "number",
                      "boolean",
                      "datetime",
                      "date",
                      "select",
                      "multi_select",
                      "rating",
                      "currency",
                      "email",
                      "link",
                      "phone",
                      "location",
                      "relation",
                      "file",
                      "whatsapp_number",
                      "tag",
                      "priority",
                      "status",
                      "start_date",
                      "due_date",
                      "source_name",
                      "session_name",
                      "provider_id",
                      "smart_catalog",
                      "serial_number",
                      "calculation",
                      "photo",
                      "lookup",
                      "rollup"
                    ]
                  },
                  "required": {},
                  "default_value": {},
                  "options": {},
                  "is_active": {},
                  "api_only": {
                    "description": "Lock this field's VALUE to API-origin writes (FRD API-only FR-101).\nA definition-level toggle, gated by `object.*` like the rest of this DTO —\nrejected on computed field types, which are already engine-owned."
                  }
                },
                "required": [
                  "name",
                  "label",
                  "type"
                ]
              }
            }
          }
        }
      }
    },
    "/api/v1/object/field/all/{objectSlug}": {
      "get": {
        "summary": "List all fields an object owns, active and inactive.",
        "description": "List all fields an object owns, active and inactive.\n\nReturns the object's own fields in one response, active first, then by\n`sort_order`. Fields shared into the object from other objects and\nrelations are not included. Each item has the same shape as\n`GET /object/field/{objectSlug}`.\n\nScope: `read.<object slug>`, for example `read.contacts`.\n\nErrors:\n- `400` — no object has that slug, or the object is inactive.\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"All fields retrieved successfully\",\n  \"data\": [\n    {\n      \"id\": 912,\n      \"name\": \"stage\",\n      \"slug\": \"stage-k2j9h4g7\",\n      \"label\": \"Stage\",\n      \"icon\": null,\n      \"type\": \"select\",\n      \"required\": false,\n      \"default_value\": null,\n      \"options\": {\n        \"values\": [\n          {\n            \"id\": \"6a1f3c9e-2b7d-4e8a-9f0c-1d2e3f4a5b6c\",\n            \"value\": \"lead\",\n            \"label\": \"Lead\",\n            \"color\": \"#3B82F6\"\n          }\n        ]\n      },\n      \"is_active\": true,\n      \"sort_order\": 4,\n      \"group_slug\": null,\n      \"group_ineligible\": false,\n      \"is_system\": false,\n      \"api_only\": false,\n      \"is_shared\": false,\n      \"shared_with\": [],\n      \"is_visible\": true,\n      \"created_at\": \"2026-08-02T08:30:00.000Z\",\n      \"updated_at\": \"2026-09-01T11:15:00.000Z\"\n    }\n  ]\n}\n```",
        "tags": [
          "Fields"
        ],
        "operationId": "FieldController_getAll",
        "parameters": [
          {
            "name": "objectSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "number"
                    },
                    "name": {
                      "type": "string"
                    },
                    "slug": {
                      "type": "string"
                    },
                    "label": {
                      "type": "string"
                    },
                    "icon": {},
                    "type": {
                      "type": "string"
                    },
                    "required": {
                      "type": "boolean"
                    },
                    "default_value": {},
                    "options": {
                      "description": "Archived (deleted-but-still-referenced) options are stripped — see FieldResponseDto."
                    },
                    "is_active": {
                      "type": "boolean"
                    },
                    "sort_order": {
                      "type": "number",
                      "description": "Order within the field's section — and, for a section-less field, within\nthe flat list. For a placed (shared) field this is the placement's\n`position` on THIS Unit, not the home definition's order. Same currency as\n`PUT /object/field/order` and as `positions[]` on the section assign call."
                    },
                    "group_slug": {
                      "description": "The field's section on THIS Unit (FRD-34); `null` ⇒ the implicit\n\"Ungrouped\" bucket, which renders last. `null` on paths that skip\n`stampSections` (nothing grouped, or a write-echo response), so the key\nis always present on the wire."
                    },
                    "group_ineligible": {
                      "type": "boolean",
                      "description": "FRD-34 — this field can never be moved into a section: the record shell\nrenders it (title, assignee, meta block, status/tag chrome) or it is\nsystem-defined. Derived on read rather than stored, so it is right for\nobjects seeded before sections existed and on the write-echo responses."
                    },
                    "is_system": {
                      "type": "boolean"
                    },
                    "api_only": {
                      "type": "boolean",
                      "description": "Value-write lock — drives the \"API only\" toggle's state in field settings."
                    },
                    "is_shared": {
                      "type": "boolean",
                      "description": "CFM-2: `is_shared`/`shared_with` are computed by\nFieldQueryService.mergeWithPlacedFields but were stripped here.\n`false`/`[]` on paths that skip the merge (e.g. findBySlug)."
                    },
                    "shared_with": {},
                    "is_visible": {
                      "type": "boolean",
                      "description": "Per-Unit visibility of a placed (shared) field on THIS Unit — set by\nmergeWithPlacedFields from the placement row. Own fields (and paths\nthat skip the merge) default to `true`; their on/off switch is\n`is_active`."
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updated_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  },
                  "required": [
                    "id",
                    "name",
                    "slug",
                    "label",
                    "icon",
                    "type",
                    "required",
                    "default_value",
                    "options",
                    "is_active",
                    "sort_order",
                    "group_slug",
                    "group_ineligible",
                    "is_system",
                    "api_only",
                    "is_shared",
                    "shared_with",
                    "is_visible",
                    "created_at",
                    "updated_at"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/object/field/active/{objectSlug}": {
      "get": {
        "summary": "List the active fields of an object, relations included.",
        "description": "List the active fields of an object, relations included.\n\nReturns what a form or table for this object needs, in one response:\nthe active own fields, the visible fields shared into the object, and one\nentry per relation with `type` `relation`. The title field comes first.\nA relation entry carries `relation_type` (`hasMany` or `belongsTo`),\n`direction` and `target_object` instead of the settings of a stored\nfield; its `slug` is the key used for that relation in record\n`relations`.\n\nQuery parameters:\n- `types` — keep these field types: `types=select,status` or repeated\n  `types=select&types=status`. Relations are included only when the list\n  contains `relation` or `types` is omitted.\n\nScope: `read.<object slug>`, for example `read.contacts`.\n\nErrors:\n- `400` — no object has that slug, or the object is inactive.\n- `422` — `types` contains an unknown type.\n\nResponse `200` (abridged):\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Active fields retrieved successfully\",\n  \"data\": [\n    {\n      \"id\": 912,\n      \"name\": \"stage\",\n      \"slug\": \"stage-k2j9h4g7\",\n      \"label\": \"Stage\",\n      \"icon\": null,\n      \"type\": \"select\",\n      \"required\": false,\n      \"default_value\": null,\n      \"options\": {\n        \"values\": [\n          {\n            \"id\": \"6a1f3c9e-2b7d-4e8a-9f0c-1d2e3f4a5b6c\",\n            \"value\": \"lead\",\n            \"label\": \"Lead\",\n            \"color\": \"#3B82F6\"\n          }\n        ]\n      },\n      \"is_active\": true,\n      \"sort_order\": 4,\n      \"group_slug\": null,\n      \"group_ineligible\": false,\n      \"is_system\": false,\n      \"api_only\": false,\n      \"is_shared\": false,\n      \"shared_with\": [],\n      \"is_visible\": true,\n      \"created_at\": \"2026-08-02T08:30:00.000Z\",\n      \"updated_at\": \"2026-09-01T11:15:00.000Z\"\n    },\n    {\n      \"name\": \"company-mf3k2p9a\",\n      \"slug\": \"company-mf3k2p9a\",\n      \"label\": \"Company\",\n      \"type\": \"relation\",\n      \"icon\": \"tabler-building\",\n      \"options\": {\n        \"display_columns\": [],\n        \"display_columns_meta\": []\n      },\n      \"is_system\": false,\n      \"required\": false,\n      \"default_value\": null,\n      \"is_active\": true,\n      \"api_only\": false,\n      \"relation_type\": \"belongsTo\",\n      \"direction\": \"forward\",\n      \"target_object\": {\n        \"slug\": \"companies\",\n        \"name\": \"Companies\",\n        \"icon\": \"tabler-building\"\n      },\n      \"target_object_slug\": \"companies\"\n    }\n  ]\n}\n```",
        "tags": [
          "Fields"
        ],
        "operationId": "FieldController_getActive",
        "parameters": [
          {
            "name": "objectSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "query",
            "in": "query",
            "required": false,
            "schema": {}
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "number"
                    },
                    "name": {
                      "type": "string"
                    },
                    "slug": {
                      "type": "string"
                    },
                    "label": {
                      "type": "string"
                    },
                    "icon": {},
                    "type": {
                      "type": "string"
                    },
                    "required": {
                      "type": "boolean"
                    },
                    "default_value": {},
                    "options": {
                      "description": "Archived (deleted-but-still-referenced) options are stripped — see FieldResponseDto."
                    },
                    "is_active": {
                      "type": "boolean"
                    },
                    "sort_order": {
                      "type": "number",
                      "description": "Order within the field's section — and, for a section-less field, within\nthe flat list. For a placed (shared) field this is the placement's\n`position` on THIS Unit, not the home definition's order. Same currency as\n`PUT /object/field/order` and as `positions[]` on the section assign call."
                    },
                    "group_slug": {
                      "description": "The field's section on THIS Unit (FRD-34); `null` ⇒ the implicit\n\"Ungrouped\" bucket, which renders last. `null` on paths that skip\n`stampSections` (nothing grouped, or a write-echo response), so the key\nis always present on the wire."
                    },
                    "group_ineligible": {
                      "type": "boolean",
                      "description": "FRD-34 — this field can never be moved into a section: the record shell\nrenders it (title, assignee, meta block, status/tag chrome) or it is\nsystem-defined. Derived on read rather than stored, so it is right for\nobjects seeded before sections existed and on the write-echo responses."
                    },
                    "is_system": {
                      "type": "boolean"
                    },
                    "api_only": {
                      "type": "boolean",
                      "description": "Value-write lock — drives the \"API only\" toggle's state in field settings."
                    },
                    "is_shared": {
                      "type": "boolean",
                      "description": "CFM-2: `is_shared`/`shared_with` are computed by\nFieldQueryService.mergeWithPlacedFields but were stripped here.\n`false`/`[]` on paths that skip the merge (e.g. findBySlug)."
                    },
                    "shared_with": {},
                    "is_visible": {
                      "type": "boolean",
                      "description": "Per-Unit visibility of a placed (shared) field on THIS Unit — set by\nmergeWithPlacedFields from the placement row. Own fields (and paths\nthat skip the merge) default to `true`; their on/off switch is\n`is_active`."
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updated_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  },
                  "required": [
                    "id",
                    "name",
                    "slug",
                    "label",
                    "icon",
                    "type",
                    "required",
                    "default_value",
                    "options",
                    "is_active",
                    "sort_order",
                    "group_slug",
                    "group_ineligible",
                    "is_system",
                    "api_only",
                    "is_shared",
                    "shared_with",
                    "is_visible",
                    "created_at",
                    "updated_at"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/object/field/select/{objectSlug}": {
      "get": {
        "summary": "List the select, status and priority fields of an object.",
        "description": "List the select, status and priority fields of an object.\n\nDeprecated: use\n`GET /object/field/active/{objectSlug}?types=select,status,priority`,\nwhich returns full field entries. This route returns only each field's\n`slug` and `name`, for active and inactive fields.\n\nScope: `read.<object slug>`, for example `read.contacts`.\n\nErrors:\n- `400` — no object has that slug, or the object is inactive.\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Select fields retrieved successfully\",\n  \"data\": [\n    { \"slug\": \"stage-k2j9h4g7\", \"name\": \"stage\" },\n    { \"slug\": \"status-a1b2c3d4\", \"name\": \"status\" }\n  ]\n}\n```",
        "tags": [
          "Fields"
        ],
        "operationId": "FieldController_getSelectFields",
        "parameters": [
          {
            "name": "objectSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "slug": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "slug",
                    "name"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/object/field/trigger-fields/{objectSlug}": {
      "get": {
        "summary": "List the fields that can make another field required.",
        "description": "List the fields that can make another field required.\n\nA field can be required only when another field has a given value. This\nroute returns the active fields that can play that trigger role, and the\noperators a rule can use. Choice fields (`select`, `status`, `priority`,\n`multi_select`, `tag`) come with their choices in `values`; `text`,\n`number`, `boolean`, `date`, `email` and `phone` fields come with an\nempty `values`.\n\nQuery parameters:\n- `exclude` — the slug of a field to leave out, usually the field being\n  edited, since a field cannot trigger itself.\n\nScope: `read.<object slug>`, for example `read.contacts`.\n\nErrors:\n- `400` — no object has that slug, or the object is inactive.\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Trigger fields retrieved successfully\",\n  \"data\": {\n    \"trigger_fields\": [\n      {\n        \"slug\": \"stage-k2j9h4g7\",\n        \"name\": \"stage\",\n        \"label\": \"Stage\",\n        \"type\": \"select\",\n        \"values\": [\n          {\n            \"id\": \"6a1f3c9e-2b7d-4e8a-9f0c-1d2e3f4a5b6c\",\n            \"value\": \"lead\",\n            \"label\": \"Lead\",\n            \"color\": \"#3B82F6\"\n          }\n        ]\n      }\n    ],\n    \"operators\": [\n      { \"value\": \"equals\", \"label\": \"Equals\" },\n      { \"value\": \"not_equals\", \"label\": \"Does not equal\" },\n      { \"value\": \"in\", \"label\": \"Is one of\" },\n      { \"value\": \"not_in\", \"label\": \"Is not one of\" },\n      { \"value\": \"is_empty\", \"label\": \"Is empty\" },\n      { \"value\": \"is_not_empty\", \"label\": \"Is not empty\" }\n    ]\n  }\n}\n```",
        "tags": [
          "Fields"
        ],
        "operationId": "FieldController_getTriggerFields",
        "parameters": [
          {
            "name": "objectSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "exclude",
            "in": "query",
            "required": false,
            "schema": {}
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "slug": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "label": {
                      "type": "string"
                    },
                    "type": {
                      "type": "string"
                    },
                    "values": {
                      "type": "array",
                      "items": {}
                    }
                  },
                  "required": [
                    "slug",
                    "name",
                    "label",
                    "type",
                    "values"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/object/field/show/{objectSlug}/{fieldSlug}": {
      "get": {
        "summary": "Get one field by slug.",
        "description": "Get one field by slug.\n\nReturns the field with its full settings in `options`. For a `lookup`,\n`rollup` or `smart_catalog` field, `options` also carries the resolved\nsource details. `group_slug` names the form section the field sits in on\nthis object, or is `null`.\n\nScope: `read.<object slug>`, for example `read.contacts`.\n\nErrors:\n- `400` — no object has that slug, the object is inactive, or no field\n  has that slug.\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Field retrieved successfully\",\n  \"data\": {\n    \"id\": 912,\n    \"name\": \"stage\",\n    \"slug\": \"stage-k2j9h4g7\",\n    \"label\": \"Stage\",\n    \"icon\": null,\n    \"type\": \"select\",\n    \"required\": false,\n    \"default_value\": null,\n    \"options\": {\n      \"values\": [\n        {\n          \"id\": \"6a1f3c9e-2b7d-4e8a-9f0c-1d2e3f4a5b6c\",\n          \"value\": \"lead\",\n          \"label\": \"Lead\",\n          \"color\": \"#3B82F6\"\n        }\n      ]\n    },\n    \"is_active\": true,\n    \"sort_order\": 4,\n    \"group_slug\": null,\n    \"group_ineligible\": false,\n    \"is_system\": false,\n    \"api_only\": false,\n    \"is_shared\": false,\n    \"shared_with\": [],\n    \"is_visible\": true,\n    \"created_at\": \"2026-08-02T08:30:00.000Z\",\n    \"updated_at\": \"2026-09-01T11:15:00.000Z\"\n  }\n}\n```",
        "tags": [
          "Fields"
        ],
        "operationId": "FieldController_show",
        "parameters": [
          {
            "name": "objectSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "fieldSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "number"
                    },
                    "name": {
                      "type": "string"
                    },
                    "slug": {
                      "type": "string"
                    },
                    "label": {
                      "type": "string"
                    },
                    "icon": {},
                    "type": {
                      "type": "string"
                    },
                    "required": {
                      "type": "boolean"
                    },
                    "default_value": {},
                    "options": {
                      "description": "Archived (deleted-but-still-referenced) options are stripped — see FieldResponseDto."
                    },
                    "is_active": {
                      "type": "boolean"
                    },
                    "sort_order": {
                      "type": "number",
                      "description": "Order within the field's section — and, for a section-less field, within\nthe flat list. For a placed (shared) field this is the placement's\n`position` on THIS Unit, not the home definition's order. Same currency as\n`PUT /object/field/order` and as `positions[]` on the section assign call."
                    },
                    "group_slug": {
                      "description": "The field's section on THIS Unit (FRD-34); `null` ⇒ the implicit\n\"Ungrouped\" bucket, which renders last. `null` on paths that skip\n`stampSections` (nothing grouped, or a write-echo response), so the key\nis always present on the wire."
                    },
                    "group_ineligible": {
                      "type": "boolean",
                      "description": "FRD-34 — this field can never be moved into a section: the record shell\nrenders it (title, assignee, meta block, status/tag chrome) or it is\nsystem-defined. Derived on read rather than stored, so it is right for\nobjects seeded before sections existed and on the write-echo responses."
                    },
                    "is_system": {
                      "type": "boolean"
                    },
                    "api_only": {
                      "type": "boolean",
                      "description": "Value-write lock — drives the \"API only\" toggle's state in field settings."
                    },
                    "is_shared": {
                      "type": "boolean",
                      "description": "CFM-2: `is_shared`/`shared_with` are computed by\nFieldQueryService.mergeWithPlacedFields but were stripped here.\n`false`/`[]` on paths that skip the merge (e.g. findBySlug)."
                    },
                    "shared_with": {},
                    "is_visible": {
                      "type": "boolean",
                      "description": "Per-Unit visibility of a placed (shared) field on THIS Unit — set by\nmergeWithPlacedFields from the placement row. Own fields (and paths\nthat skip the merge) default to `true`; their on/off switch is\n`is_active`."
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updated_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  },
                  "required": [
                    "id",
                    "name",
                    "slug",
                    "label",
                    "icon",
                    "type",
                    "required",
                    "default_value",
                    "options",
                    "is_active",
                    "sort_order",
                    "group_slug",
                    "group_ineligible",
                    "is_system",
                    "api_only",
                    "is_shared",
                    "shared_with",
                    "is_visible",
                    "created_at",
                    "updated_at"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/object/field/duplicate/{objectSlug}": {
      "post": {
        "summary": "Copy fields of an object into other objects.",
        "description": "Copy fields of an object into other objects.\n\nCopies each field in `field_slugs` from the object in the path into each\nobject in `target_object_slugs`. A copy into the same object is named\n`<name> (copy)`; a copy into another object gets `name_prefix` in front of\nits name when you send one. Relation fields are skipped and listed in\n`skipped_relation_fields`.\n\nEach target object succeeds or fails on its own. `created` maps each\ntarget object slug to its new fields. `failed_targets` lists the targets\nthat failed, with the reason; nothing was added to them. When a copied\nfield was required only on a condition whose trigger field the target\nlacks, the condition is dropped and reported in `conditional_stripped`.\n\nLimits: at most 50 target objects, and at most 200 new fields in total\n(fields times targets).\n\nScope: `object.create`.\n\nErrors:\n- `400` — more than 200 new fields in total, a target object does not\n  exist or is inactive, a field slug is not on the source object, or all\n  chosen fields are relations.\n- `422` — the body is invalid, for example an empty `field_slugs`.\n\nRequest:\n\n```json\n{\n  \"field_slugs\": [\"phone-q8w2e4r6\", \"stage-k2j9h4g7\"],\n  \"target_object_slugs\": [\"deals\"],\n  \"name_prefix\": \"contact_\"\n}\n```\n\nResponse `201`:\n\n```json\n{\n  \"status\": 201,\n  \"message\": \"Fields duplicated successfully\",\n  \"data\": {\n    \"created\": {\n      \"deals\": [\n        {\n          \"id\": 1044,\n          \"name\": \"contact_stage\",\n          \"slug\": \"730418526901\",\n          \"label\": \"Stage\",\n          \"icon\": null,\n          \"type\": \"select\",\n          \"required\": false,\n          \"default_value\": null,\n          \"options\": {\n            \"values\": [\n              {\n                \"id\": \"6a1f3c9e-2b7d-4e8a-9f0c-1d2e3f4a5b6c\",\n                \"value\": \"lead\",\n                \"label\": \"Lead\",\n                \"color\": \"#3B82F6\"\n              }\n            ]\n          },\n          \"is_active\": true,\n          \"sort_order\": 9,\n          \"group_slug\": null,\n          \"group_ineligible\": false,\n          \"is_system\": false,\n          \"api_only\": false,\n          \"is_shared\": false,\n          \"shared_with\": [],\n          \"is_visible\": true,\n          \"created_at\": \"2026-09-14T10:00:00.000Z\",\n          \"updated_at\": \"2026-09-14T10:00:00.000Z\"\n        }\n      ]\n    },\n    \"skipped_relation_fields\": [],\n    \"failed_targets\": [],\n    \"conditional_stripped\": []\n  }\n}\n```",
        "tags": [
          "Fields"
        ],
        "operationId": "FieldController_duplicate",
        "parameters": [
          {
            "name": "objectSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "number"
                    },
                    "name": {
                      "type": "string"
                    },
                    "slug": {
                      "type": "string"
                    },
                    "label": {
                      "type": "string"
                    },
                    "icon": {},
                    "type": {
                      "type": "string"
                    },
                    "required": {
                      "type": "boolean"
                    },
                    "default_value": {},
                    "options": {
                      "description": "Archived (deleted-but-still-referenced) options are stripped — see FieldResponseDto."
                    },
                    "is_active": {
                      "type": "boolean"
                    },
                    "sort_order": {
                      "type": "number",
                      "description": "Order within the field's section — and, for a section-less field, within\nthe flat list. For a placed (shared) field this is the placement's\n`position` on THIS Unit, not the home definition's order. Same currency as\n`PUT /object/field/order` and as `positions[]` on the section assign call."
                    },
                    "group_slug": {
                      "description": "The field's section on THIS Unit (FRD-34); `null` ⇒ the implicit\n\"Ungrouped\" bucket, which renders last. `null` on paths that skip\n`stampSections` (nothing grouped, or a write-echo response), so the key\nis always present on the wire."
                    },
                    "group_ineligible": {
                      "type": "boolean",
                      "description": "FRD-34 — this field can never be moved into a section: the record shell\nrenders it (title, assignee, meta block, status/tag chrome) or it is\nsystem-defined. Derived on read rather than stored, so it is right for\nobjects seeded before sections existed and on the write-echo responses."
                    },
                    "is_system": {
                      "type": "boolean"
                    },
                    "api_only": {
                      "type": "boolean",
                      "description": "Value-write lock — drives the \"API only\" toggle's state in field settings."
                    },
                    "is_shared": {
                      "type": "boolean",
                      "description": "CFM-2: `is_shared`/`shared_with` are computed by\nFieldQueryService.mergeWithPlacedFields but were stripped here.\n`false`/`[]` on paths that skip the merge (e.g. findBySlug)."
                    },
                    "shared_with": {},
                    "is_visible": {
                      "type": "boolean",
                      "description": "Per-Unit visibility of a placed (shared) field on THIS Unit — set by\nmergeWithPlacedFields from the placement row. Own fields (and paths\nthat skip the merge) default to `true`; their on/off switch is\n`is_active`."
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updated_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  },
                  "required": [
                    "id",
                    "name",
                    "slug",
                    "label",
                    "icon",
                    "type",
                    "required",
                    "default_value",
                    "options",
                    "is_active",
                    "sort_order",
                    "group_slug",
                    "group_ineligible",
                    "is_system",
                    "api_only",
                    "is_shared",
                    "shared_with",
                    "is_visible",
                    "created_at",
                    "updated_at"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "field_slugs": {
                    "type": "string"
                  },
                  "target_object_slugs": {
                    "type": "string"
                  },
                  "name_prefix": {}
                },
                "required": [
                  "field_slugs",
                  "target_object_slugs"
                ]
              }
            }
          }
        }
      }
    },
    "/api/v1/object/field/option/{objectSlug}/{fieldSlug}": {
      "post": {
        "summary": "Add one choice to a choice field.",
        "description": "Add one choice to a choice field.\n\nAppends a choice to a `select`, `multi_select`, `status`, `priority`,\n`tag` or `source_name` field and returns the field. Existing choices keep\ntheir ids, so records that hold them are not affected. Only `label` is\nrequired; the new choice gets a generated `id`.\n\nScope: `object.update`.\n\nErrors:\n- `400` — the field type has no choices, the label or value is already\n  used by another choice (`code: DUPLICATE_OPTION_LABEL` or\n  `DUPLICATE_OPTION_VALUE`), a color is not a hex color, no object has\n  that slug, or no field has that slug.\n- `422` — the body is invalid, for example an empty `label`.\n\nRequest:\n\n```json\n{\n  \"label\": \"Qualified\",\n  \"value\": \"qualified\",\n  \"color\": \"#10B981\"\n}\n```\n\nResponse `201`:\n\n```json\n{\n  \"status\": 201,\n  \"message\": \"Field option added successfully\",\n  \"data\": {\n    \"id\": 912,\n    \"name\": \"stage\",\n    \"slug\": \"stage-k2j9h4g7\",\n    \"label\": \"Stage\",\n    \"icon\": null,\n    \"type\": \"select\",\n    \"required\": false,\n    \"default_value\": null,\n    \"options\": {\n      \"values\": [\n        {\n          \"id\": \"6a1f3c9e-2b7d-4e8a-9f0c-1d2e3f4a5b6c\",\n          \"value\": \"lead\",\n          \"label\": \"Lead\",\n          \"color\": \"#3B82F6\"\n        },\n        {\n          \"id\": \"0d7c5e3a-9b1f-4c2e-8a6d-4f3b2e1c0a9d\",\n          \"label\": \"Qualified\",\n          \"value\": \"qualified\",\n          \"color\": \"#10B981\"\n        }\n      ]\n    },\n    \"is_active\": true,\n    \"sort_order\": 4,\n    \"group_slug\": null,\n    \"group_ineligible\": false,\n    \"is_system\": false,\n    \"api_only\": false,\n    \"is_shared\": false,\n    \"shared_with\": [],\n    \"is_visible\": true,\n    \"created_at\": \"2026-08-02T08:30:00.000Z\",\n    \"updated_at\": \"2026-09-01T11:15:00.000Z\"\n  }\n}\n```",
        "tags": [
          "Fields"
        ],
        "operationId": "FieldController_addOption",
        "parameters": [
          {
            "name": "objectSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "fieldSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "number"
                    },
                    "name": {
                      "type": "string"
                    },
                    "slug": {
                      "type": "string"
                    },
                    "label": {
                      "type": "string"
                    },
                    "icon": {},
                    "type": {
                      "type": "string"
                    },
                    "required": {
                      "type": "boolean"
                    },
                    "default_value": {},
                    "options": {
                      "description": "Archived (deleted-but-still-referenced) options are stripped — see FieldResponseDto."
                    },
                    "is_active": {
                      "type": "boolean"
                    },
                    "sort_order": {
                      "type": "number",
                      "description": "Order within the field's section — and, for a section-less field, within\nthe flat list. For a placed (shared) field this is the placement's\n`position` on THIS Unit, not the home definition's order. Same currency as\n`PUT /object/field/order` and as `positions[]` on the section assign call."
                    },
                    "group_slug": {
                      "description": "The field's section on THIS Unit (FRD-34); `null` ⇒ the implicit\n\"Ungrouped\" bucket, which renders last. `null` on paths that skip\n`stampSections` (nothing grouped, or a write-echo response), so the key\nis always present on the wire."
                    },
                    "group_ineligible": {
                      "type": "boolean",
                      "description": "FRD-34 — this field can never be moved into a section: the record shell\nrenders it (title, assignee, meta block, status/tag chrome) or it is\nsystem-defined. Derived on read rather than stored, so it is right for\nobjects seeded before sections existed and on the write-echo responses."
                    },
                    "is_system": {
                      "type": "boolean"
                    },
                    "api_only": {
                      "type": "boolean",
                      "description": "Value-write lock — drives the \"API only\" toggle's state in field settings."
                    },
                    "is_shared": {
                      "type": "boolean",
                      "description": "CFM-2: `is_shared`/`shared_with` are computed by\nFieldQueryService.mergeWithPlacedFields but were stripped here.\n`false`/`[]` on paths that skip the merge (e.g. findBySlug)."
                    },
                    "shared_with": {},
                    "is_visible": {
                      "type": "boolean",
                      "description": "Per-Unit visibility of a placed (shared) field on THIS Unit — set by\nmergeWithPlacedFields from the placement row. Own fields (and paths\nthat skip the merge) default to `true`; their on/off switch is\n`is_active`."
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updated_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  },
                  "required": [
                    "id",
                    "name",
                    "slug",
                    "label",
                    "icon",
                    "type",
                    "required",
                    "default_value",
                    "options",
                    "is_active",
                    "sort_order",
                    "group_slug",
                    "group_ineligible",
                    "is_system",
                    "api_only",
                    "is_shared",
                    "shared_with",
                    "is_visible",
                    "created_at",
                    "updated_at"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "label": {
                    "type": "string"
                  },
                  "value": {},
                  "color": {},
                  "icon": {}
                },
                "required": [
                  "label"
                ],
                "description": "Append a single option to a select-family field (select, multi_select,\nstatus, priority, tag, source_name). `id` and `value` are server-filled by\n`reconcileOptionIds`, so only `label` is required; `value`/`color`/`icon`\nare validated against the same rules as a full options edit when supplied."
              }
            }
          }
        }
      }
    },
    "/api/v1/object/field/{objectSlug}/order": {
      "put": {
        "summary": "Change the order of an object's fields.",
        "description": "Change the order of an object's fields.\n\nSend each field's `slug` with its new `sort_order`. Fields you leave out\nkeep their place. The field named `name` always stays first at `0`; any\nother field sent with `0` gets `1`. A field shared into this object is\nreordered on this object only. The whole list is checked before anything\nis saved. Returns the object's fields in their new order.\n\nScope: `object.update`.\n\nErrors:\n- `400` — a slug is not a field of this object, or no active object has\n  that slug.\n- `422` — the body is invalid, for example a `sort_order` that is not a\n  number.\n\nRequest:\n\n```json\n{\n  \"fields\": [\n    { \"slug\": \"phone-q8w2e4r6\", \"sort_order\": 1 },\n    { \"slug\": \"stage-k2j9h4g7\", \"sort_order\": 2 }\n  ]\n}\n```\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Field order updated successfully\",\n  \"data\": [\n    {\n      \"id\": 912,\n      \"name\": \"stage\",\n      \"slug\": \"stage-k2j9h4g7\",\n      \"label\": \"Stage\",\n      \"icon\": null,\n      \"type\": \"select\",\n      \"required\": false,\n      \"default_value\": null,\n      \"options\": {\n        \"values\": [\n          {\n            \"id\": \"6a1f3c9e-2b7d-4e8a-9f0c-1d2e3f4a5b6c\",\n            \"value\": \"lead\",\n            \"label\": \"Lead\",\n            \"color\": \"#3B82F6\"\n          }\n        ]\n      },\n      \"is_active\": true,\n      \"sort_order\": 2,\n      \"group_slug\": null,\n      \"group_ineligible\": false,\n      \"is_system\": false,\n      \"api_only\": false,\n      \"is_shared\": false,\n      \"shared_with\": [],\n      \"is_visible\": true,\n      \"created_at\": \"2026-08-02T08:30:00.000Z\",\n      \"updated_at\": \"2026-09-01T11:15:00.000Z\"\n    }\n  ]\n}\n```",
        "tags": [
          "Fields"
        ],
        "operationId": "FieldController_updateOrder",
        "parameters": [
          {
            "name": "objectSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "number"
                    },
                    "name": {
                      "type": "string"
                    },
                    "slug": {
                      "type": "string"
                    },
                    "label": {
                      "type": "string"
                    },
                    "icon": {},
                    "type": {
                      "type": "string"
                    },
                    "required": {
                      "type": "boolean"
                    },
                    "default_value": {},
                    "options": {
                      "description": "Archived (deleted-but-still-referenced) options are stripped — see FieldResponseDto."
                    },
                    "is_active": {
                      "type": "boolean"
                    },
                    "sort_order": {
                      "type": "number",
                      "description": "Order within the field's section — and, for a section-less field, within\nthe flat list. For a placed (shared) field this is the placement's\n`position` on THIS Unit, not the home definition's order. Same currency as\n`PUT /object/field/order` and as `positions[]` on the section assign call."
                    },
                    "group_slug": {
                      "description": "The field's section on THIS Unit (FRD-34); `null` ⇒ the implicit\n\"Ungrouped\" bucket, which renders last. `null` on paths that skip\n`stampSections` (nothing grouped, or a write-echo response), so the key\nis always present on the wire."
                    },
                    "group_ineligible": {
                      "type": "boolean",
                      "description": "FRD-34 — this field can never be moved into a section: the record shell\nrenders it (title, assignee, meta block, status/tag chrome) or it is\nsystem-defined. Derived on read rather than stored, so it is right for\nobjects seeded before sections existed and on the write-echo responses."
                    },
                    "is_system": {
                      "type": "boolean"
                    },
                    "api_only": {
                      "type": "boolean",
                      "description": "Value-write lock — drives the \"API only\" toggle's state in field settings."
                    },
                    "is_shared": {
                      "type": "boolean",
                      "description": "CFM-2: `is_shared`/`shared_with` are computed by\nFieldQueryService.mergeWithPlacedFields but were stripped here.\n`false`/`[]` on paths that skip the merge (e.g. findBySlug)."
                    },
                    "shared_with": {},
                    "is_visible": {
                      "type": "boolean",
                      "description": "Per-Unit visibility of a placed (shared) field on THIS Unit — set by\nmergeWithPlacedFields from the placement row. Own fields (and paths\nthat skip the merge) default to `true`; their on/off switch is\n`is_active`."
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updated_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  },
                  "required": [
                    "id",
                    "name",
                    "slug",
                    "label",
                    "icon",
                    "type",
                    "required",
                    "default_value",
                    "options",
                    "is_active",
                    "sort_order",
                    "group_slug",
                    "group_ineligible",
                    "is_system",
                    "api_only",
                    "is_shared",
                    "shared_with",
                    "is_visible",
                    "created_at",
                    "updated_at"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "fields": {
                    "type": "array",
                    "items": {}
                  }
                },
                "required": [
                  "fields"
                ]
              }
            }
          }
        }
      }
    },
    "/api/v1/object/field/{objectSlug}/{fieldSlug}": {
      "put": {
        "summary": "Update a field's settings.",
        "description": "Update a field's settings.\n\nSend only the properties you change. The field's `type` cannot change.\nWhen you send `options`, existing choices keep their ids when they match\nby id, value or label, so records that hold them are not affected. A\nchoice you drop while records still hold it is kept as archived instead\nof deleted. System fields cannot be renamed, change type, or be turned\noff.\n\nTurning a field off (`is_active: false`) while a board view groups by it\nanswers `409`, unless you add `force=true`.\n\nThe response is the stored field, including storage details you should\nnot rely on; read `GET /object/field/show/{objectSlug}/{fieldSlug}` for\nthe stable shape.\n\nQuery parameters:\n- `force` — `true` to turn the field off even when a board groups by it.\n  Any other value counts as `false`.\n\nScope: `object.update`.\n\nErrors:\n- `400` — the new name is taken, `options` or `default_value` do not fit\n  the type, the change is not allowed on a system field, or the object or\n  field does not exist.\n- `409` — a board groups by this field and `force` is not `true`\n  (`code: FIELD_IN_USE_BY_VIEWS`). The message names the boards.\n- `422` — the body is invalid, for example a `label` over 100 characters.\n\nRequest:\n\n```json\n{\n  \"label\": \"Pipeline stage\",\n  \"required\": true\n}\n```\n\nResponse `200` (abridged):\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Field updated successfully\",\n  \"data\": {\n    \"id\": 912,\n    \"name\": \"stage\",\n    \"slug\": \"stage-k2j9h4g7\",\n    \"label\": \"Pipeline stage\",\n    \"icon\": null,\n    \"type\": \"select\",\n    \"required\": true,\n    \"default_value\": null,\n    \"options\": {\n      \"values\": [\n        {\n          \"id\": \"6a1f3c9e-2b7d-4e8a-9f0c-1d2e3f4a5b6c\",\n          \"value\": \"lead\",\n          \"label\": \"Lead\",\n          \"color\": \"#3B82F6\"\n        }\n      ]\n    },\n    \"is_active\": true,\n    \"sort_order\": 4,\n    \"is_system\": false,\n    \"api_only\": false,\n    \"created_at\": \"2026-08-02T08:30:00.000Z\",\n    \"updated_at\": \"2026-09-14T10:00:00.000Z\"\n  }\n}\n```",
        "tags": [
          "Fields"
        ],
        "operationId": "FieldController_update",
        "parameters": [
          {
            "name": "objectSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "fieldSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "query",
            "in": "query",
            "required": false,
            "schema": {}
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "number"
                    },
                    "name": {
                      "type": "string"
                    },
                    "slug": {
                      "type": "string"
                    },
                    "label": {
                      "type": "string"
                    },
                    "icon": {
                      "type": "string"
                    },
                    "type": {
                      "type": "string",
                      "enum": [
                        "text",
                        "long_text",
                        "number",
                        "boolean",
                        "datetime",
                        "date",
                        "select",
                        "multi_select",
                        "rating",
                        "currency",
                        "email",
                        "link",
                        "phone",
                        "location",
                        "relation",
                        "file",
                        "whatsapp_number",
                        "tag",
                        "priority",
                        "status",
                        "start_date",
                        "due_date",
                        "source_name",
                        "session_name",
                        "provider_id",
                        "smart_catalog",
                        "serial_number",
                        "calculation",
                        "photo",
                        "lookup",
                        "rollup"
                      ]
                    },
                    "required": {
                      "type": "boolean"
                    },
                    "default_value": {},
                    "options": {},
                    "is_active": {
                      "type": "boolean"
                    },
                    "sort_order": {
                      "type": "number"
                    },
                    "column_name": {
                      "type": "string",
                      "description": "Physical PostgreSQL column name.\n\nINVARIANT: this column is guaranteed to be a safe PostgreSQL identifier\n— every write path passes the value through `sanitizePgIdentifier()`\nbefore storing (see FieldCreateService, ObjectTemplateApplicator,\nDataMigrationService, seeders). Callers that interpolate this value into\nraw SQL do NOT need to re-sanitize it.\n\nNULL for a NOT-STORED type (`lookup`), which owns no physical column.\nThe TS type stays `string` rather than `string | null` only because\nwidening it breaks ~40 call sites across unrelated modules that declare\ntheir own `column_name: string` structural types; every read site that can\nmeet a lookup already guards with `if (!field.column_name)`. Widening this\nrepo-wide is a worthwhile standalone refactor — do NOT assume non-null in\nnew code."
                    },
                    "column_type": {
                      "type": "string"
                    },
                    "is_system": {
                      "type": "boolean"
                    },
                    "api_only": {
                      "type": "boolean",
                      "description": "Value-write lock: when true only a write whose resolved origin is `'api'`\nmay set this field's value; every other origin has it silently stripped\n(FRD API-only FR-100/104). Orthogonal to `is_system` (which protects the\nDEFINITION) and to `required`/`default_value`."
                    },
                    "relation_id": {
                      "description": "FK to the companion `relations` row for `type='relation'` fields.\nNULL on non-relation fields. Backfilled by migration 1788000000007 from\nthe implicit `field.name == relation.name` join. NOT NULL is enforced\non relation-type fields by migration 1788000000008."
                    },
                    "object": {},
                    "group_id": {
                      "description": "The field's section (FRD-34) on its HOME object. NULL ⇒ the implicit\n\"Ungrouped\" section. Placed (shared) fields carry their own per-object\nsection on `field_placements.group_id` instead — mirroring how home\nordering lives on `field.sort_order` and placed ordering on\n`field_placements.position`. FK `ON DELETE SET NULL`: deleting a group\nonly un-groups the field, never deletes it (FR-3412)."
                    },
                    "workspace_id": {},
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updated_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  },
                  "required": [
                    "id",
                    "name",
                    "slug",
                    "label",
                    "icon",
                    "type",
                    "required",
                    "default_value",
                    "options",
                    "is_active",
                    "sort_order",
                    "column_name",
                    "column_type",
                    "is_system",
                    "api_only",
                    "relation_id",
                    "object",
                    "created_at",
                    "updated_at"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {},
                  "icon": {},
                  "label": {},
                  "type": {},
                  "required": {},
                  "default_value": {},
                  "options": {},
                  "is_active": {},
                  "api_only": {
                    "description": "Lock this field's VALUE to API-origin writes (FRD API-only FR-101).\nA definition-level toggle, gated by `object.*` like the rest of this DTO —\nrejected on computed field types, which are already engine-owned."
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete a field and its values.",
        "description": "Delete a field and its values.\n\nRemoves the field from the object, deletes the value it held on every\nrecord, and deletes the files stored in it. This cannot be undone. System\nfields cannot be deleted. When a board view groups by the field, the call\nanswers `409` unless you add `force=true`; with `force`, those boards are\ndeleted with the field.\n\nQuery parameters:\n- `force` — `true` to delete the field even when a board groups by it.\n  Any other value counts as `false`.\n\nScope: `object.delete`.\n\nErrors:\n- `400` — it is a system field, no field has that slug, or no active\n  object has that slug.\n- `409` — a board groups by this field and `force` is not `true`\n  (`code: FIELD_IN_USE_BY_VIEWS`).\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Field deleted successfully\",\n  \"data\": null\n}\n```",
        "tags": [
          "Fields"
        ],
        "operationId": "FieldController_remove",
        "parameters": [
          {
            "name": "objectSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "fieldSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "query",
            "in": "query",
            "required": false,
            "schema": {}
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "message"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/object/field/toggle-status/{objectSlug}/{fieldSlug}": {
      "put": {
        "summary": "Turn a field on or off on an object.",
        "description": "Turn a field on or off on an object.\n\nNo request body. For a field the object owns, flips `is_active`. For a\nfield shared into the object from another object, shows or hides it on\nthis object only (`is_visible`); the field stays on elsewhere. The\nmessage says which way it flipped. System fields cannot be turned off.\n\nTurning a field off while a board view groups by it answers `409`,\nunless you add `force=true`.\n\nQuery parameters:\n- `force` — `true` to turn the field off even when a board groups by it.\n  Any other value counts as `false`.\n\nScope: `object.update`.\n\nErrors:\n- `400` — no field has that slug, the field is not on this object, it is\n  an active system field, or no active object has that slug.\n- `409` — a board groups by this field and `force` is not `true`\n  (`code: FIELD_IN_USE_BY_VIEWS`).\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Field deactivated successfully\",\n  \"data\": {\n    \"id\": 912,\n    \"name\": \"stage\",\n    \"slug\": \"stage-k2j9h4g7\",\n    \"label\": \"Stage\",\n    \"icon\": null,\n    \"type\": \"select\",\n    \"required\": false,\n    \"default_value\": null,\n    \"options\": {\n      \"values\": [\n        {\n          \"id\": \"6a1f3c9e-2b7d-4e8a-9f0c-1d2e3f4a5b6c\",\n          \"value\": \"lead\",\n          \"label\": \"Lead\",\n          \"color\": \"#3B82F6\"\n        }\n      ]\n    },\n    \"is_active\": false,\n    \"sort_order\": 4,\n    \"group_slug\": null,\n    \"group_ineligible\": false,\n    \"is_system\": false,\n    \"api_only\": false,\n    \"is_shared\": false,\n    \"shared_with\": [],\n    \"is_visible\": true,\n    \"created_at\": \"2026-08-02T08:30:00.000Z\",\n    \"updated_at\": \"2026-09-14T10:00:00.000Z\"\n  }\n}\n```",
        "tags": [
          "Fields"
        ],
        "operationId": "FieldController_toggleStatus",
        "parameters": [
          {
            "name": "objectSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "fieldSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "query",
            "in": "query",
            "required": false,
            "schema": {}
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "number"
                    },
                    "name": {
                      "type": "string"
                    },
                    "slug": {
                      "type": "string"
                    },
                    "label": {
                      "type": "string"
                    },
                    "icon": {},
                    "type": {
                      "type": "string"
                    },
                    "required": {
                      "type": "boolean"
                    },
                    "default_value": {},
                    "options": {
                      "description": "Archived (deleted-but-still-referenced) options are stripped — see FieldResponseDto."
                    },
                    "is_active": {
                      "type": "boolean"
                    },
                    "sort_order": {
                      "type": "number",
                      "description": "Order within the field's section — and, for a section-less field, within\nthe flat list. For a placed (shared) field this is the placement's\n`position` on THIS Unit, not the home definition's order. Same currency as\n`PUT /object/field/order` and as `positions[]` on the section assign call."
                    },
                    "group_slug": {
                      "description": "The field's section on THIS Unit (FRD-34); `null` ⇒ the implicit\n\"Ungrouped\" bucket, which renders last. `null` on paths that skip\n`stampSections` (nothing grouped, or a write-echo response), so the key\nis always present on the wire."
                    },
                    "group_ineligible": {
                      "type": "boolean",
                      "description": "FRD-34 — this field can never be moved into a section: the record shell\nrenders it (title, assignee, meta block, status/tag chrome) or it is\nsystem-defined. Derived on read rather than stored, so it is right for\nobjects seeded before sections existed and on the write-echo responses."
                    },
                    "is_system": {
                      "type": "boolean"
                    },
                    "api_only": {
                      "type": "boolean",
                      "description": "Value-write lock — drives the \"API only\" toggle's state in field settings."
                    },
                    "is_shared": {
                      "type": "boolean",
                      "description": "CFM-2: `is_shared`/`shared_with` are computed by\nFieldQueryService.mergeWithPlacedFields but were stripped here.\n`false`/`[]` on paths that skip the merge (e.g. findBySlug)."
                    },
                    "shared_with": {},
                    "is_visible": {
                      "type": "boolean",
                      "description": "Per-Unit visibility of a placed (shared) field on THIS Unit — set by\nmergeWithPlacedFields from the placement row. Own fields (and paths\nthat skip the merge) default to `true`; their on/off switch is\n`is_active`."
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updated_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  },
                  "required": [
                    "id",
                    "name",
                    "slug",
                    "label",
                    "icon",
                    "type",
                    "required",
                    "default_value",
                    "options",
                    "is_active",
                    "sort_order",
                    "group_slug",
                    "group_ineligible",
                    "is_system",
                    "api_only",
                    "is_shared",
                    "shared_with",
                    "is_visible",
                    "created_at",
                    "updated_at"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/object/field/tag": {
      "get": {
        "summary": "List the workspace tags.",
        "description": "List the workspace tags.\n\nReturns every tag in list order, with `usage_count`, the number of live\nrecords that hold it, and `units`, that count split by object. The list is\nnot paginated. Retired tags are left out unless you ask for them: a tag is\nretired when it is deleted while records still hold it.\n\nQuery parameters:\n- `search` — keep tags whose label or value contains this text, ignoring\n  letter case.\n- `include_archived` — send `true` to include retired tags. Omit it to\n  leave them out.\n\nScope: `object.read`.\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Tags retrieved successfully\",\n  \"data\": [\n    {\n      \"slug\": \"3f1c9a2e-7b4d-4e0a-9c61-2d8f5b0e41a7\",\n      \"label\": \"VIP\",\n      \"value\": \"VIP\",\n      \"color\": \"#F59E0B\",\n      \"is_archived\": false,\n      \"usage_count\": 12,\n      \"units\": [\n        { \"slug\": \"contacts\", \"name\": \"Contacts\", \"count\": 9 },\n        { \"slug\": \"deals\", \"name\": \"Deals\", \"count\": 3 }\n      ]\n    }\n  ]\n}\n```",
        "tags": [
          "Object - Fields - Tag"
        ],
        "operationId": "TagController_list",
        "parameters": [
          {
            "name": "query",
            "in": "query",
            "required": false,
            "schema": {}
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "slug": {},
                    "label": {
                      "type": "string"
                    },
                    "value": {
                      "type": "string"
                    },
                    "color": {},
                    "icon": {},
                    "is_archived": {
                      "type": "boolean",
                      "description": "A retired tag: still rendered on records that hold it, never pickable."
                    },
                    "usage_count": {
                      "type": "number",
                      "description": "Live records holding this tag across every Unit."
                    },
                    "units": {
                      "type": "array",
                      "items": {}
                    }
                  },
                  "required": [
                    "slug",
                    "label",
                    "value",
                    "color",
                    "icon",
                    "is_archived",
                    "usage_count",
                    "units"
                  ],
                  "description": "A tag on the wire.\n\nThe option's `id` is published as `slug`, matching how `TagFieldResponseDto`\nalready exposes it — a numeric/opaque identifier never crosses the wire under\nthe name `id` (CLAUDE.md §4.2)."
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Add a tag to the workspace list.",
        "description": "Add a tag to the workspace list.\n\nThe new tag can be set on records of every object right away. `label` is\nrequired (1 to 100 characters). `color` is a hex color; when you omit it,\nthe tag gets one from a fixed palette. The tag's `value` is set from the\nlabel once and does not change on a later rename.\n\nLabels are unique, ignoring surrounding spaces and letter case. When a\nretired tag has the same label, it is restored with its original `slug`\nand its current usage instead of creating a second tag.\n\nScope: `object.update`. To add a tag while editing a record, with only\nwrite access to that record's object, use\n`POST /object/field/tag/unit/{objectSlug}`.\n\nErrors:\n- `409` — a live tag already has this label. The error carries\n  `code: TAG_ALREADY_EXISTS` and the existing tag's slug in `data.slug`.\n- `422` — the body is invalid, for example an empty `label` or a `color`\n  that is not a hex color.\n\nRequest:\n\n```json\n{\n  \"label\": \"Partner\",\n  \"color\": \"#10B981\"\n}\n```\n\nResponse `201`:\n\n```json\n{\n  \"status\": 201,\n  \"message\": \"Tag created successfully\",\n  \"data\": {\n    \"slug\": \"8d2b6e4f-1a3c-4b5d-9e7f-0a1b2c3d4e5f\",\n    \"label\": \"Partner\",\n    \"value\": \"Partner\",\n    \"color\": \"#10B981\",\n    \"is_archived\": false,\n    \"usage_count\": 0,\n    \"units\": []\n  }\n}\n```\n\nError `409`:\n\n```json\n{\n  \"status\": 409,\n  \"message\": \"A tag called \\\"Partner\\\" already exists.\",\n  \"code\": \"TAG_ALREADY_EXISTS\",\n  \"data\": { \"slug\": \"8d2b6e4f-1a3c-4b5d-9e7f-0a1b2c3d4e5f\" }\n}\n```",
        "tags": [
          "Object - Fields - Tag"
        ],
        "operationId": "TagController_create",
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "slug": {},
                    "label": {
                      "type": "string"
                    },
                    "value": {
                      "type": "string"
                    },
                    "color": {},
                    "icon": {},
                    "is_archived": {
                      "type": "boolean",
                      "description": "A retired tag: still rendered on records that hold it, never pickable."
                    },
                    "usage_count": {
                      "type": "number",
                      "description": "Live records holding this tag across every Unit."
                    },
                    "units": {}
                  },
                  "required": [
                    "slug",
                    "label",
                    "value",
                    "color",
                    "icon",
                    "is_archived",
                    "usage_count",
                    "units"
                  ],
                  "description": "A tag on the wire.\n\nThe option's `id` is published as `slug`, matching how `TagFieldResponseDto`\nalready exposes it — a numeric/opaque identifier never crosses the wire under\nthe name `id` (CLAUDE.md §4.2)."
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "label": {
                    "type": "string"
                  },
                  "color": {},
                  "icon": {}
                },
                "required": [
                  "label"
                ]
              }
            }
          }
        }
      }
    },
    "/api/v1/object/field/tag/order": {
      "put": {
        "summary": "Reorder the workspace tags.",
        "description": "Reorder the workspace tags.\n\nSend each tag's `slug` with its new `position`; lower positions come\nfirst. Tags you leave out keep their current relative order and move\nafter the ones you list. Retired tags can be listed too.\n\nScope: `object.update`.\n\nErrors:\n- `422` — the body is invalid, for example a `position` below `0`.\n\nRequest:\n\n```json\n{\n  \"tags\": [\n    { \"slug\": \"3f1c9a2e-7b4d-4e0a-9c61-2d8f5b0e41a7\", \"position\": 0 },\n    { \"slug\": \"8d2b6e4f-1a3c-4b5d-9e7f-0a1b2c3d4e5f\", \"position\": 1 }\n  ]\n}\n```\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Tags reordered successfully\",\n  \"data\": null\n}\n```",
        "tags": [
          "Object - Fields - Tag"
        ],
        "operationId": "TagController_reorder",
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "message"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "tags": {
                    "type": "array",
                    "items": {}
                  }
                },
                "required": [
                  "tags"
                ]
              }
            }
          }
        }
      }
    },
    "/api/v1/object/field/tag/unit/{objectSlug}": {
      "post": {
        "summary": "Add a tag from a record of one object.",
        "description": "Add a tag from a record of one object.\n\nWorks like `POST /object/field/tag` and takes the same body, but needs\nwrite access to the object in the path instead of `object.update`. Use it\nwhen your app adds a tag while it edits a record. The object slug only\ndecides the scope: the tag joins the workspace list and is available on\nevery object. Renaming, reordering and deleting tags still need\n`object.update`.\n\nScope: `update.<object slug>`, for example `update.contacts`.\n\nErrors:\n- `409` — a live tag already has this label. The error carries\n  `code: TAG_ALREADY_EXISTS` and the existing tag's slug in `data.slug`.\n- `422` — the body is invalid, for example an empty `label`.\n\nRequest:\n\n```json\n{\n  \"label\": \"Follow up\"\n}\n```\n\nResponse `201`:\n\n```json\n{\n  \"status\": 201,\n  \"message\": \"Tag created successfully\",\n  \"data\": {\n    \"slug\": \"c4a7e1b9-2d3f-4a6b-8c5e-7f9a0b1c2d3e\",\n    \"label\": \"Follow up\",\n    \"value\": \"Follow up\",\n    \"color\": \"#8B5CF6\",\n    \"is_archived\": false,\n    \"usage_count\": 0,\n    \"units\": []\n  }\n}\n```",
        "tags": [
          "Object - Fields - Tag"
        ],
        "operationId": "TagController_createForUnit",
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "slug": {},
                    "label": {
                      "type": "string"
                    },
                    "value": {
                      "type": "string"
                    },
                    "color": {},
                    "icon": {},
                    "is_archived": {
                      "type": "boolean",
                      "description": "A retired tag: still rendered on records that hold it, never pickable."
                    },
                    "usage_count": {
                      "type": "number",
                      "description": "Live records holding this tag across every Unit."
                    },
                    "units": {}
                  },
                  "required": [
                    "slug",
                    "label",
                    "value",
                    "color",
                    "icon",
                    "is_archived",
                    "usage_count",
                    "units"
                  ],
                  "description": "A tag on the wire.\n\nThe option's `id` is published as `slug`, matching how `TagFieldResponseDto`\nalready exposes it — a numeric/opaque identifier never crosses the wire under\nthe name `id` (CLAUDE.md §4.2)."
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "label": {
                    "type": "string"
                  },
                  "color": {},
                  "icon": {}
                },
                "required": [
                  "label"
                ]
              }
            }
          }
        }
      }
    },
    "/api/v1/object/field/tag/{tagSlug}/usage": {
      "get": {
        "summary": "Get one tag with its usage.",
        "description": "Get one tag with its usage.\n\nReturns the tag with `usage_count`, the number of live records that hold\nit, and `units`, that count split by object. Retired tags can be read\ntoo. Use it before deleting a tag to show what the delete affects.\n\nScope: `object.read`.\n\nErrors:\n- `404` — no tag has that slug.\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Tag usage retrieved successfully\",\n  \"data\": {\n    \"slug\": \"3f1c9a2e-7b4d-4e0a-9c61-2d8f5b0e41a7\",\n    \"label\": \"VIP\",\n    \"value\": \"VIP\",\n    \"color\": \"#F59E0B\",\n    \"is_archived\": false,\n    \"usage_count\": 12,\n    \"units\": [\n      { \"slug\": \"contacts\", \"name\": \"Contacts\", \"count\": 9 },\n      { \"slug\": \"deals\", \"name\": \"Deals\", \"count\": 3 }\n    ]\n  }\n}\n```",
        "tags": [
          "Object - Fields - Tag"
        ],
        "operationId": "TagController_usage",
        "parameters": [
          {
            "name": "tagSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "slug": {},
                    "label": {
                      "type": "string"
                    },
                    "value": {
                      "type": "string"
                    },
                    "color": {},
                    "icon": {},
                    "is_archived": {
                      "type": "boolean",
                      "description": "A retired tag: still rendered on records that hold it, never pickable."
                    },
                    "usage_count": {
                      "type": "number",
                      "description": "Live records holding this tag across every Unit."
                    },
                    "units": {}
                  },
                  "required": [
                    "slug",
                    "label",
                    "value",
                    "color",
                    "icon",
                    "is_archived",
                    "usage_count",
                    "units"
                  ],
                  "description": "A tag on the wire.\n\nThe option's `id` is published as `slug`, matching how `TagFieldResponseDto`\nalready exposes it — a numeric/opaque identifier never crosses the wire under\nthe name `id` (CLAUDE.md §4.2)."
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/object/field/tag/{tagSlug}": {
      "delete": {
        "summary": "Delete a tag, or retire it when records still hold it.",
        "description": "Delete a tag, or retire it when records still hold it.\n\nA tag that no record holds is removed. A tag that records still hold is\nretired instead: those records keep showing it, but it leaves the default\ntag list. `data.archived` tells you which happened. There is no option to\nforce the removal of a tag in use.\n\nScope: `object.update`.\n\nErrors:\n- `404` — no tag has that slug.\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Tag is still in use and was retired instead of deleted\",\n  \"data\": { \"archived\": true }\n}\n```",
        "tags": [
          "Object - Fields - Tag"
        ],
        "operationId": "TagController_remove",
        "parameters": [
          {
            "name": "tagSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "data": {}
                  },
                  "required": [
                    "message",
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Rename or recolor a tag.",
        "description": "Rename or recolor a tag.\n\nSend any of `label`, `color` and `icon`; fields you omit keep their value.\nThe tag's `slug` and `value` never change, so records that hold the tag\nkeep it and show the new label. This response does not count usage:\n`usage_count` is `0` and `units` is empty. Read\n`GET /object/field/tag/{tagSlug}/usage` for the real numbers.\n\nScope: `object.update`.\n\nErrors:\n- `400` — another tag already has this label\n  (`code: DUPLICATE_OPTION_LABEL`).\n- `404` — no tag has that slug.\n- `422` — the body is invalid, for example a `color` that is not a hex\n  color, or a property other than `label`, `color` and `icon`.\n\nRequest:\n\n```json\n{\n  \"label\": \"Key account\",\n  \"color\": \"#EC4899\"\n}\n```\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Tag updated successfully\",\n  \"data\": {\n    \"slug\": \"3f1c9a2e-7b4d-4e0a-9c61-2d8f5b0e41a7\",\n    \"label\": \"Key account\",\n    \"value\": \"VIP\",\n    \"color\": \"#EC4899\",\n    \"is_archived\": false,\n    \"usage_count\": 0,\n    \"units\": []\n  }\n}\n```",
        "tags": [
          "Object - Fields - Tag"
        ],
        "operationId": "TagController_update",
        "parameters": [
          {
            "name": "tagSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "slug": {},
                    "label": {
                      "type": "string"
                    },
                    "value": {
                      "type": "string"
                    },
                    "color": {},
                    "icon": {},
                    "is_archived": {
                      "type": "boolean",
                      "description": "A retired tag: still rendered on records that hold it, never pickable."
                    },
                    "usage_count": {
                      "type": "number",
                      "description": "Live records holding this tag across every Unit."
                    },
                    "units": {}
                  },
                  "required": [
                    "slug",
                    "label",
                    "value",
                    "color",
                    "icon",
                    "is_archived",
                    "usage_count",
                    "units"
                  ],
                  "description": "A tag on the wire.\n\nThe option's `id` is published as `slug`, matching how `TagFieldResponseDto`\nalready exposes it — a numeric/opaque identifier never crosses the wire under\nthe name `id` (CLAUDE.md §4.2)."
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "label": {},
                  "color": {},
                  "icon": {}
                },
                "description": "A rename or recolor. Neither `id` nor `value` is accepted: records store the\nid, and chat-label mapping keys on the value, so both are identity, not copy."
              }
            }
          }
        }
      }
    },
    "/api/v1/object/features": {
      "get": {
        "summary": "List which optional record features this deployment has turned on.",
        "description": "List which optional record features this deployment has turned on.\n\nSome routes exist only when their feature is on and answer `404`\notherwise. Read this once and hide what the deployment does not offer:\n`record_duplicate` covers record duplication, `record_activity_tab` the\nrecord activity feed, `record_signals` the record badges route. The\nflags are the same for every caller. It needs no scope.\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Feature flags resolved successfully\",\n  \"data\": {\n    \"views_config_engine\": true,\n    \"record_duplicate\": true,\n    \"record_signals\": false,\n    \"record_activity_tab\": true,\n    \"unit_duplicate\": false\n  }\n}\n```",
        "tags": [
          "Crm"
        ],
        "operationId": "ObjectController_features",
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "data": {}
                  },
                  "required": [
                    "message",
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/object": {
      "get": {
        "summary": "List objects, one page at a time.",
        "description": "List objects, one page at a time.\n\nReturns the objects whose records the credential can read\n(`read.<object slug>`). A credential with `object.read` gets every object.\nResults are ordered active first, then by `sort_order`, then newest first.\nItems carry the listed fields only; `is_system`, `is_public`, `options`\nand `updated_at` are not included here. It needs no scope.\n\nQuery parameters:\n- `page` — page number, from 1. Default `1`.\n- `limit` — page size, 1 to 200. Default `50`.\n- `search` — keep objects whose name, slug or description contains this\n  text, ignoring letter case. At most 100 characters.\n- `is_active` — `true` or `false` to keep only active or inactive objects.\n\nErrors:\n- `422` — a query parameter is invalid, for example `limit` above 200.\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Objects retrieved successfully\",\n  \"data\": [\n    {\n      \"id\": 37,\n      \"name\": \"Contacts\",\n      \"slug\": \"contacts\",\n      \"icon\": \"tabler-users\",\n      \"description\": \"People we sell to\",\n      \"permission_name\": \"contacts\",\n      \"is_active\": true,\n      \"sort_order\": 2,\n      \"created_at\": \"2026-07-20T09:12:44.000Z\",\n      \"folder_slug\": null,\n      \"default_view_slug\": null,\n      \"default_view_type\": null\n    }\n  ],\n  \"pagination\": {\n    \"total\": 1,\n    \"page\": 1,\n    \"limit\": 50,\n    \"totalPages\": 1,\n    \"nextPage\": null,\n    \"prevPage\": null\n  }\n}\n```",
        "tags": [
          "Crm"
        ],
        "operationId": "ObjectController_get",
        "parameters": [
          {
            "name": "query",
            "in": "query",
            "required": false,
            "schema": {}
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "number"
                    },
                    "name": {
                      "type": "string"
                    },
                    "slug": {
                      "type": "string"
                    },
                    "icon": {},
                    "description": {},
                    "permission_name": {},
                    "is_active": {
                      "type": "boolean"
                    },
                    "sort_order": {
                      "type": "number"
                    },
                    "is_system": {
                      "type": "boolean"
                    },
                    "is_public": {
                      "type": "boolean"
                    },
                    "options": {},
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updated_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "folder_slug": {},
                    "default_view_slug": {},
                    "default_view_type": {}
                  },
                  "required": [
                    "id",
                    "name",
                    "slug",
                    "icon",
                    "description",
                    "permission_name",
                    "is_active",
                    "sort_order",
                    "is_system",
                    "is_public",
                    "options",
                    "created_at",
                    "updated_at",
                    "folder_slug",
                    "default_view_slug",
                    "default_view_type"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create an object.",
        "description": "Create an object.\n\nCreates the object with a generated 12-digit `slug` and its default\nfields: a name, a status and a description field, plus the workspace tag\nfield. Default field labels use the language in the `X-App-Locale`\nheader, else `Accept-Language` (`en`, `ar` or `tr`; default `en`). The\ncredential's owner gets full access to the new object's records; other\nroles get access to their own records only. A `slug` in the body is\nignored.\n\nScope: `object.create`.\n\nErrors:\n- `400` — the name is empty after trimming.\n- `409` — an active object already has this name, ignoring letter case.\n- `422` — the body is invalid, for example a `name` over 100 characters.\n\nRequest:\n\n```json\n{\n  \"name\": \"Partners\",\n  \"description\": \"Resellers and referral partners\",\n  \"icon\": \"tabler-building-store\"\n}\n```\n\nResponse `201`:\n\n```json\n{\n  \"status\": 201,\n  \"message\": \"Object created successfully\",\n  \"data\": {\n    \"id\": 52,\n    \"name\": \"Partners\",\n    \"slug\": \"482913570264\",\n    \"icon\": \"tabler-building-store\",\n    \"description\": \"Resellers and referral partners\",\n    \"permission_name\": \"482913570264\",\n    \"is_active\": true,\n    \"sort_order\": 0,\n    \"is_system\": false,\n    \"is_public\": false,\n    \"options\": null,\n    \"created_at\": \"2026-09-14T10:00:00.000Z\",\n    \"updated_at\": \"2026-09-14T10:00:00.000Z\",\n    \"folder_slug\": null,\n    \"default_view_slug\": null,\n    \"default_view_type\": null\n  }\n}\n```",
        "tags": [
          "Crm"
        ],
        "operationId": "ObjectController_create",
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "number"
                    },
                    "name": {
                      "type": "string"
                    },
                    "slug": {
                      "type": "string"
                    },
                    "icon": {},
                    "description": {},
                    "permission_name": {},
                    "is_active": {
                      "type": "boolean"
                    },
                    "sort_order": {
                      "type": "number"
                    },
                    "is_system": {
                      "type": "boolean"
                    },
                    "is_public": {
                      "type": "boolean"
                    },
                    "options": {},
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updated_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "folder_slug": {},
                    "default_view_slug": {},
                    "default_view_type": {}
                  },
                  "required": [
                    "id",
                    "name",
                    "slug",
                    "icon",
                    "description",
                    "permission_name",
                    "is_active",
                    "sort_order",
                    "is_system",
                    "is_public",
                    "options",
                    "created_at",
                    "updated_at",
                    "folder_slug",
                    "default_view_slug",
                    "default_view_type"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "description": {},
                  "icon": {},
                  "is_active": {},
                  "slug": {}
                },
                "required": [
                  "name"
                ]
              }
            }
          }
        }
      }
    },
    "/api/v1/object/active": {
      "get": {
        "summary": "List the active objects the credential can read.",
        "description": "List the active objects the credential can read.\n\nReturns every active object with `read.<object slug>` in the credential's\nscopes, ordered by `sort_order` and then name. The list is not paginated.\n`default_view_slug` and `default_view_type` name the default saved view\nof the credential's owner for that object, or are `null`. It needs no\nscope.\n\nQuery parameters:\n- `search` — keep objects whose name or slug contains this text,\n  ignoring letter case. Other query parameters are ignored.\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Active objects retrieved successfully\",\n  \"data\": [\n    {\n      \"id\": 37,\n      \"name\": \"Contacts\",\n      \"slug\": \"contacts\",\n      \"icon\": \"tabler-users\",\n      \"permission_name\": \"contacts\",\n      \"sort_order\": 2,\n      \"folder_slug\": null,\n      \"default_view_slug\": \"5c0e7a9b3d21\",\n      \"default_view_type\": \"table\"\n    }\n  ]\n}\n```",
        "tags": [
          "Crm"
        ],
        "operationId": "ObjectController_getActive",
        "parameters": [
          {
            "name": "search",
            "in": "query",
            "required": false,
            "schema": {}
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "number"
                    },
                    "name": {
                      "type": "string"
                    },
                    "slug": {
                      "type": "string"
                    },
                    "icon": {},
                    "description": {},
                    "permission_name": {},
                    "is_active": {
                      "type": "boolean"
                    },
                    "sort_order": {
                      "type": "number"
                    },
                    "is_system": {
                      "type": "boolean"
                    },
                    "is_public": {
                      "type": "boolean"
                    },
                    "options": {},
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updated_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "folder_slug": {},
                    "default_view_slug": {},
                    "default_view_type": {}
                  },
                  "required": [
                    "id",
                    "name",
                    "slug",
                    "icon",
                    "description",
                    "permission_name",
                    "is_active",
                    "sort_order",
                    "is_system",
                    "is_public",
                    "options",
                    "created_at",
                    "updated_at",
                    "folder_slug",
                    "default_view_slug",
                    "default_view_type"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/object/active/stat": {
      "get": {
        "summary": "Count records and saved views for each active object.",
        "description": "Count records and saved views for each active object.\n\nReturns one row per active object, ordered by `sort_order` and then name:\n`data_count` is the number of live records, and `views_count` the number\nof saved views. Counts can lag behind recent writes by a few minutes. The\nlist is not paginated. It needs no scope.\n\nResponse `200` (abridged):\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Active objects retrieved successfully\",\n  \"data\": [\n    {\n      \"id\": 37,\n      \"name\": \"Contacts\",\n      \"slug\": \"contacts\",\n      \"icon\": \"tabler-users\",\n      \"views_count\": 3,\n      \"data_count\": 1284\n    }\n  ]\n}\n```",
        "tags": [
          "Crm"
        ],
        "operationId": "ObjectController_getActiveStat",
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "data": {
                      "type": "array",
                      "items": {}
                    }
                  },
                  "required": [
                    "message",
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/object/overview": {
      "get": {
        "summary": "Get workspace totals for records, saved views and objects.",
        "description": "Get workspace totals for records, saved views and objects.\n\n`records` is the number of live records across all active objects, and\n`records_prev_week` the same total as it stood seven days ago.\n`views_series_7d` holds the running total of saved views at the end of\neach of the last seven days, oldest first; `views` is its last value.\n`active_units` and `total_units` count active and all objects. Values can\nlag behind recent writes by up to five minutes. It needs no scope.\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Home overview retrieved successfully\",\n  \"data\": {\n    \"records\": 5230,\n    \"records_prev_week\": 4987,\n    \"views\": 14,\n    \"views_series_7d\": [11, 11, 12, 12, 13, 14, 14],\n    \"active_units\": 6,\n    \"total_units\": 8\n  }\n}\n```",
        "tags": [
          "Crm"
        ],
        "operationId": "ObjectController_getHomeOverview",
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "records": {
                      "type": "number",
                      "description": "Current total records across all active objects."
                    },
                    "records_prev_week": {
                      "type": "number",
                      "description": "Records total as of 7 days ago — client derives the weekly trend."
                    },
                    "views": {
                      "type": "number",
                      "description": "Current total saved custom views."
                    },
                    "views_series_7d": {
                      "type": "number",
                      "description": "End-of-day cumulative view totals, last 7 days, oldest → newest."
                    },
                    "active_units": {
                      "type": "number",
                      "description": "Active objects (units)."
                    },
                    "total_units": {
                      "type": "number",
                      "description": "All objects, active + inactive."
                    }
                  },
                  "required": [
                    "records",
                    "records_prev_week",
                    "views",
                    "views_series_7d",
                    "active_units",
                    "total_units"
                  ],
                  "description": "Response shape for `GET /object/overview` — the Home screen KPI tiles\n(records week-over-week delta, views sparkline, unit counts)."
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/object/dedup-rules": {
      "get": {
        "summary": "List the duplicate-detection rule of every object.",
        "description": "List the duplicate-detection rule of every object.\n\nReturns one entry per object, active or not, ordered by object name. An\nobject that never had a rule set comes back with mode `off`. Each entry\nhas the same fields as `GET /object/{objectSlug}/dedup-rule`, plus\n`object_slug`. The list is not paginated.\n\nScope: `object.dedupRule.read`.\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Deduplication rules retrieved successfully\",\n  \"data\": [\n    {\n      \"object_slug\": \"contacts\",\n      \"mode\": \"separate_flag\",\n      \"match_keys\": [\"phone-q8w2e4r6\", \"email-p0zx7c1v\"],\n      \"match_logic\": \"or\"\n    },\n    {\n      \"object_slug\": \"deals\",\n      \"mode\": \"off\",\n      \"match_keys\": [],\n      \"match_logic\": \"or\"\n    }\n  ]\n}\n```",
        "tags": [
          "Crm"
        ],
        "operationId": "ObjectController_getAllDedupRules",
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object_slug": {
                      "type": "string"
                    },
                    "mode": {
                      "type": "string",
                      "enum": [
                        "off",
                        "separate_flag",
                        "unify",
                        "mirror",
                        "comment"
                      ]
                    },
                    "match_keys": {
                      "type": "string"
                    },
                    "match_logic": {
                      "type": "string",
                      "enum": [
                        "or",
                        "and"
                      ]
                    },
                    "comment_targets": {}
                  },
                  "required": [
                    "object_slug",
                    "mode",
                    "match_keys",
                    "match_logic"
                  ],
                  "description": "One element of the bulk dedup-rules read (`GET /object/dedup-rules`).\n\nSame per-rule shape as `DedupRuleResponseDto`, plus the `object_slug` it\nbelongs to, so the settings list can render every object's mode chip from a\nsingle response (no per-object N+1). Objects with no configured rule are\nreturned explicitly as Off."
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/object/check-name": {
      "get": {
        "summary": "Check whether an object name is free to use.",
        "description": "Check whether an object name is free to use.\n\nA name is taken when an active object already has it, ignoring letter\ncase. Inactive objects do not hold their names. When you rename an\nobject, pass its slug in `exclude_slug` so it does not collide with\nitself. Create and update run the same check, so this call only helps you\nwarn early.\n\nQuery parameters:\n- `name` (required) — the name to check, 1 to 100 characters.\n- `exclude_slug` — the slug of an object to leave out of the check.\n\nScope: `object.read`.\n\nErrors:\n- `422` — `name` is missing or longer than 100 characters.\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"There is already a unit with the same name.\",\n  \"data\": { \"available\": false }\n}\n```",
        "tags": [
          "Crm"
        ],
        "operationId": "ObjectController_checkName",
        "parameters": [
          {
            "name": "query",
            "in": "query",
            "required": false,
            "schema": {}
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "data": {}
                  },
                  "required": [
                    "message",
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/object/{objectSlug}": {
      "get": {
        "summary": "Get one object by slug.",
        "description": "Get one object by slug.\n\nReturns the object's settings. `options` holds object-level settings\nsuch as the duplicate-detection rule, or `null`.\n\nScope: `read.<object slug>`, for example `read.contacts`.\n\nErrors:\n- `400` — no object has that slug, or the object is inactive.\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Object retrieved successfully\",\n  \"data\": {\n    \"id\": 37,\n    \"name\": \"Contacts\",\n    \"slug\": \"contacts\",\n    \"icon\": \"tabler-users\",\n    \"description\": \"People we sell to\",\n    \"permission_name\": \"contacts\",\n    \"is_active\": true,\n    \"sort_order\": 2,\n    \"is_system\": false,\n    \"is_public\": false,\n    \"options\": null,\n    \"created_at\": \"2026-07-20T09:12:44.000Z\",\n    \"updated_at\": \"2026-09-01T14:03:10.000Z\",\n    \"folder_slug\": null,\n    \"default_view_slug\": null,\n    \"default_view_type\": null\n  }\n}\n```",
        "tags": [
          "Crm"
        ],
        "operationId": "ObjectController_show",
        "parameters": [
          {
            "name": "objectSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "number"
                    },
                    "name": {
                      "type": "string"
                    },
                    "slug": {
                      "type": "string"
                    },
                    "icon": {},
                    "description": {},
                    "permission_name": {},
                    "is_active": {
                      "type": "boolean"
                    },
                    "sort_order": {
                      "type": "number"
                    },
                    "is_system": {
                      "type": "boolean"
                    },
                    "is_public": {
                      "type": "boolean"
                    },
                    "options": {},
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updated_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "folder_slug": {},
                    "default_view_slug": {},
                    "default_view_type": {}
                  },
                  "required": [
                    "id",
                    "name",
                    "slug",
                    "icon",
                    "description",
                    "permission_name",
                    "is_active",
                    "sort_order",
                    "is_system",
                    "is_public",
                    "options",
                    "created_at",
                    "updated_at",
                    "folder_slug",
                    "default_view_slug",
                    "default_view_type"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "put": {
        "summary": "Update an object's name, description, icon, status or folder.",
        "description": "Update an object's name, description, icon, status or folder.\n\nSend only the fields you change. The slug never changes. A new `name` is\ntrimmed and must not match another active object's name, ignoring letter\ncase. `is_public` turns the object's public form on or off.\n`folder_slug` moves the object into a folder; send `null` to take it out.\n\nScope: `object.update`.\n\nErrors:\n- `400` — no object has that slug, the object is inactive, the name is\n  empty, or no folder has that `folder_slug`.\n- `409` — another active object already has this name.\n- `422` — the body is invalid, for example a `slug` property.\n\nRequest:\n\n```json\n{\n  \"name\": \"Customers\",\n  \"icon\": \"tabler-user-check\"\n}\n```\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Object updated successfully\",\n  \"data\": {\n    \"id\": 37,\n    \"name\": \"Customers\",\n    \"slug\": \"contacts\",\n    \"icon\": \"tabler-user-check\",\n    \"description\": \"People we sell to\",\n    \"permission_name\": \"contacts\",\n    \"is_active\": true,\n    \"sort_order\": 2,\n    \"is_system\": false,\n    \"is_public\": false,\n    \"options\": null,\n    \"created_at\": \"2026-07-20T09:12:44.000Z\",\n    \"updated_at\": \"2026-09-14T10:00:00.000Z\",\n    \"folder_slug\": null,\n    \"default_view_slug\": null,\n    \"default_view_type\": null\n  }\n}\n```",
        "tags": [
          "Crm"
        ],
        "operationId": "ObjectController_update",
        "parameters": [
          {
            "name": "objectSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "number"
                    },
                    "name": {
                      "type": "string"
                    },
                    "slug": {
                      "type": "string"
                    },
                    "icon": {},
                    "description": {},
                    "permission_name": {},
                    "is_active": {
                      "type": "boolean"
                    },
                    "sort_order": {
                      "type": "number"
                    },
                    "is_system": {
                      "type": "boolean"
                    },
                    "is_public": {
                      "type": "boolean"
                    },
                    "options": {},
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updated_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "folder_slug": {},
                    "default_view_slug": {},
                    "default_view_type": {}
                  },
                  "required": [
                    "id",
                    "name",
                    "slug",
                    "icon",
                    "description",
                    "permission_name",
                    "is_active",
                    "sort_order",
                    "is_system",
                    "is_public",
                    "options",
                    "created_at",
                    "updated_at",
                    "folder_slug",
                    "default_view_slug",
                    "default_view_type"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {},
                  "description": {},
                  "icon": {},
                  "is_active": {},
                  "is_public": {},
                  "folder_slug": {}
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete an object and all of its records.",
        "description": "Delete an object and all of its records.\n\nRemoves the object, its fields and its stored records. This cannot be\nundone. Remove the object's relations first. System objects cannot be\ndeleted.\n\nScope: `object.delete`.\n\nErrors:\n- `400` — no object has that slug, the object is inactive, or it is a\n  system object.\n- `500` — the object still has relations to other objects.\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Object deleted successfully\",\n  \"data\": null\n}\n```",
        "tags": [
          "Crm"
        ],
        "operationId": "ObjectController_remove",
        "parameters": [
          {
            "name": "objectSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "message"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/object/{objectSlug}/other-objects": {
      "get": {
        "summary": "List the other active objects, for picking a relation target.",
        "description": "List the other active objects, for picking a relation target.\n\nReturns every active object except the one in the path, ordered by name,\nwith its `id`, `name`, `slug` and `icon`. The list is not paginated.\n\nScope: `read.<object slug>` on the object in the path.\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Other objects retrieved successfully\",\n  \"data\": [\n    {\n      \"id\": 41,\n      \"name\": \"Deals\",\n      \"slug\": \"deals\",\n      \"icon\": \"tabler-briefcase\",\n      \"folder_slug\": null,\n      \"default_view_slug\": null,\n      \"default_view_type\": null\n    }\n  ]\n}\n```",
        "tags": [
          "Crm"
        ],
        "operationId": "ObjectController_getOtherObjects",
        "parameters": [
          {
            "name": "objectSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "number"
                    },
                    "name": {
                      "type": "string"
                    },
                    "slug": {
                      "type": "string"
                    },
                    "icon": {},
                    "description": {},
                    "permission_name": {},
                    "is_active": {
                      "type": "boolean"
                    },
                    "sort_order": {
                      "type": "number"
                    },
                    "is_system": {
                      "type": "boolean"
                    },
                    "is_public": {
                      "type": "boolean"
                    },
                    "options": {},
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updated_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "folder_slug": {},
                    "default_view_slug": {},
                    "default_view_type": {}
                  },
                  "required": [
                    "id",
                    "name",
                    "slug",
                    "icon",
                    "description",
                    "permission_name",
                    "is_active",
                    "sort_order",
                    "is_system",
                    "is_public",
                    "options",
                    "created_at",
                    "updated_at",
                    "folder_slug",
                    "default_view_slug",
                    "default_view_type"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/object/{objectSlug}/fields": {
      "get": {
        "summary": "List the active fields of an object.",
        "description": "List the active fields of an object.\n\nReturns the field slugs you need to read and write records: a record's\n`data` is keyed by these slugs, never by labels. `label` falls back to\nthe field name when no label is set. `options` holds the field type's\nsettings, such as the choices of a `select` field, or `null`. The list is\nnot paginated. It needs no scope.\n\nErrors:\n- `400` — no object has that slug, or the object is inactive.\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Object fields retrieved successfully\",\n  \"data\": [\n    {\n      \"slug\": \"title-mpoc3dn1\",\n      \"label\": \"Name\",\n      \"type\": \"text\",\n      \"options\": null,\n      \"is_system\": true\n    },\n    {\n      \"slug\": \"stage-k2j9h4g7\",\n      \"label\": \"Stage\",\n      \"type\": \"select\",\n      \"options\": {\n        \"values\": [\n          {\n            \"id\": \"6a1f3c9e-2b7d-4e8a-9f0c-1d2e3f4a5b6c\",\n            \"value\": \"lead\",\n            \"label\": \"Lead\",\n            \"color\": \"#3B82F6\"\n          }\n        ]\n      },\n      \"is_system\": false\n    }\n  ]\n}\n```",
        "tags": [
          "Crm"
        ],
        "operationId": "ObjectController_getObjectFields",
        "parameters": [
          {
            "name": "objectSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "data": {
                      "type": "array",
                      "items": {}
                    }
                  },
                  "required": [
                    "message",
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/object/{objectSlug}/dedup-rule": {
      "get": {
        "summary": "Get the duplicate-detection rule of an object.",
        "description": "Get the duplicate-detection rule of an object.\n\nThe rule decides what happens when a record created from an incoming\nchat matches existing records on `match_keys`, which are field slugs.\n`match_logic` `or` matches on any key, `and` on all of them. An object\nthat never had a rule set returns mode `off`. See\n`PUT /object/{objectSlug}/dedup-rule` for the modes.\n\nScope: `object.dedupRule.read`.\n\nErrors:\n- `400` — no object has that slug, or the object is inactive.\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Deduplication rule retrieved successfully\",\n  \"data\": {\n    \"mode\": \"comment\",\n    \"match_keys\": [\"phone-q8w2e4r6\"],\n    \"match_logic\": \"or\",\n    \"comment_targets\": \"original\"\n  }\n}\n```",
        "tags": [
          "Crm"
        ],
        "operationId": "ObjectController_getDedupRule",
        "parameters": [
          {
            "name": "objectSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "mode": {
                      "type": "string",
                      "enum": [
                        "off",
                        "separate_flag",
                        "unify",
                        "mirror",
                        "comment"
                      ]
                    },
                    "match_keys": {
                      "type": "string"
                    },
                    "match_logic": {
                      "type": "string",
                      "enum": [
                        "or",
                        "and"
                      ]
                    },
                    "comment_targets": {}
                  },
                  "required": [
                    "mode",
                    "match_keys",
                    "match_logic"
                  ],
                  "description": "API response shape for an object's deduplication rule.\n\n`match_keys` are field slugs (stable ids), never labels."
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "put": {
        "summary": "Set the duplicate-detection rule of an object.",
        "description": "Set the duplicate-detection rule of an object.\n\nReplaces the whole rule. It applies when a record is created from an\nincoming chat and matches existing records on `match_keys`:\n- `off` — no duplicate detection.\n- `separate_flag` — create a new record and flag it as a duplicate.\n- `unify` — add the chat to the most recently updated match and create no\n  record.\n- `mirror` — create a new record, flag it, and show the chat under every\n  match.\n- `comment` — create a new record, flag it, and post a note on the most\n  recently updated match (`comment_targets` `original`) or on every match\n  (`all`).\n\n`match_keys` are field slugs of active fields on this object.\n`match_logic` is `or` (any key matches) or `and` (all keys match).\n\nScope: `object.dedupRule.update`.\n\nErrors:\n- `400` — a match key is not an active field of the object.\n- `404` — no object has that slug.\n- `422` — the body is invalid, for example an unknown `mode`.\n\nRequest:\n\n```json\n{\n  \"mode\": \"separate_flag\",\n  \"match_keys\": [\"phone-q8w2e4r6\", \"email-p0zx7c1v\"],\n  \"match_logic\": \"or\"\n}\n```\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Deduplication rule updated successfully\",\n  \"data\": {\n    \"mode\": \"separate_flag\",\n    \"match_keys\": [\"phone-q8w2e4r6\", \"email-p0zx7c1v\"],\n    \"match_logic\": \"or\"\n  }\n}\n```",
        "tags": [
          "Crm"
        ],
        "operationId": "ObjectController_updateDedupRule",
        "parameters": [
          {
            "name": "objectSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "mode": {
                      "type": "string",
                      "enum": [
                        "off",
                        "separate_flag",
                        "unify",
                        "mirror",
                        "comment"
                      ]
                    },
                    "match_keys": {
                      "type": "string"
                    },
                    "match_logic": {
                      "type": "string",
                      "enum": [
                        "or",
                        "and"
                      ]
                    },
                    "comment_targets": {}
                  },
                  "required": [
                    "mode",
                    "match_keys",
                    "match_logic"
                  ],
                  "description": "API response shape for an object's deduplication rule.\n\n`match_keys` are field slugs (stable ids), never labels."
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "mode": {
                    "type": "string",
                    "enum": [
                      "off",
                      "separate_flag",
                      "unify",
                      "mirror",
                      "comment"
                    ]
                  },
                  "match_keys": {
                    "type": "string"
                  },
                  "match_logic": {
                    "type": "string",
                    "enum": [
                      "or",
                      "and"
                    ]
                  },
                  "comment_targets": {}
                },
                "required": [
                  "mode",
                  "match_keys",
                  "match_logic"
                ],
                "description": "Request body for updating an object's deduplication rule.\n\n`match_keys` are FIELD SLUGS (stable ids) — never labels (CODE_PRINCIPLES Rule 10)."
              }
            }
          }
        }
      }
    },
    "/api/v1/object/order": {
      "put": {
        "summary": "Change the display order of objects.",
        "description": "Change the display order of objects.\n\nSend each object's `slug` with its new `sort_order`. Objects you leave\nout keep their current value. Returns every object, active and inactive,\nordered by `sort_order`.\n\nScope: `object.update`.\n\nErrors:\n- `400` — no object has one of the slugs.\n- `422` — the body is invalid, for example a `sort_order` that is not a\n  number.\n\nRequest:\n\n```json\n{\n  \"objects\": [\n    { \"slug\": \"deals\", \"sort_order\": 1 },\n    { \"slug\": \"contacts\", \"sort_order\": 2 }\n  ]\n}\n```\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Object order updated successfully\",\n  \"data\": [\n    {\n      \"id\": 37,\n      \"name\": \"Contacts\",\n      \"slug\": \"contacts\",\n      \"icon\": \"tabler-users\",\n      \"description\": \"People we sell to\",\n      \"permission_name\": \"contacts\",\n      \"is_active\": true,\n      \"sort_order\": 2,\n      \"is_system\": false,\n      \"is_public\": false,\n      \"options\": null,\n      \"created_at\": \"2026-07-20T09:12:44.000Z\",\n      \"updated_at\": \"2026-09-01T14:03:10.000Z\",\n      \"folder_slug\": null,\n      \"default_view_slug\": null,\n      \"default_view_type\": null\n    }\n  ]\n}\n```",
        "tags": [
          "Crm"
        ],
        "operationId": "ObjectController_updateOrder",
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "number"
                    },
                    "name": {
                      "type": "string"
                    },
                    "slug": {
                      "type": "string"
                    },
                    "icon": {},
                    "description": {},
                    "permission_name": {},
                    "is_active": {
                      "type": "boolean"
                    },
                    "sort_order": {
                      "type": "number"
                    },
                    "is_system": {
                      "type": "boolean"
                    },
                    "is_public": {
                      "type": "boolean"
                    },
                    "options": {},
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updated_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "folder_slug": {},
                    "default_view_slug": {},
                    "default_view_type": {}
                  },
                  "required": [
                    "id",
                    "name",
                    "slug",
                    "icon",
                    "description",
                    "permission_name",
                    "is_active",
                    "sort_order",
                    "is_system",
                    "is_public",
                    "options",
                    "created_at",
                    "updated_at",
                    "folder_slug",
                    "default_view_slug",
                    "default_view_type"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "objects": {
                    "type": "array",
                    "items": {}
                  }
                },
                "required": [
                  "objects"
                ]
              }
            }
          }
        }
      }
    },
    "/api/v1/object/toggle-status/{objectSlug}": {
      "put": {
        "summary": "Turn an object on or off.",
        "description": "Turn an object on or off.\n\nNo request body. Flips `is_active` and returns the updated object. An\ninactive object is left out of `GET /object/active`, and routes that look\nan object up by slug, such as `GET /object/{objectSlug}`, answer `400`\nfor it until you turn it back on. The message says which way it flipped.\n\nScope: `object.activate`.\n\nErrors:\n- `400` — no object has that slug.\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Object deactivated successfully\",\n  \"data\": {\n    \"id\": 37,\n    \"name\": \"Contacts\",\n    \"slug\": \"contacts\",\n    \"icon\": \"tabler-users\",\n    \"description\": \"People we sell to\",\n    \"permission_name\": \"contacts\",\n    \"is_active\": false,\n    \"sort_order\": 2,\n    \"is_system\": false,\n    \"is_public\": false,\n    \"options\": null,\n    \"created_at\": \"2026-07-20T09:12:44.000Z\",\n    \"updated_at\": \"2026-09-14T10:00:00.000Z\",\n    \"folder_slug\": null,\n    \"default_view_slug\": null,\n    \"default_view_type\": null\n  }\n}\n```",
        "tags": [
          "Crm"
        ],
        "operationId": "ObjectController_toggleStatus",
        "parameters": [
          {
            "name": "objectSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "number"
                    },
                    "name": {
                      "type": "string"
                    },
                    "slug": {
                      "type": "string"
                    },
                    "icon": {},
                    "description": {},
                    "permission_name": {},
                    "is_active": {
                      "type": "boolean"
                    },
                    "sort_order": {
                      "type": "number"
                    },
                    "is_system": {
                      "type": "boolean"
                    },
                    "is_public": {
                      "type": "boolean"
                    },
                    "options": {},
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updated_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "folder_slug": {},
                    "default_view_slug": {},
                    "default_view_type": {}
                  },
                  "required": [
                    "id",
                    "name",
                    "slug",
                    "icon",
                    "description",
                    "permission_name",
                    "is_active",
                    "sort_order",
                    "is_system",
                    "is_public",
                    "options",
                    "created_at",
                    "updated_at",
                    "folder_slug",
                    "default_view_slug",
                    "default_view_type"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/object/toggle-public/{objectSlug}": {
      "put": {
        "summary": "Turn an object's public form on or off.",
        "description": "Turn an object's public form on or off.\n\nNo request body. Flips `is_public` and returns the updated object. While\n`is_public` is `true` and the object is active, anonymous visitors can\nsubmit records through its public form. The message says which way it\nflipped.\n\nScope: `object.update`.\n\nErrors:\n- `400` — no object has that slug.\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Object is now public\",\n  \"data\": {\n    \"id\": 37,\n    \"name\": \"Contacts\",\n    \"slug\": \"contacts\",\n    \"icon\": \"tabler-users\",\n    \"description\": \"People we sell to\",\n    \"permission_name\": \"contacts\",\n    \"is_active\": true,\n    \"sort_order\": 2,\n    \"is_system\": false,\n    \"is_public\": true,\n    \"options\": null,\n    \"created_at\": \"2026-07-20T09:12:44.000Z\",\n    \"updated_at\": \"2026-09-14T10:00:00.000Z\",\n    \"folder_slug\": null,\n    \"default_view_slug\": null,\n    \"default_view_type\": null\n  }\n}\n```",
        "tags": [
          "Crm"
        ],
        "operationId": "ObjectController_togglePublic",
        "parameters": [
          {
            "name": "objectSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "number"
                    },
                    "name": {
                      "type": "string"
                    },
                    "slug": {
                      "type": "string"
                    },
                    "icon": {},
                    "description": {},
                    "permission_name": {},
                    "is_active": {
                      "type": "boolean"
                    },
                    "sort_order": {
                      "type": "number"
                    },
                    "is_system": {
                      "type": "boolean"
                    },
                    "is_public": {
                      "type": "boolean"
                    },
                    "options": {},
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updated_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "folder_slug": {},
                    "default_view_slug": {},
                    "default_view_type": {}
                  },
                  "required": [
                    "id",
                    "name",
                    "slug",
                    "icon",
                    "description",
                    "permission_name",
                    "is_active",
                    "sort_order",
                    "is_system",
                    "is_public",
                    "options",
                    "created_at",
                    "updated_at",
                    "folder_slug",
                    "default_view_slug",
                    "default_view_type"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/object/data/{objectSlug}/lookup": {
      "get": {
        "summary": "List records as slug and name pairs, for pickers.",
        "description": "List records as slug and name pairs, for pickers.\n\nReturns one page of the object's records with only `slug` and `name`,\nsorted by name. `search` matches the record name anywhere in the text and\nignores case. Only records the credential can see are returned.\n\nQuery parameters:\n- `search` — text to find in the record name. At most 100 characters.\n- `page` — page number, from 1. Default `1`.\n- `limit` — records per page. Default `25`. A value from `101` to `200`\n  returns 100 records.\n\nScope: `read.<object slug>`, for example `read.contacts`.\n\nErrors:\n- `400` — no active object has that slug.\n- `403` — the credential lacks `read.<object slug>`.\n- `422` — a query parameter is invalid, for example `limit` is above\n  `200`.\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Records retrieved successfully\",\n  \"data\": [\n    { \"slug\": \"9f2c4b7a1e03\", \"name\": \"Sara Ali\" },\n    { \"slug\": \"4d7b1e9a2c65\", \"name\": \"Sarah Morgan\" }\n  ],\n  \"pagination\": {\n    \"total\": 2,\n    \"page\": 1,\n    \"limit\": 25,\n    \"totalPages\": 1,\n    \"nextPage\": null,\n    \"prevPage\": null\n  }\n}\n```",
        "tags": [
          "Records"
        ],
        "operationId": "DataReadController_getRecordLookup",
        "parameters": [
          {
            "name": "objectSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "query",
            "in": "query",
            "required": false,
            "schema": {}
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "data": {
                      "type": "array",
                      "items": {}
                    },
                    "pagination": {}
                  },
                  "required": [
                    "message",
                    "data",
                    "pagination"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/object/data/{objectSlug}": {
      "get": {
        "summary": "List an object's records, with filters and sorting.",
        "description": "List an object's records, with filters and sorting.\n\nReturns one page of records in `data.data` and the object's columns in\n`data.fields`: its fields first, then one entry per relation. Only records\nthe credential can see are returned. Without `filters` or `sortBy`, the\nnewest records come first.\n\nRecord values are keyed by field slug. Each record also carries `name`,\n`assignees`, `creator`, `updater`, `createdAt`, `updatedAt`, and\n`capabilities` (what the credential may do to that record). Linked records\nlive in `relations`, keyed by relation slug. Each relation cell holds up to\n10 linked records and a `pagination` block; follow `pagination.nextPage` on\n`GET /object/data/{objectSlug}/{dataSlug}/relations/{relationSlug}`.\n\nA record carries `is_duplicate` only when the credential holds\n`duplicate-flag.view`. It carries `signals` (open tasks, overdue, unread\nmessages, mentions) only when `GET /object/features` reports\n`record_signals: true`.\n\nQuery parameters:\n- `page` — page number, from 1. Default `1`.\n- `limit` — records per page, up to `200`. Default `25`.\n- `filters` — a URL-encoded JSON array of conditions. A record must match\n  all of them. At most 50 conditions. A value that is not a JSON array is\n  ignored. Each condition has these keys:\n  - `fieldSlug` — a field slug, a relation slug, or one of `created_at`,\n    `updated_at`, `created_by`, `updated_by` and `linked_record`. A\n    condition on an unknown slug is ignored.\n  - `operator` — one of `equals`, `does_not_equal`, `includes`,\n    `does_not_include`, `multi_includes`, `starts_with`, `ends_with`,\n    `is_empty`, `is_not_empty`, `greater_than`, `less_than`,\n    `greater_than_or_equal`, `less_than_or_equal`, `between`, `before`,\n    `after`, `date_between`, `today`, `this_week` or `this_month`. Each\n    entry in `data.fields` lists the operators it supports in\n    `filterOperators`.\n  - `value` — what to compare with. Omit it for `is_empty`,\n    `is_not_empty`, `today`, `this_week` and `this_month`. For\n    `multi_includes`, send an array; a record matches if any entry\n    matches.\n  - `valueTo` — the upper bound for `between` and `date_between`. Both\n    bounds are inclusive.\n- `sortBy` — a field slug, `created_at` or `updated_at`. A belongs-to\n  relation slug sorts by the linked record's name. An unknown value is\n  ignored.\n- `sortDirection` — `asc` or `desc`. Default `asc`.\n- `adminSlug` — member slugs, comma-separated. Returns records assigned to\n  any of them.\n- `unassigned` — `true` returns records with no assignee. Combined with\n  `adminSlug`, a record matches either one.\n\nHow `value` is read:\n- Text: `includes`, `starts_with` and `ends_with` ignore case; `equals`\n  does not.\n- Select, status, priority and source fields: the option `id`. Multi-select\n  and tag fields: `includes` with an option `id`.\n- Dates, `created_at` and `updated_at`: a day as `YYYY-MM-DD`, in the\n  credential owner's timezone. `before` and `after` exclude that day.\n- `created_by` and `updated_by`: a member slug.\n- Relations and `linked_record`: the linked record's slug. Relations\n  support `equals`, `is_empty` and `is_not_empty`.\n\nExample `filters` value, before URL encoding:\n\n```json\n[\n  { \"fieldSlug\": \"email-k2v8x1qa\", \"operator\": \"ends_with\", \"value\": \"@example.com\" },\n  { \"fieldSlug\": \"created_at\", \"operator\": \"date_between\", \"value\": \"2026-09-01\", \"valueTo\": \"2026-09-14\" }\n]\n```\n\nScope: `read.<object slug>`, for example `read.contacts`.\n\nErrors:\n- `400` — no active object has that slug.\n- `400` — `sortBy` names a relation or lookup that links many records\n  (code `RELATION_SORT_UNSUPPORTED` or `LOOKUP_SORT_UNSUPPORTED`).\n- `400` — a condition names a lookup field whose source no longer exists\n  (code `LOOKUP_FILTER_UNSUPPORTED`).\n- `403` — the credential lacks `read.<object slug>`.\n- `422` — a query parameter is invalid, for example an unknown `operator`,\n  more than 50 conditions, or `limit` above `200`.\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Object data retrieved successfully\",\n  \"data\": {\n    \"fields\": [\n      {\n        \"name\": \"name\",\n        \"slug\": \"name-mp9nm29v\",\n        \"label\": \"Name\",\n        \"type\": \"text\",\n        \"icon\": null,\n        \"options\": null,\n        \"is_system\": true,\n        \"api_only\": false,\n        \"field_group\": null,\n        \"required\": false,\n        \"default_value\": null,\n        \"sort_order\": 0,\n        \"is_active\": true,\n        \"filterOperators\": [\"includes\", \"multi_includes\", \"does_not_include\", \"equals\", \"does_not_equal\", \"starts_with\", \"ends_with\", \"is_empty\", \"is_not_empty\"]\n      },\n      {\n        \"name\": \"email\",\n        \"slug\": \"email-k2v8x1qa\",\n        \"label\": \"Email\",\n        \"type\": \"email\",\n        \"icon\": null,\n        \"options\": null,\n        \"is_system\": false,\n        \"api_only\": false,\n        \"field_group\": null,\n        \"required\": false,\n        \"default_value\": null,\n        \"sort_order\": 1,\n        \"is_active\": true,\n        \"filterOperators\": [\"includes\", \"multi_includes\", \"equals\", \"does_not_equal\", \"starts_with\", \"ends_with\", \"is_empty\", \"is_not_empty\"]\n      }\n    ],\n    \"data\": [\n      {\n        \"name-mp9nm29v\": \"Sara Ali\",\n        \"assignees\": [\n          {\n            \"type\": \"user\",\n            \"slug\": \"5e1a9c3b7d20\",\n            \"name\": \"Omar Hassan\",\n            \"email\": \"omar@example.com\",\n            \"profile_image\": null\n          }\n        ],\n        \"email-k2v8x1qa\": \"sara@example.com\",\n        \"name\": \"Sara Ali\",\n        \"relations\": {},\n        \"slug\": \"9f2c4b7a1e03\",\n        \"random_id\": \"a3f9c21b\",\n        \"createdAt\": \"2026-09-14T10:00:00.000Z\",\n        \"updatedAt\": \"2026-09-14T10:00:00.000Z\",\n        \"position\": null,\n        \"creator\": {\n          \"type\": \"user\",\n          \"slug\": \"5e1a9c3b7d20\",\n          \"name\": \"Omar Hassan\",\n          \"email\": \"omar@example.com\",\n          \"profile_image\": null\n        },\n        \"updater\": null,\n        \"linked_record\": null,\n        \"capabilities\": { \"can_edit\": true, \"can_delete\": true, \"can_assign\": true }\n      }\n    ]\n  },\n  \"pagination\": {\n    \"total\": 1,\n    \"page\": 1,\n    \"limit\": 25,\n    \"totalPages\": 1,\n    \"nextPage\": null,\n    \"prevPage\": null\n  }\n}\n```",
        "tags": [
          "Records"
        ],
        "operationId": "DataReadController_getAllDataForObject",
        "parameters": [
          {
            "name": "objectSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "query",
            "in": "query",
            "required": false,
            "schema": {}
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "data": {},
                    "pagination": {}
                  },
                  "required": [
                    "message",
                    "data",
                    "pagination"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create a record.",
        "description": "Create a record.\n\nPut field values in `data`, keyed by field slug, never by field name or\nlabel. Find the slugs with `GET /object/{objectSlug}/fields`. A choice\nfield takes the option `id`, or an array of ids for multi-value fields.\nKeys that are not fields of the object are ignored. Fields you leave out\nget their default value.\n\n`relations` links other records, keyed by relation slug, each value a\nrecord slug or an array of record slugs (at most 1,000). `admin_slug` sets the owner; it\nis ignored when the credential may not assign records, and the owner is\nthen the credential's user. `parent_link` also points a relation of an\nexisting record at the new one, in the same transaction; the response\nthen carries that record in `parent_record`.\n\nSome values are never written: computed fields (`calculation`,\n`serial_number`, `lookup`, `rollup`); fields marked API-only unless you\ncall with an API key or OAuth token; and `source_name`, `session_name`\nand `provider_id` fields when you call with a user session. The keys\nremoved are listed in `dropped_fields`. Non-fatal problems are listed in\n`warnings`.\n\nTo upload files, send `multipart/form-data`: put `data` and `relations`\nas JSON strings, and add each file as a part named after its file or\nphoto field's slug. A file may be up to 100 MB; the field's own settings\ncan narrow the size and the formats.\n\nThe response is the new record, in the same shape as a record read.\n\nScope: `create.<object slug>`, for example `create.contacts`.\n\nErrors:\n- `400` — a value is invalid or a required field is missing (the message\n  starts `Validation failed:`), a required relation is missing, a linked\n  record in `relations` cannot be found or seen, a file is rejected, a\n  linked or parent record does not exist, another record already has the\n  same values for a field set marked as a possible duplicate, or no active\n  object has that slug.\n- `409` — another record already has the same value in a unique field.\n- `422` — the body is invalid, for example `data` is not an object.\n\nRequest:\n\n```json\n{\n  \"data\": {\n    \"title-mpoc3dn1\": \"Sara Ali\",\n    \"email-p0zx7c1v\": \"sara@example.com\",\n    \"stage-k2j9h4g7\": \"6a1f3c9e-2b7d-4e8a-9f0c-1d2e3f4a5b6c\"\n  },\n  \"relations\": { \"company-mf3k2p9a\": \"2e8b5d1f7c4a\" },\n  \"admin_slug\": \"a3f1b2c8e9d7\"\n}\n```\n\nResponse `201`:\n\n```json\n{\n  \"status\": 201,\n  \"message\": \"Data created successfully\",\n  \"data\": {\n    \"title-mpoc3dn1\": \"Sara Ali\",\n    \"assignees\": [\n      {\n        \"type\": \"user\",\n        \"slug\": \"a3f1b2c8e9d7\",\n        \"name\": \"Omar Hassan\",\n        \"email\": \"omar@example.com\",\n        \"profile_image\": null\n      }\n    ],\n    \"email-p0zx7c1v\": \"sara@example.com\",\n    \"stage-k2j9h4g7\": [\n      {\n        \"id\": \"6a1f3c9e-2b7d-4e8a-9f0c-1d2e3f4a5b6c\",\n        \"value\": \"lead\",\n        \"label\": \"Lead\",\n        \"color\": \"#3B82F6\",\n        \"icon\": null\n      }\n    ],\n    \"name\": \"Sara Ali\",\n    \"relations\": {\n      \"company-mf3k2p9a\": {\n        \"items\": [\n          {\n            \"type\": \"relation\",\n            \"name\": \"Acme Inc\",\n            \"label\": \"Acme Inc\",\n            \"slug\": \"2e8b5d1f7c4a\",\n            \"random_id\": \"7d3e9a1c\",\n            \"object_slug\": \"companies\",\n            \"object_name\": \"Companies\",\n            \"object_icon\": \"tabler-building\"\n          }\n        ],\n        \"pagination\": {\n          \"total\": 1,\n          \"page\": 1,\n          \"limit\": 10,\n          \"totalPages\": 1,\n          \"nextPage\": null,\n          \"prevPage\": null\n        }\n      }\n    },\n    \"slug\": \"9f2c4b7a1e03\",\n    \"random_id\": \"5c0e7a9b\",\n    \"createdAt\": \"2026-09-14T10:00:00.000Z\",\n    \"updatedAt\": \"2026-09-14T10:00:00.000Z\",\n    \"position\": \"1000\",\n    \"creator\": {\n      \"type\": \"user\",\n      \"slug\": \"a3f1b2c8e9d7\",\n      \"name\": \"Omar Hassan\",\n      \"email\": \"omar@example.com\",\n      \"profile_image\": null\n    },\n    \"updater\": {\n      \"type\": \"user\",\n      \"slug\": \"a3f1b2c8e9d7\",\n      \"name\": \"Omar Hassan\",\n      \"email\": \"omar@example.com\",\n      \"profile_image\": null\n    },\n    \"linked_record\": null,\n    \"capabilities\": {\n      \"can_edit\": true,\n      \"can_delete\": true,\n      \"can_assign\": true\n    }\n  }\n}\n```",
        "tags": [
          "Records"
        ],
        "operationId": "DataWriteController_create",
        "parameters": [
          {
            "name": "objectSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "dropped_fields": {},
                    "warnings": {},
                    "parent_record": {},
                    "message": {
                      "type": "string"
                    },
                    "data": {}
                  },
                  "required": [
                    "message"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "data": {
                    "description": "Data object where keys are field slugs (not field names)\nExample: { \"first_name\": \"John\", \"email_address\": \"john@example.com\" }"
                  },
                  "relations": {
                    "description": "Relations object where keys are field slugs for relation fields"
                  },
                  "admin_slug": {},
                  "linked_data_slug": {
                    "description": "Optional slug of another data record to link this record to.\nPrimarily used for linking tasks to other objects (customers, orders, etc.)"
                  },
                  "linked_object_slug": {
                    "description": "Slug of the object that owns `linked_data_slug`.\n\nOptional for back-compat: a client that sends only `linked_data_slug` still\nlinks, resolved by scanning object tables for that slug. Sending it here\nskips the scan and, more importantly, removes the ambiguity — slugs are\nunique per table, not per workspace, so two objects can hold the same one."
                  },
                  "parent_link": {
                    "description": "Optional atomic parent link — creates this record AND points a\nrelation field on a pre-existing parent record at the newly created\nrow, inside one transaction. Replaces the legacy two-step FE flow\n(POST → PUT). See `ParentLinkDto`."
                  }
                },
                "required": [
                  "data"
                ]
              }
            }
          }
        }
      }
    },
    "/api/v1/object/data/{objectSlug}/search": {
      "post": {
        "summary": "Search an object's records by free text.",
        "description": "Search an object's records by free text.\n\nFinds records whose text, long text, email, phone, WhatsApp number or\nserial number fields contain `searchTerm`, ignoring case. The record slug\nand its `random_id` are searched too. When the term holds 6 or more digits,\nphone and WhatsApp numbers also match on digits alone, so `201505779515`\nfinds `+20 150 577 9515`. An empty or missing `searchTerm` returns all\nrecords. Results come newest first.\n\nThe response has the same shape as `GET /object/data/{objectSlug}`:\nrecords keyed by field slug in `data.data`, the columns in `data.fields`,\nand `pagination` at the top level. Only records the credential can see are\nreturned.\n\nQuery parameters:\n- `page` — page number, from 1. Default `1`.\n- `limit` — records per page, up to `200`. Default `25`.\n\nScope: `read.<object slug>`, for example `read.contacts`.\n\nErrors:\n- `400` — no active object has that slug.\n- `403` — the credential lacks `read.<object slug>`.\n- `422` — the body or query is invalid, for example `searchTerm` is longer\n  than 200 characters.\n\nRequest:\n\n```json\n{ \"searchTerm\": \"sara\" }\n```\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Search completed successfully\",\n  \"data\": {\n    \"fields\": [\n      {\n        \"name\": \"name\",\n        \"slug\": \"name-mp9nm29v\",\n        \"label\": \"Name\",\n        \"type\": \"text\",\n        \"icon\": null,\n        \"options\": null,\n        \"is_system\": true,\n        \"api_only\": false,\n        \"field_group\": null,\n        \"required\": false,\n        \"default_value\": null,\n        \"sort_order\": 0,\n        \"is_active\": true,\n        \"filterOperators\": [\"includes\", \"multi_includes\", \"does_not_include\", \"equals\", \"does_not_equal\", \"starts_with\", \"ends_with\", \"is_empty\", \"is_not_empty\"]\n      }\n    ],\n    \"data\": [\n      {\n        \"name-mp9nm29v\": \"Sara Ali\",\n        \"assignees\": [],\n        \"name\": \"Sara Ali\",\n        \"relations\": {},\n        \"slug\": \"9f2c4b7a1e03\",\n        \"random_id\": \"a3f9c21b\",\n        \"createdAt\": \"2026-09-14T10:00:00.000Z\",\n        \"updatedAt\": \"2026-09-14T10:00:00.000Z\",\n        \"position\": null,\n        \"creator\": null,\n        \"updater\": null,\n        \"linked_record\": null,\n        \"capabilities\": { \"can_edit\": true, \"can_delete\": true, \"can_assign\": true }\n      }\n    ]\n  },\n  \"pagination\": {\n    \"total\": 1,\n    \"page\": 1,\n    \"limit\": 25,\n    \"totalPages\": 1,\n    \"nextPage\": null,\n    \"prevPage\": null\n  }\n}\n```",
        "tags": [
          "Records"
        ],
        "operationId": "DataReadController_searchData",
        "parameters": [
          {
            "name": "objectSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "paginationQuery",
            "in": "query",
            "required": false,
            "schema": {}
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "data": {},
                    "pagination": {}
                  },
                  "required": [
                    "message",
                    "data",
                    "pagination"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "searchTerm": {
                    "description": "Text to find in the record's text-like fields, slug and `random_id`."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/object/data/select-options/{objectSlug}": {
      "get": {
        "summary": "List the records you can link through every relation of an object.",
        "description": "List the records you can link through every relation of an object.\n\nDeprecated: use\n`GET /object/data/select-options/{objectSlug}/{fieldSlug}` instead.\n\nReturns one group per relation, keyed by the relation field slug. Each\ngroup lists every record on the other side of the relation that the\ncredential may link, sorted by record slug, with no pagination. A relation\nwith no such records has an empty `options` list. This does not scale to\nlarge objects; the per-field route pages and searches.\n\nScope: `read.<object slug>`, for example `read.deals`.\n\nErrors:\n- `400` — no active object has that slug.\n- `403` — the credential lacks `read.<object slug>`.\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Select options retrieved successfully\",\n  \"data\": {\n    \"contact-mpoc4k2z\": {\n      \"field_slug\": \"contact-mpoc4k2z\",\n      \"label\": \"Contact\",\n      \"relation_type\": \"belongsTo\",\n      \"direction\": \"forward\",\n      \"object_slug\": \"contacts\",\n      \"object_name\": \"Contacts\",\n      \"object_icon\": \"users\",\n      \"options\": [\n        {\n          \"type\": \"relation\",\n          \"slug\": \"9f2c4b7a1e03\",\n          \"random_id\": \"a3f9c21b\",\n          \"name\": \"Sara Ali\",\n          \"label\": \"Sara Ali\",\n          \"icon\": \"users\"\n        }\n      ]\n    }\n  }\n}\n```",
        "tags": [
          "Records"
        ],
        "operationId": "DataReadController_getSelectOptions",
        "parameters": [
          {
            "name": "objectSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "data": {}
                  },
                  "required": [
                    "message",
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/object/data/select-options/{objectSlug}/{fieldSlug}": {
      "get": {
        "summary": "List the records you can link through one relation field.",
        "description": "List the records you can link through one relation field.\n\n`fieldSlug` is the slug of a relation column in `fields`. Returns one page\nof records on the other side of that relation that the credential may\nlink, sorted by display name, plus the relation's details.\n\n`search` matches the record name, slug, `random_id`, and its text, long\ntext, email, phone, WhatsApp number and serial number fields. When the\nterm holds 6 or more digits, phone numbers also match on digits alone.\n\nQuery parameters:\n- `search` — text to find. At most 100 characters.\n- `page` — page number, from 1. Default `1`.\n- `limit` — options per page, up to `200`. Default `25`.\n\nScope: `read.<object slug>`, for example `read.deals`.\n\nErrors:\n- `400` — no active object has that slug.\n- `403` — the credential lacks `read.<object slug>`.\n- `404` — `Relation field 'contact-mpoc4k2z' not found on object 'deals'`.\n- `422` — a query parameter is invalid, for example `limit` is above\n  `200`.\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Select options retrieved successfully\",\n  \"data\": {\n    \"field_slug\": \"contact-mpoc4k2z\",\n    \"label\": \"Contact\",\n    \"relation_type\": \"belongsTo\",\n    \"direction\": \"forward\",\n    \"object_slug\": \"contacts\",\n    \"object_name\": \"Contacts\",\n    \"object_icon\": \"users\",\n    \"options\": [\n      {\n        \"type\": \"relation\",\n        \"slug\": \"9f2c4b7a1e03\",\n        \"random_id\": \"a3f9c21b\",\n        \"name\": \"Sara Ali\",\n        \"label\": \"Sara Ali\",\n        \"icon\": \"users\"\n      }\n    ]\n  },\n  \"pagination\": {\n    \"total\": 1,\n    \"page\": 1,\n    \"limit\": 25,\n    \"totalPages\": 1,\n    \"nextPage\": null,\n    \"prevPage\": null\n  }\n}\n```",
        "tags": [
          "Records"
        ],
        "operationId": "DataReadController_getSelectOptionsForField",
        "parameters": [
          {
            "name": "objectSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "fieldSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "query",
            "in": "query",
            "required": false,
            "schema": {}
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "data": {},
                    "pagination": {}
                  },
                  "required": [
                    "message",
                    "data",
                    "pagination"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/object/data/duplicates/{objectSlug}/{fieldSlug}": {
      "get": {
        "summary": "List the records that share a value in one field.",
        "description": "List the records that share a value in one field.\n\nReturns every record whose value in `fieldSlug` also appears on another\nrecord, sorted by that value so records that share it sit together. Use it\nto find the records that stop a field from requiring unique values. Works\non text, number, email, phone and WhatsApp number fields.\n\nThe response has the same shape as `GET /object/data/{objectSlug}`, plus\ntwo keys on each record: `duplicate_value` (the shared value) and\n`duplicate_count` (how many records hold it). `duplicate_groups` at the top\nlevel counts the distinct shared values; `pagination.total` counts records.\n\nThe credential must be able to view all records of the object, not only\nits own or its group's.\n\nQuery parameters:\n- `page` — page number, from 1. Default `1`.\n- `limit` — records per page, up to `200`. Default `25`.\n\nScope: `read.<object slug>`, for example `read.contacts`.\n\nErrors:\n- `400` — no active object has that slug.\n- `400` — `Field with slug 'email-k2v8x1qa' not found or has no column.`\n- `400` — the field type does not support unique values.\n- `403` — the credential lacks `read.<object slug>`, or cannot view all\n  records of the object.\n- `422` — a query parameter is invalid, for example `limit` is above\n  `200`.\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Duplicate records retrieved successfully\",\n  \"data\": {\n    \"fields\": [\n      {\n        \"name\": \"name\",\n        \"slug\": \"name-mp9nm29v\",\n        \"label\": \"Name\",\n        \"type\": \"text\",\n        \"icon\": null,\n        \"options\": null,\n        \"is_system\": true,\n        \"api_only\": false,\n        \"field_group\": null,\n        \"required\": false,\n        \"default_value\": null,\n        \"sort_order\": 0,\n        \"is_active\": true,\n        \"filterOperators\": [\"includes\", \"multi_includes\", \"does_not_include\", \"equals\", \"does_not_equal\", \"starts_with\", \"ends_with\", \"is_empty\", \"is_not_empty\"]\n      },\n      {\n        \"name\": \"email\",\n        \"slug\": \"email-k2v8x1qa\",\n        \"label\": \"Email\",\n        \"type\": \"email\",\n        \"icon\": null,\n        \"options\": null,\n        \"is_system\": false,\n        \"api_only\": false,\n        \"field_group\": null,\n        \"required\": false,\n        \"default_value\": null,\n        \"sort_order\": 1,\n        \"is_active\": true,\n        \"filterOperators\": [\"includes\", \"multi_includes\", \"equals\", \"does_not_equal\", \"starts_with\", \"ends_with\", \"is_empty\", \"is_not_empty\"]\n      }\n    ],\n    \"data\": [\n      {\n        \"name-mp9nm29v\": \"Sara Ali\",\n        \"assignees\": [],\n        \"email-k2v8x1qa\": \"sara@example.com\",\n        \"name\": \"Sara Ali\",\n        \"relations\": {},\n        \"slug\": \"9f2c4b7a1e03\",\n        \"random_id\": \"a3f9c21b\",\n        \"createdAt\": \"2026-09-14T10:00:00.000Z\",\n        \"updatedAt\": \"2026-09-14T10:00:00.000Z\",\n        \"position\": null,\n        \"creator\": null,\n        \"updater\": null,\n        \"linked_record\": null,\n        \"capabilities\": { \"can_edit\": true, \"can_delete\": true, \"can_assign\": true },\n        \"duplicate_value\": \"sara@example.com\",\n        \"duplicate_count\": 2\n      },\n      {\n        \"name-mp9nm29v\": \"Sara A.\",\n        \"assignees\": [],\n        \"email-k2v8x1qa\": \"sara@example.com\",\n        \"name\": \"Sara A.\",\n        \"relations\": {},\n        \"slug\": \"4d7b1e9a2c65\",\n        \"random_id\": \"7c01e4d9\",\n        \"createdAt\": \"2026-09-14T11:30:00.000Z\",\n        \"updatedAt\": \"2026-09-14T11:30:00.000Z\",\n        \"position\": null,\n        \"creator\": null,\n        \"updater\": null,\n        \"linked_record\": null,\n        \"capabilities\": { \"can_edit\": true, \"can_delete\": true, \"can_assign\": true },\n        \"duplicate_value\": \"sara@example.com\",\n        \"duplicate_count\": 2\n      }\n    ]\n  },\n  \"pagination\": {\n    \"total\": 2,\n    \"page\": 1,\n    \"limit\": 25,\n    \"totalPages\": 1,\n    \"nextPage\": null,\n    \"prevPage\": null\n  },\n  \"duplicate_groups\": 1\n}\n```",
        "tags": [
          "Records"
        ],
        "operationId": "DataReadController_getFieldDuplicates",
        "parameters": [
          {
            "name": "objectSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "fieldSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "query",
            "in": "query",
            "required": false,
            "schema": {}
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "data": {},
                    "pagination": {},
                    "duplicate_groups": {
                      "type": "number"
                    }
                  },
                  "required": [
                    "message",
                    "data",
                    "pagination",
                    "duplicate_groups"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/object/data/facets/{objectSlug}": {
      "get": {
        "summary": "List the values of dependent filter fields.",
        "description": "List the values of dependent filter fields.\n\nSome objects define filter dependencies: a child field whose choices\ndepend on the value of a parent field, such as a city that depends on a\ncountry. For each child field, this returns the distinct values stored on\nrecords, narrowed by the parent values you pass. Only records the\ncredential can see count. The object returns `{}` when it defines no\nfilter dependencies.\n\nParent and child fields are named by the field's `name`, not its slug.\n\nQuery parameters:\n- `<parent field name>` — the parent value to narrow by, for example\n  `country=Egypt`. Parameters that name no parent are ignored.\n\nScope: `read.<object slug>`, for example `read.contacts`.\n\nErrors:\n- `400` — no active object has that slug.\n- `403` — the credential lacks `read.<object slug>`.\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Filter facets retrieved successfully\",\n  \"data\": {\n    \"city\": [\"Alexandria\", \"Cairo\", \"Giza\"]\n  }\n}\n```",
        "tags": [
          "Records"
        ],
        "operationId": "DataReadController_getFilterFacets",
        "parameters": [
          {
            "name": "objectSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "filters",
            "in": "query",
            "required": false,
            "schema": {}
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "data": {}
                  },
                  "required": [
                    "message",
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/object/data/{objectSlug}/{dataSlug}/relations/{relationSlug}": {
      "get": {
        "summary": "List the records linked to one record through one relation.",
        "description": "List the records linked to one record through one relation.\n\nA relation cell on a record read holds one page of linked records. When\nits `pagination.nextPage` is not null, call this route for the rest.\n`relationSlug` is the key of that cell in the record's `relations`.\nLinked records come newest first, in the same order as the cell, so the\nfirst page starts with the records the cell already showed. Only linked\nrecords the credential can see are returned.\n\n`relation_type` at the top level is `belongsTo` or `hasMany`. A belongs-to\nrelation returns at most one record. An item carries `columns`, keyed by\nfield slug, when the relation has display columns set.\n\nQuery parameters:\n- `page` — page number, from 1. Default `1`.\n- `limit` — records per page, up to `200`. Default `25`.\n\nScope: `read.<object slug>`, for example `read.contacts`.\n\nErrors:\n- `400` — no active object has that slug.\n- `400` — `Data with slug '9f2c4b7a1e03' not found`: the record does not\n  exist or the credential cannot see it.\n- `403` — the credential lacks `read.<object slug>`.\n- `404` — `Relation 'deals-mpoc4k2z' not found on object 'contacts'`.\n- `422` — a query parameter is invalid, for example `limit` is above\n  `200`.\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Relation page retrieved successfully\",\n  \"data\": [\n    {\n      \"type\": \"relation\",\n      \"name\": \"Office renewal\",\n      \"label\": \"Office renewal\",\n      \"slug\": \"c81f5a0d3e97\",\n      \"random_id\": \"5b2e8d14\",\n      \"object_slug\": \"deals\",\n      \"object_name\": \"Deals\",\n      \"object_icon\": \"briefcase\"\n    }\n  ],\n  \"pagination\": {\n    \"total\": 26,\n    \"page\": 2,\n    \"limit\": 25,\n    \"totalPages\": 2,\n    \"nextPage\": null,\n    \"prevPage\": 1\n  },\n  \"relation_type\": \"hasMany\"\n}\n```",
        "tags": [
          "Records"
        ],
        "operationId": "DataReadController_getRelationPage",
        "parameters": [
          {
            "name": "objectSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "dataSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "relationSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "paginationQuery",
            "in": "query",
            "required": false,
            "schema": {}
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "type": {},
                    "name": {
                      "type": "string"
                    },
                    "label": {
                      "type": "string",
                      "description": "Display text for `slug`. Same value as `name`, under the key every\n other slug-bearing shape on the wire uses for its human text."
                    },
                    "slug": {
                      "type": "string"
                    },
                    "random_id": {
                      "type": "string"
                    },
                    "object_slug": {
                      "type": "string"
                    },
                    "object_name": {
                      "type": "string"
                    },
                    "object_icon": {
                      "type": "string",
                      "description": "The partner OBJECT's icon — a record has none of its own. `\"\"` when the\n object never got one, so the client keeps its per-direction fallback."
                    },
                    "columns": {
                      "description": "The relation's configured display columns for THIS linked record, keyed by\nthe target field's slug, in the admin's configured order\n(`RelationOptions.display_columns`).\n\nEach value is already in the shape a normal record read emits for that\nfield type — option-backed fields arrive as `{id,value,label,color,icon}[]`,\nnever a raw option id — so the client renders a mirrored cell with the same\nrenderer it uses on the Data Table, keyed off the field's own type.\n\n**Absent, not empty**, when the relation has no columns configured or the\nfeature is off. Clients must treat a missing key as \"no columns\", which is\nalso what every client written before this key did."
                    }
                  },
                  "required": [
                    "name",
                    "label",
                    "slug",
                    "random_id",
                    "object_slug",
                    "object_name",
                    "object_icon"
                  ],
                  "description": "Single relation entry shape — emitted under `relations[<relation-name>]`.\nFor one-to-one relations, the key holds a single instance; for\none-to-many and many-to-many, an array of instances.\n\nSlug-first (CLAUDE.md): no numeric `id` on the wire. The frontend pins\non `slug` (record) and `object_slug` (object). `random_id` carries the\nhuman-readable display id (e.g. `INV-001`), not the DB primary key.\n\nEvery slug on this shape travels with the human text for the same thing,\nso a client never has to resolve one to render it: `slug` → `label`\n(the record's display name), `object_slug` → `object_name` / `object_icon`.\nWithout `object_name` a chip could name its record but not the object it\nlives on, which is exactly what a cross-object linked-record list has to\nshow — and `object_icon` is what tells a contact chip from a company chip\nbefore either is read."
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/object/data/{objectSlug}/column-values/{fieldSlug}": {
      "post": {
        "summary": "List the distinct values stored in one field, most common first.",
        "description": "List the distinct values stored in one field, most common first.\n\nReturns each distinct, non-empty value of `fieldSlug` with the number of\nrecords that hold it, sorted by that count and then by value. Use it to\nbuild a filter menu. Only records the credential can see count.\n\n`filters` narrows the records first, in the same condition format as the\n`filters` query parameter of `GET /object/data/{objectSlug}`. `search`\nkeeps values that contain the text, ignoring case. `limit` caps the list;\n`has_more` is `true` when more values exist. For select, status, priority\nand source fields, `value` is the stored option `id`.\n\nTo list the options a select field allows, including unused ones, use\n`GET /object/data/{objectSlug}/field-options/{fieldSlug}`. Multi-select and\ntag fields are not supported here.\n\nBody fields are all optional. `limit` is 1 to 200, default `50`.\n\nScope: `read.<object slug>`, for example `read.contacts`.\n\nErrors:\n- `400` — no active object has that slug.\n- `400` — `Field with slug 'city-mpoc7h1s' not found or has no column.`\n- `400` — the field is a multi-select or tag field.\n- `403` — the credential lacks `read.<object slug>`.\n- `422` — the body is invalid, for example `limit` is above `200` or a\n  condition has an unknown `operator`.\n\nRequest:\n\n```json\n{\n  \"filters\": [\n    { \"fieldSlug\": \"country-mpoc6b3n\", \"operator\": \"equals\", \"value\": \"Egypt\" }\n  ],\n  \"search\": \"a\",\n  \"limit\": 20\n}\n```\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Column values retrieved successfully\",\n  \"data\": {\n    \"values\": [\n      { \"value\": \"Cairo\", \"count\": 42 },\n      { \"value\": \"Alexandria\", \"count\": 17 },\n      { \"value\": \"Giza\", \"count\": 9 }\n    ],\n    \"has_more\": false\n  }\n}\n```",
        "tags": [
          "Records"
        ],
        "operationId": "DataReadController_getColumnValues",
        "parameters": [
          {
            "name": "objectSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "fieldSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "data": {}
                  },
                  "required": [
                    "message",
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "filters": {
                    "description": "The view's active filter, as a flat condition list. Narrows the population\nbefore the distinct values are taken — omit it to ask about the object."
                  },
                  "search": {
                    "description": "Type-ahead. Matched as *contains*, case-insensitively."
                  },
                  "limit": {
                    "description": "Capped rather than merely defaulted: the response is one row per distinct\nvalue, and a free-text column has as many as it has records."
                  }
                },
                "description": "What a per-column filter menu asks for.\n\nA POST for a read, matching `:objectSlug/search` beside it: the view's own\nfilter travels with the question so the menu offers values that are actually\nreachable there, and a filter set does not fit a query string."
              }
            }
          }
        }
      }
    },
    "/api/v1/object/data/{objectSlug}/signals": {
      "post": {
        "summary": "Get badge counts for a set of records.",
        "description": "Get badge counts for a set of records.\n\nSend up to 500 record slugs in `slugs`. Returns one entry per record the\ncredential can see; slugs it cannot see, and slugs that match no record,\nare left out. Each entry holds:\n- `open_tasks` — open tasks linked to the record.\n- `overdue` — `true` when one of those tasks is past its due date.\n- `mentions` — unread comment mentions of the credential owner.\n- `unread_incoming` — unread incoming messages in chats linked to the\n  record, or `null` when the credential owner may not read chats.\n\nReturns an empty list unless `GET /object/features` reports\n`record_signals: true`. Counts can be up to 60 seconds old.\n\nScope: `read.<object slug>`, for example `read.contacts`.\n\nErrors:\n- `400` — no active object has that slug.\n- `403` — the credential lacks `read.<object slug>`.\n- `422` — `slugs` is missing, empty, holds more than 500 entries, or holds\n  a value that is not a string.\n\nRequest:\n\n```json\n{ \"slugs\": [\"9f2c4b7a1e03\", \"4d7b1e9a2c65\"] }\n```\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Record signals retrieved successfully\",\n  \"data\": [\n    {\n      \"record_slug\": \"9f2c4b7a1e03\",\n      \"unread_incoming\": 3,\n      \"open_tasks\": 2,\n      \"overdue\": true,\n      \"mentions\": 0\n    },\n    {\n      \"record_slug\": \"4d7b1e9a2c65\",\n      \"unread_incoming\": 0,\n      \"open_tasks\": 0,\n      \"overdue\": false,\n      \"mentions\": 1\n    }\n  ]\n}\n```",
        "tags": [
          "Records"
        ],
        "operationId": "DataReadController_getRecordSignals",
        "parameters": [
          {
            "name": "objectSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "data": {
                      "type": "array",
                      "items": {}
                    }
                  },
                  "required": [
                    "message",
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "slugs": {
                    "type": "string",
                    "description": "Capped at the bulk ceiling: the response is one entry per slug and the\ncounts behind it are three grouped queries, so an unbounded list would\nturn one request into a table scan per signal source."
                  }
                },
                "required": [
                  "slugs"
                ],
                "description": "The records a client wants badges for (FR-508 / BE-P5-02).\n\nA POST for a read, like `:objectSlug/search` beside it: the slug list is the\nquestion, and a page of them does not fit a query string.\n\nIt exists because `unread_incoming` cannot ride the `data.signals` room\nbroadcast — whether a viewer may see chat volume at all comes from\n`role_chat_rights`, a different matrix from the record rights that put the row\non their screen, so one payload cannot serve every socket in the room. The\nevent names the key and says nothing about its value; the client asks here,\nonce for the whole visible page, instead of re-reading every record whose\nbadge might have moved."
              }
            }
          }
        }
      }
    },
    "/api/v1/object/data/{objectSlug}/field-options/{fieldSlug}": {
      "get": {
        "summary": "List the options a select-type field allows.",
        "description": "List the options a select-type field allows.\n\nWorks on `select`, `multi_select`, `status`, `priority`, `tag` and\n`source_name` fields. Returns the field's options in their configured\norder. Deleted options are left out; records that still hold one read it\nback flagged `is_deleted`. Write an option's `id` when you set the field\non a record.\n\nScope: `read.<object slug>`, for example `read.deals`.\n\nErrors:\n- `400` — no active object has that slug.\n- `400` — the field type has no predefined options.\n- `403` — the credential lacks `read.<object slug>`.\n- `404` — `Field 'stage-mpoc2x9d' not found on object 'deals'`.\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Field options retrieved successfully\",\n  \"data\": {\n    \"field_slug\": \"stage-mpoc2x9d\",\n    \"field_type\": \"status\",\n    \"options\": [\n      {\n        \"id\": \"3b8e2f4a-6c1d-4e9b-a7f2-0d5c8b1e6a34\",\n        \"value\": \"new\",\n        \"label\": \"New\",\n        \"color\": \"#3B82F6\"\n      },\n      {\n        \"id\": \"9d4a7c2e-1f8b-4b3d-8e6a-5c2f0b9d7e11\",\n        \"value\": \"won\",\n        \"label\": \"Won\",\n        \"color\": \"#22C55E\"\n      }\n    ]\n  }\n}\n```",
        "tags": [
          "Records"
        ],
        "operationId": "DataReadController_getFieldOptions",
        "parameters": [
          {
            "name": "objectSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "fieldSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "value": {
                      "type": "string"
                    },
                    "label": {
                      "type": "string"
                    },
                    "color": {}
                  },
                  "required": [
                    "id",
                    "value",
                    "label"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/object/data/{objectSlug}/{dataSlug}": {
      "get": {
        "summary": "Get one record by slug, with its columns and change history.",
        "description": "Get one record by slug, with its columns and change history.\n\nReturns the record in `data.data`, keyed by field slug, with the same keys\nas a record in `GET /object/data/{objectSlug}`, plus `logs`: every change\nto the record, newest first. `data.fields` lists the object's columns.\n\nLinked records live in `relations`, keyed by relation slug. Each cell\nholds one page of linked records, newest first, and a `pagination` block.\n`page` and `limit` set that page for every cell. When a cell's\n`pagination.nextPage` is not null, fetch the rest from\n`GET /object/data/{objectSlug}/{dataSlug}/relations/{relationSlug}`.\n\n`data.duplicate_flag` is `null` unless the credential holds\n`duplicate-flag.view`. Then it holds `is_duplicate`, `linked_records` (the\nother records flagged as duplicates of this one) and `match_keys` (the\nfield slugs that matched).\n\nQuery parameters:\n- `page` — the page of linked records in each relation cell, from 1.\n  Default `1`.\n- `limit` — linked records per relation cell, up to `200`. Default `10`.\n\nScope: `read.<object slug>`, for example `read.contacts`.\n\nErrors:\n- `400` — no active object has that slug.\n- `400` — `Data with slug '9f2c4b7a1e03' not found`: the record does not\n  exist or the credential cannot see it.\n- `403` — the credential lacks `read.<object slug>`.\n- `422` — a query parameter is invalid, for example `limit` is above\n  `200`.\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Data retrieved successfully\",\n  \"data\": {\n    \"fields\": [\n      {\n        \"name\": \"name\",\n        \"slug\": \"name-mp9nm29v\",\n        \"label\": \"Name\",\n        \"type\": \"text\",\n        \"icon\": null,\n        \"options\": null,\n        \"is_system\": true,\n        \"api_only\": false,\n        \"field_group\": null,\n        \"required\": false,\n        \"default_value\": null,\n        \"sort_order\": 0,\n        \"is_active\": true,\n        \"filterOperators\": [\"includes\", \"multi_includes\", \"does_not_include\", \"equals\", \"does_not_equal\", \"starts_with\", \"ends_with\", \"is_empty\", \"is_not_empty\"]\n      }\n    ],\n    \"data\": {\n      \"name-mp9nm29v\": \"Sara Ali\",\n      \"assignees\": [],\n      \"name\": \"Sara Ali\",\n      \"relations\": {},\n      \"slug\": \"9f2c4b7a1e03\",\n      \"random_id\": \"a3f9c21b\",\n      \"createdAt\": \"2026-09-14T10:00:00.000Z\",\n      \"updatedAt\": \"2026-09-14T10:05:00.000Z\",\n      \"position\": null,\n      \"creator\": null,\n      \"updater\": {\n        \"type\": \"user\",\n        \"slug\": \"5e1a9c3b7d20\",\n        \"name\": \"Omar Hassan\",\n        \"email\": \"omar@example.com\",\n        \"profile_image\": null\n      },\n      \"linked_record\": null,\n      \"capabilities\": { \"can_edit\": true, \"can_delete\": true, \"can_assign\": true },\n      \"logs\": [\n        {\n          \"id\": 58213,\n          \"action\": \"updated\",\n          \"field_name\": \"Name\",\n          \"old_value\": \"Sara\",\n          \"new_value\": \"Sara Ali\",\n          \"record_name\": \"Sara Ali\",\n          \"record_type\": \"record\",\n          \"object_slug\": \"contacts\",\n          \"data_slug\": \"9f2c4b7a1e03\",\n          \"performed_by\": {\n            \"id\": 7,\n            \"name\": \"Omar Hassan\",\n            \"profile_image\": null,\n            \"actor_type\": \"admin\",\n            \"on_behalf_of\": null\n          },\n          \"ip_address\": \"203.0.113.24\",\n          \"source_type\": \"manual\",\n          \"action_category\": \"field_change\",\n          \"changes\": [\n            {\n              \"fieldName\": \"name-mp9nm29v\",\n              \"displayName\": \"Name\",\n              \"oldValue\": \"Sara\",\n              \"newValue\": \"Sara Ali\",\n              \"type\": \"updated\"\n            }\n          ],\n          \"metadata\": null,\n          \"timestamp\": \"Sep 14, 2026, 10:05 AM\",\n          \"summary\": \"Name changed from \\\"Sara\\\" to \\\"Sara Ali\\\"\\nin Contacts: Sara Ali\\nby Omar Hassan\"\n        }\n      ]\n    },\n    \"duplicate_flag\": null\n  }\n}\n```",
        "tags": [
          "Records"
        ],
        "operationId": "DataReadController_getSingleData",
        "parameters": [
          {
            "name": "objectSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "dataSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "relationWindowQuery",
            "in": "query",
            "required": false,
            "schema": {}
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "data": {}
                  },
                  "required": [
                    "message",
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "put": {
        "summary": "Update a record.",
        "description": "Update a record.\n\nSend only what changes. `data` is keyed by field slug, never by field\nname or label; a key you omit keeps its value, and an empty value clears\nthe field (a required field cannot be cleared). `relations` is keyed by\nrelation slug and replaces the links of each relation you send.\n`admin_slug` changes the owner and is ignored when the credential may not\nassign records; `null` removes the owner.\n\nThe same values are dropped as on create and listed in `dropped_fields`.\nTo upload files, send `multipart/form-data` as on create; in `data`, a\nfile or photo field then lists the stored files to keep.\n\nThe response is the whole record after the write, computed fields\nincluded, in the same shape as a record read. `_meta.pending_fields`\nnames fields whose value a background recalculation will still replace.\n\nScope: `update.<object slug>`, for example `update.contacts`.\n\nErrors:\n- `400` — no record has that slug, a value is invalid (the message starts\n  `Validation failed:`), a status change is not an allowed transition, a\n  linked record in `relations` cannot be found or seen, a file is\n  rejected, or no active object has that slug.\n- `403` — the credential may not edit this record.\n- `409` — another record already has the same value in a unique field.\n- `422` — the body is invalid, for example `data` is not an object.\n\nRequest:\n\n```json\n{\n  \"data\": { \"stage-k2j9h4g7\": \"b2c4d6e8-1a3b-4c5d-8e7f-9a0b1c2d3e4f\" }\n}\n```\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Data updated successfully\",\n  \"data\": {\n    \"title-mpoc3dn1\": \"Sara Ali\",\n    \"assignees\": [\n      {\n        \"type\": \"user\",\n        \"slug\": \"a3f1b2c8e9d7\",\n        \"name\": \"Omar Hassan\",\n        \"email\": \"omar@example.com\",\n        \"profile_image\": null\n      }\n    ],\n    \"email-p0zx7c1v\": \"sara@example.com\",\n    \"stage-k2j9h4g7\": [\n      {\n        \"id\": \"b2c4d6e8-1a3b-4c5d-8e7f-9a0b1c2d3e4f\",\n        \"value\": \"customer\",\n        \"label\": \"Customer\",\n        \"color\": \"#10B981\",\n        \"icon\": null\n      }\n    ],\n    \"name\": \"Sara Ali\",\n    \"relations\": {\n      \"company-mf3k2p9a\": {\n        \"items\": [\n          {\n            \"type\": \"relation\",\n            \"name\": \"Acme Inc\",\n            \"label\": \"Acme Inc\",\n            \"slug\": \"2e8b5d1f7c4a\",\n            \"random_id\": \"7d3e9a1c\",\n            \"object_slug\": \"companies\",\n            \"object_name\": \"Companies\",\n            \"object_icon\": \"tabler-building\"\n          }\n        ],\n        \"pagination\": {\n          \"total\": 1,\n          \"page\": 1,\n          \"limit\": 10,\n          \"totalPages\": 1,\n          \"nextPage\": null,\n          \"prevPage\": null\n        }\n      }\n    },\n    \"slug\": \"9f2c4b7a1e03\",\n    \"random_id\": \"5c0e7a9b\",\n    \"createdAt\": \"2026-09-14T10:00:00.000Z\",\n    \"updatedAt\": \"2026-09-14T11:30:00.000Z\",\n    \"position\": \"1000\",\n    \"creator\": {\n      \"type\": \"user\",\n      \"slug\": \"a3f1b2c8e9d7\",\n      \"name\": \"Omar Hassan\",\n      \"email\": \"omar@example.com\",\n      \"profile_image\": null\n    },\n    \"updater\": {\n      \"type\": \"user\",\n      \"slug\": \"a3f1b2c8e9d7\",\n      \"name\": \"Omar Hassan\",\n      \"email\": \"omar@example.com\",\n      \"profile_image\": null\n    },\n    \"linked_record\": null,\n    \"capabilities\": {\n      \"can_edit\": true,\n      \"can_delete\": true,\n      \"can_assign\": true\n    }\n  },\n  \"_meta\": { \"enriched\": true }\n}\n```",
        "tags": [
          "Records"
        ],
        "operationId": "DataWriteController_update",
        "parameters": [
          {
            "name": "objectSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "dataSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "dropped_fields": {},
                    "warnings": {},
                    "message": {
                      "type": "string"
                    },
                    "data": {},
                    "_meta": {}
                  },
                  "required": [
                    "message",
                    "_meta"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "data": {
                    "description": "Data object where keys are field slugs (not field names)\nOnly include fields that need to be updated\nExample: { \"first_name\": \"Jane\", \"phone_number\": \"+1234567890\" }"
                  },
                  "relations": {
                    "description": "Relations object where keys are field slugs for relation fields\nOnly include relations that need to be updated"
                  },
                  "admin_slug": {},
                  "linked_data_slug": {
                    "description": "Optional slug of another data record to link this record to.\nSet to null to remove the link. Primarily used for tasks."
                  },
                  "linked_object_slug": {
                    "description": "Slug of the object that owns `linked_data_slug`.\n\nOptional for back-compat: a client that sends only `linked_data_slug` still\nlinks, resolved by scanning object tables for that slug. Sending it here\nskips the scan and, more importantly, removes the ambiguity — slugs are\nunique per table, not per workspace, so two objects can hold the same one."
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete a record.",
        "description": "Delete a record.\n\nRemoves the record from lists and reads. Values that other records show\nfrom it, such as lookups, stop showing.\n\nScope: `delete.<object slug>`, for example `delete.contacts`.\n\nErrors:\n- `400` — no record has that slug, or no active object has that slug.\n- `403` — the credential may not delete this record.\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Data deleted successfully\",\n  \"data\": null\n}\n```",
        "tags": [
          "Records"
        ],
        "operationId": "DataWriteController_remove",
        "parameters": [
          {
            "name": "objectSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "dataSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "message"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/object/data/{objectSlug}/{dataSlug}/logs": {
      "get": {
        "summary": "List every change to one record, newest first.",
        "description": "List every change to one record, newest first.\n\nReturns the record's whole change history in one response, with no\npagination. For long histories, use\n`GET /object/data/{objectSlug}/{dataSlug}/activity`, which pages and\nfilters, when `GET /object/features` reports `record_activity_tab: true`.\n\nEach entry names the `action` (`created`, `updated`, `deleted`, `moved` or\n`move_reversed`), who made the change in `performed_by`, and each changed\nfield in `changes`, keyed by field slug in `fieldName`. `field_name`,\n`old_value` and `new_value` repeat the first change. `timestamp` is\nformatted for display in the credential owner's timezone.\n\nScope: `read.<object slug>`, for example `read.contacts`.\n\nErrors:\n- `400` — no active object has that slug.\n- `400` — `Data with slug '9f2c4b7a1e03' not found`: the record does not\n  exist or the credential cannot see it.\n- `403` — the credential lacks `read.<object slug>`.\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Logs retrieved successfully\",\n  \"data\": [\n    {\n      \"id\": 58213,\n      \"action\": \"updated\",\n      \"field_name\": \"Email\",\n      \"old_value\": \"sara@old.example.com\",\n      \"new_value\": \"sara@example.com\",\n      \"record_name\": \"Sara Ali\",\n      \"record_type\": \"record\",\n      \"object_slug\": \"contacts\",\n      \"data_slug\": \"9f2c4b7a1e03\",\n      \"performed_by\": {\n        \"id\": 7,\n        \"name\": \"Omar Hassan\",\n        \"profile_image\": null,\n        \"actor_type\": \"admin\",\n        \"on_behalf_of\": null\n      },\n      \"ip_address\": \"203.0.113.24\",\n      \"source_type\": \"manual\",\n      \"action_category\": \"field_change\",\n      \"changes\": [\n        {\n          \"fieldName\": \"email-k2v8x1qa\",\n          \"displayName\": \"Email\",\n          \"oldValue\": \"sara@old.example.com\",\n          \"newValue\": \"sara@example.com\",\n          \"type\": \"updated\"\n        }\n      ],\n      \"metadata\": null,\n      \"timestamp\": \"Sep 14, 2026, 10:05 AM\",\n      \"summary\": \"Email changed from \\\"sara@old.example.com\\\" to \\\"sara@example.com\\\"\\nin Contacts: Sara Ali\\nby Omar Hassan\"\n    }\n  ]\n}\n```",
        "tags": [
          "Records"
        ],
        "operationId": "DataReadController_getDataLogs",
        "parameters": [
          {
            "name": "objectSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "dataSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "number"
                    },
                    "action": {},
                    "field_name": {},
                    "old_value": {},
                    "new_value": {},
                    "record_name": {
                      "type": "string"
                    },
                    "record_type": {
                      "type": "string"
                    },
                    "object_slug": {},
                    "data_slug": {},
                    "performed_by": {},
                    "ip_address": {},
                    "source_type": {},
                    "action_category": {},
                    "changes": {},
                    "metadata": {
                      "description": "Structured extras a writer attached for clients, keyed as they stored them.\n\n`old_value` / `new_value` are the first CHANGE's scalar values, not the\nstored objects — so `rawData`, and everything a writer deliberately put\nthere for a reader, never reached the client. Lead distribution stamps\n`_distribution` (rule name, method) and `_decision_trace` (why a lead went\nwhere it did) precisely so its Logs view can render them; both were\nsilently dropped, leaving those columns and the trace button permanently\nempty.\n\nOnly underscore-prefixed keys travel: that prefix is the existing\nconvention for \"this is metadata for a reader\", so the row's own column\nvalues are not re-published here in a second shape. `null` when the writer\nattached none, which is the common case."
                    },
                    "timestamp": {
                      "type": "string"
                    },
                    "summary": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "id",
                    "action",
                    "field_name",
                    "old_value",
                    "new_value",
                    "record_name",
                    "record_type",
                    "object_slug",
                    "data_slug",
                    "performed_by",
                    "ip_address",
                    "source_type",
                    "action_category",
                    "changes",
                    "metadata",
                    "timestamp",
                    "summary"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/object/data/{objectSlug}/{dataSlug}/activity": {
      "get": {
        "summary": "List one page of a record's change history, with filters.",
        "description": "List one page of a record's change history, with filters.\n\nReturns changes newest first, in the same entry shape as\n`GET /object/data/{objectSlug}/{dataSlug}/logs`. Changes that one bulk\noperation made to this record are folded into one entry with\n`type: \"batch\"`, a `summary`, `child_count` and the entries in `children`.\nAn entry lists at most 50 changed fields; when there are more, the last\nitem in `changes` is `{ \"truncated\": true, \"omitted_count\": 12 }`.\n\nA page can hold fewer entries than `limit` while more remain, so page on\n`pagination.nextPage`. `applied_filters` echoes the filters in force, with\ndates as UTC instants. `origin` describes how the record was created.\n\nAvailable only when `GET /object/features` reports\n`record_activity_tab: true`; otherwise `404`.\n\nQuery parameters:\n- `page` — page number, from 1. Default `1`.\n- `limit` — entries per page, up to `100`. Default `30`.\n- `action` — comma-separated: `INSERT`, `UPDATE`, `DELETE`, `MOVE`,\n  `MOVE_REVERSED`.\n- `sourceType` — comma-separated: `manual`, `automation`, `system`,\n  `api`, `ai`, `impersonation`.\n- `actionCategory` — comma-separated: `creation`, `deletion`,\n  `field_change`, `status_change`, `assignment`.\n- `doneBy` — a member slug, or `system` for changes no member made.\n- `fieldSlug` — only changes to this field.\n- `dateFrom` — the first day to include, as `YYYY-MM-DD`, in the\n  credential owner's timezone.\n- `dateTo` — the last day to include, as `YYYY-MM-DD`. The range can span\n  at most 366 days.\n\nAn unknown `doneBy` or `fieldSlug` returns an empty page, not an error.\n\nScope: `read.<object slug>`, for example `read.contacts`.\n\nErrors:\n- `403` — the credential lacks `read.<object slug>`.\n- `403` — `You don't have access to this record's activity`: the record\n  does not exist or the credential cannot see it.\n- `404` — no active object has that slug.\n- `422` — a query parameter is invalid, for example an unknown `action`\n  value, a malformed date, `dateFrom` after `dateTo`, or `limit` above\n  `100`.\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Record activity retrieved successfully\",\n  \"data\": [\n    {\n      \"id\": 58213,\n      \"action\": \"updated\",\n      \"field_name\": \"Email\",\n      \"old_value\": \"sara@old.example.com\",\n      \"new_value\": \"sara@example.com\",\n      \"record_name\": \"Sara Ali\",\n      \"record_type\": \"record\",\n      \"object_slug\": \"contacts\",\n      \"data_slug\": \"9f2c4b7a1e03\",\n      \"performed_by\": {\n        \"id\": 7,\n        \"name\": \"Omar Hassan\",\n        \"profile_image\": null,\n        \"actor_type\": \"admin\",\n        \"on_behalf_of\": null\n      },\n      \"ip_address\": \"203.0.113.24\",\n      \"source_type\": \"manual\",\n      \"action_category\": \"field_change\",\n      \"changes\": [\n        {\n          \"fieldName\": \"email-k2v8x1qa\",\n          \"displayName\": \"Email\",\n          \"oldValue\": \"sara@old.example.com\",\n          \"newValue\": \"sara@example.com\",\n          \"type\": \"updated\"\n        }\n      ],\n      \"metadata\": null,\n      \"timestamp\": \"Sep 14, 2026, 10:05 AM\",\n      \"summary\": \"Email changed from \\\"sara@old.example.com\\\" to \\\"sara@example.com\\\"\\nin Contacts: Sara Ali\\nby Omar Hassan\"\n    }\n  ],\n  \"pagination\": {\n    \"total\": 1,\n    \"page\": 1,\n    \"limit\": 30,\n    \"totalPages\": 1,\n    \"nextPage\": null,\n    \"prevPage\": null\n  },\n  \"applied_filters\": {\n    \"action\": [\"UPDATE\"],\n    \"dateFrom\": \"2026-08-31T21:00:00.000Z\"\n  },\n  \"origin\": {\n    \"label\": \"Created via API\",\n    \"actor\": { \"name\": \"Omar Hassan\", \"slug\": \"5e1a9c3b7d20\" },\n    \"source_type\": \"api\",\n    \"source_name\": null,\n    \"session_name\": null,\n    \"provider_id\": null,\n    \"first_fill\": [\n      { \"field\": \"name-mp9nm29v\", \"label\": \"Name\", \"value\": \"Sara Ali\" }\n    ]\n  }\n}\n```",
        "tags": [
          "Records"
        ],
        "operationId": "DataReadController_getRecordActivity",
        "parameters": [
          {
            "name": "objectSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "dataSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "query",
            "in": "query",
            "required": false,
            "schema": {}
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "data": {
                      "type": "array",
                      "items": {}
                    },
                    "origin": {},
                    "applied_filters": {},
                    "pagination": {}
                  },
                  "required": [
                    "message",
                    "data",
                    "origin",
                    "applied_filters",
                    "pagination"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/object/data/{objectSlug}/{recordSlug}/duplicate": {
      "post": {
        "summary": "Duplicate a record.",
        "description": "Duplicate a record.\n\nNo request body. Creates a new record in the same object with the source\nrecord's values, links, files and assignees, and returns it in the same\nshape as `POST /object/data/{objectSlug}`. The copy's name gets a copy\nsuffix. Computed values are recalculated, not copied. What could not be\ncopied, such as linked records the credential cannot see or files that\nfailed to copy, is listed in `warnings`.\n\nAvailable only when `GET /object/features` reports\n`record_duplicate: true`; otherwise `404`.\n\nScope: `create.<object slug>`, for example `create.contacts`.\n\nErrors:\n- `403` or `404` — the source record does not exist, or the credential\n  cannot view it.\n- `400` — the copy fails the same checks as a new record.\n\nResponse `201` (abridged):\n\n```json\n{\n  \"status\": 201,\n  \"message\": \"Record duplicated successfully\",\n  \"data\": {\n    \"title-mpoc3dn1\": \"Sara Ali (copy)\",\n    \"name\": \"Sara Ali (copy)\",\n    \"slug\": \"c83e1a5f2d97\",\n    \"random_id\": \"0f4a2c7e\",\n    \"relations\": {},\n    \"createdAt\": \"2026-09-14T10:05:00.000Z\",\n    \"updatedAt\": \"2026-09-14T10:05:00.000Z\"\n  },\n  \"warnings\": [\n    {\n      \"code\": \"RELATION_TARGETS_HIDDEN\",\n      \"message\": \"1 linked record(s) you cannot view were left out of the copy\"\n    }\n  ]\n}\n```",
        "tags": [
          "Records"
        ],
        "operationId": "DataWriteController_duplicate",
        "parameters": [
          {
            "name": "objectSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "recordSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "warnings": {},
                    "message": {
                      "type": "string"
                    },
                    "data": {}
                  },
                  "required": [
                    "message"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/object/data/{objectSlug}/inline": {
      "post": {
        "summary": "Create a record from a quick-add row in a table or board.",
        "description": "Create a record from a quick-add row in a table or board.\n\nA lighter create: `data` is keyed by field slug, and only fields that can\nbe edited inline are kept; the others are dropped and listed in\n`dropped_fields`. `group` places the record in a board column: the field\nnamed by `group.field_slug` gets `group.value`, the column's option id,\nwhatever `data` says. The record is recorded as created inline, so fields\nmarked API-only are dropped even with an API key. The response is the new\nrecord, in the same shape as `POST /object/data/{objectSlug}`.\n\nScope: `create.<object slug>`, for example `create.contacts`.\n\nErrors:\n- `400` — `group.field_slug` is not a field of the object\n  (`code: GROUP_FIELD_NOT_FOUND`) or cannot be set inline\n  (`code: GROUP_FIELD_NOT_WRITABLE`), or a value fails validation.\n- `409` — another record already has the same value in a unique field.\n- `422` — the body is invalid, for example `data` is missing.\n\nRequest:\n\n```json\n{\n  \"data\": { \"title-mpoc3dn1\": \"Follow up with Acme\" },\n  \"group\": {\n    \"field_slug\": \"stage-k2j9h4g7\",\n    \"value\": \"6a1f3c9e-2b7d-4e8a-9f0c-1d2e3f4a5b6c\"\n  }\n}\n```\n\nResponse `201` (abridged):\n\n```json\n{\n  \"status\": 201,\n  \"message\": \"Record created successfully\",\n  \"data\": {\n    \"title-mpoc3dn1\": \"Follow up with Acme\",\n    \"stage-k2j9h4g7\": [\n      {\n        \"id\": \"6a1f3c9e-2b7d-4e8a-9f0c-1d2e3f4a5b6c\",\n        \"value\": \"lead\",\n        \"label\": \"Lead\",\n        \"color\": \"#3B82F6\",\n        \"icon\": null\n      }\n    ],\n    \"name\": \"Follow up with Acme\",\n    \"slug\": \"4d9a1c6e0b53\",\n    \"relations\": {},\n    \"createdAt\": \"2026-09-14T10:00:00.000Z\",\n    \"updatedAt\": \"2026-09-14T10:00:00.000Z\"\n  }\n}\n```",
        "tags": [
          "Records"
        ],
        "operationId": "DataWriteController_inlineCreate",
        "parameters": [
          {
            "name": "objectSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "dropped_fields": {},
                    "warnings": {},
                    "message": {
                      "type": "string"
                    },
                    "data": {}
                  },
                  "required": [
                    "message"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "data": {},
                  "group": {}
                },
                "required": [
                  "data"
                ],
                "description": "An inline add-row. Deliberately has no `source_type`: the origin is a\nproperty of this endpoint, not something a caller may declare."
              }
            }
          }
        }
      }
    },
    "/api/v1/object/data/{objectSlug}/bulk-actions": {
      "post": {
        "summary": "Update, assign or delete many records in one call.",
        "description": "Update, assign or delete many records in one call.\n\n`kind` picks the action: `update` writes `data` (keyed by field slug) to\nevery selected record; `assign` sets `assignee_slugs` as the full\nassignee list of every selected record, with the same owner rule as\n`POST /object/data/{objectSlug}/bulk-assign`; `delete` deletes them.\n\nChoose the records in one of two ways:\n- `record_slugs` — the slugs of the records, one by one.\n- `select_all: true` — every record the credential can see that matches\n  `scope`, the same filters a record list takes (`search`, `filters` or\n  `filter_tree`, `admin_slugs`, `unassigned`). Omit `scope` to select\n  every record the credential can see.\n\n`excluded_record_slugs` removes records from either form. Sending both\n`record_slugs` and `select_all` answers `400`\n(`code: BULK_SELECTION_AMBIGUOUS`).\n\nUp to 100 selected records run during the request: the response is\n`200` with `data.mode` `sync` and the finished `report`. A larger\nselection is queued: the response is `202` with `data.mode` `async` and a\n`job_id`; poll `GET /object/data/{objectSlug}/bulk-actions/{jobId}` for the\nreport. Each record is checked and written on its own, so one failure does\nnot stop the rest; failed records are listed in `report.failed` with a\nreason. Nothing is rolled back.\n\nScope: `update.<object slug>`, for example `update.contacts`. Each record\nis also checked against the credential's access to it.\n\nErrors:\n- `400` — the selection is empty (`code: BULK_EMPTY_SELECTION`) or\n  ambiguous (`code: BULK_SELECTION_AMBIGUOUS`).\n- `422` — the body is invalid, for example an unknown `kind`, or neither\n  `record_slugs` nor `select_all` is sent.\n\nRequest:\n\n```json\n{\n  \"kind\": \"update\",\n  \"record_slugs\": [\"9f2c4b7a1e03\", \"c83e1a5f2d97\", \"7b0d3e9c4a18\"],\n  \"data\": { \"stage-k2j9h4g7\": \"6a1f3c9e-2b7d-4e8a-9f0c-1d2e3f4a5b6c\" }\n}\n```\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Bulk action completed\",\n  \"data\": {\n    \"mode\": \"sync\",\n    \"report\": {\n      \"job_id\": \"d41c7b9e2a60\",\n      \"status\": \"partial\",\n      \"kind\": \"update\",\n      \"object_slug\": \"contacts\",\n      \"total\": 3,\n      \"succeeded\": 2,\n      \"failed\": [\n        {\n          \"record_slug\": \"7b0d3e9c4a18\",\n          \"reason\": \"You do not have permission to edit this record\"\n        }\n      ],\n      \"created_slugs\": [],\n      \"chunks_done\": 1,\n      \"chunks_total\": 1,\n      \"started_by\": 17,\n      \"started_at\": \"2026-09-14T10:00:00.000Z\",\n      \"finished_at\": \"2026-09-14T10:00:01.000Z\"\n    }\n  }\n}\n```\n\nResponse `202`:\n\n```json\n{\n  \"status\": 202,\n  \"message\": \"Bulk action queued\",\n  \"data\": { \"mode\": \"async\", \"job_id\": \"d41c7b9e2a60\" }\n}\n```",
        "tags": [
          "Records"
        ],
        "operationId": "DataWriteController_bulkAction",
        "parameters": [
          {
            "name": "objectSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "data": {}
                  },
                  "required": [
                    "message",
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "kind": {},
                  "data": {
                    "description": "`update` only — field values applied to every selected record."
                  },
                  "assignee_slugs": {
                    "description": "`assign` only — the assignee set to put on every selected record."
                  }
                },
                "required": [
                  "kind"
                ],
                "description": "A bulk action over a selection.\n\nThe selection itself — an explicit list, or \"select all\" with exclusions —\ncomes from {@link BulkSelectionDto}, shared with `/bulk-duplicate` so both\ndoors onto the same runner accept the same selection."
              }
            }
          }
        }
      }
    },
    "/api/v1/object/data/{objectSlug}/bulk-duplicate": {
      "post": {
        "summary": "Duplicate many records in one call.",
        "description": "Duplicate many records in one call.\n\nCopies each selected record the way\n`POST /object/data/{objectSlug}/{recordSlug}/duplicate` does. The report\nlists the new records' slugs in `created_slugs`, in selection order.\n\nChoose the records in one of two ways:\n- `record_slugs` — the slugs of the records, one by one.\n- `select_all: true` — every record the credential can see that matches\n  `scope`, the same filters a record list takes (`search`, `filters` or\n  `filter_tree`, `admin_slugs`, `unassigned`). Omit `scope` to select\n  every record the credential can see.\n\n`excluded_record_slugs` removes records from either form. Sending both\n`record_slugs` and `select_all` answers `400`\n(`code: BULK_SELECTION_AMBIGUOUS`).\n\nUp to 100 selected records run during the request: the response is\n`200` with `data.mode` `sync` and the finished `report`. A larger\nselection is queued: the response is `202` with `data.mode` `async` and a\n`job_id`; poll `GET /object/data/{objectSlug}/bulk-actions/{jobId}` for the\nreport. Each record is checked and written on its own, so one failure does\nnot stop the rest; failed records are listed in `report.failed` with a\nreason. Nothing is rolled back.\n\nSend an `Idempotency-Key` header (up to 200 characters) to make retries\nsafe: for 24 hours, a request with the same key returns the first\nrequest's job instead of copying again.\n\nAvailable only when `GET /object/features` reports\n`record_duplicate: true`; otherwise `404`.\n\nScope: `create.<object slug>`, for example `create.contacts`.\n\nErrors:\n- `400` — the selection is empty (`code: BULK_EMPTY_SELECTION`) or\n  ambiguous (`code: BULK_SELECTION_AMBIGUOUS`), or the `Idempotency-Key`\n  is longer than 200 characters (`code: BULK_IDEMPOTENCY_KEY_TOO_LONG`).\n- `422` — the body is invalid, for example `record_slugs` is empty.\n\nRequest:\n\n```json\n{\n  \"select_all\": true,\n  \"scope\": { \"search\": \"acme\" },\n  \"excluded_record_slugs\": [\"7b0d3e9c4a18\"]\n}\n```\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Bulk action completed\",\n  \"data\": {\n    \"mode\": \"sync\",\n    \"report\": {\n      \"job_id\": \"e52a8c1d9f37\",\n      \"status\": \"completed\",\n      \"kind\": \"duplicate\",\n      \"object_slug\": \"contacts\",\n      \"total\": 2,\n      \"succeeded\": 2,\n      \"failed\": [],\n      \"created_slugs\": [\"1a7e4c9b0d52\", \"8f3b6d2a5e19\"],\n      \"chunks_done\": 1,\n      \"chunks_total\": 1,\n      \"started_by\": 17,\n      \"started_at\": \"2026-09-14T10:00:00.000Z\",\n      \"finished_at\": \"2026-09-14T10:00:02.000Z\"\n    }\n  }\n}\n```",
        "tags": [
          "Records"
        ],
        "operationId": "DataWriteController_bulkDuplicate",
        "parameters": [
          {
            "name": "objectSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "data": {}
                  },
                  "required": [
                    "message",
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {},
                "description": "A bulk clone over a selection.\n\nThe selection and nothing else: which of a source's values may be carried\nover is decided by walking the object's fields, never by the caller (SEC-55),\nso a body that accepted values would only be offering a way in.\n\nSelection shape is {@link BulkSelectionDto}, matching `/bulk-actions` — both\nare doors onto the same runner, and a client should not have to remember\nwhich name a given bulk route wants."
              }
            }
          }
        }
      }
    },
    "/api/v1/object/data/{objectSlug}/bulk-actions/{jobId}/cancel": {
      "post": {
        "summary": "Cancel a queued or running bulk job.",
        "description": "Cancel a queued or running bulk job.\n\nNo request body. Stops the records not yet processed; records already\nwritten stay written. The job's report is returned with status\n`cancelled`, and `succeeded` counts the records that were written. Only\nthe user who started the job can cancel it.\n\nScope: `update.<object slug>`, for example `update.contacts`.\n\nErrors:\n- `403` — another user started this job (`code: BULK_JOB_NOT_YOURS`).\n- `404` — no job has that id, or its report has expired\n  (`code: BULK_JOB_NOT_FOUND`).\n- `409` — the job already finished (`code: BULK_JOB_ALREADY_FINISHED`).\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Bulk action cancelled\",\n  \"data\": {\n    \"job_id\": \"d41c7b9e2a60\",\n    \"status\": \"cancelled\",\n    \"kind\": \"update\",\n    \"object_slug\": \"contacts\",\n    \"total\": 1200,\n    \"succeeded\": 500,\n    \"failed\": [],\n    \"created_slugs\": [],\n    \"chunks_done\": 1,\n    \"chunks_total\": 3,\n    \"started_by\": 17,\n    \"started_at\": \"2026-09-14T10:00:00.000Z\",\n    \"finished_at\": null\n  }\n}\n```",
        "tags": [
          "Records"
        ],
        "operationId": "DataWriteController_cancelBulkAction",
        "parameters": [
          {
            "name": "jobId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "data": {}
                  },
                  "required": [
                    "message",
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/object/data/{objectSlug}/bulk-actions/{jobId}": {
      "get": {
        "summary": "Get the report of a bulk job.",
        "description": "Get the report of a bulk job.\n\nPoll this after a bulk call answered `202`. `status` is `queued`,\n`running`, `completed`, `partial` (some records failed), `failed` (all\nfailed) or `cancelled`. `chunks_done` out of `chunks_total` shows\nprogress, and `finished_at` is set once the job ends. `failed` lists each\nrecord that was not written, with the reason. Reports are kept for 24\nhours.\n\nScope: `update.<object slug>`, for example `update.contacts`.\n\nErrors:\n- `404` — no job has that id, or its report has expired\n  (`code: BULK_JOB_NOT_FOUND`).\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Bulk job report retrieved successfully\",\n  \"data\": {\n    \"job_id\": \"d41c7b9e2a60\",\n    \"status\": \"running\",\n    \"kind\": \"delete\",\n    \"object_slug\": \"contacts\",\n    \"total\": 1200,\n    \"succeeded\": 500,\n    \"failed\": [],\n    \"created_slugs\": [],\n    \"chunks_done\": 1,\n    \"chunks_total\": 3,\n    \"started_by\": 17,\n    \"started_at\": \"2026-09-14T10:00:00.000Z\",\n    \"finished_at\": null\n  }\n}\n```",
        "tags": [
          "Records"
        ],
        "operationId": "DataWriteController_bulkActionReport",
        "parameters": [
          {
            "name": "jobId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "data": {}
                  },
                  "required": [
                    "message",
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/object/data/{objectSlug}/bulk-delete": {
      "post": {
        "summary": "Delete up to 500 records by slug.",
        "description": "Delete up to 500 records by slug.\n\nDeletes every record in `slugs`. Slugs that match no record are ignored.\nEvery record is checked first: when the credential may not delete one of\nthem, the call answers `403` and deletes none. For larger selections or a\nper-record report, use `POST /object/data/{objectSlug}/bulk-actions` with\n`kind` `delete`.\n\nScope: `delete.<object slug>`, for example `delete.contacts`.\n\nErrors:\n- `400` — no active object has that slug.\n- `403` — the credential may not delete one of the records.\n- `422` — `slugs` is empty or holds more than 500 slugs.\n\nRequest:\n\n```json\n{\n  \"slugs\": [\"9f2c4b7a1e03\", \"c83e1a5f2d97\"]\n}\n```\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Bulk delete completed\",\n  \"data\": null\n}\n```",
        "tags": [
          "Records"
        ],
        "operationId": "DataWriteController_bulkDelete",
        "parameters": [
          {
            "name": "objectSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "message"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "slugs": {
                    "type": "string"
                  }
                },
                "required": [
                  "slugs"
                ]
              }
            }
          }
        }
      }
    },
    "/api/v1/object/data/{objectSlug}/bulk-assign": {
      "post": {
        "summary": "Set the assignees of up to 500 records.",
        "description": "Set the assignees of up to 500 records.\n\n`assignees` is the full assignee list for every record in `slugs`: it\nreplaces the current list, and `[]` removes everyone. A record keeps its\nowner when the owner is still in the list; otherwise the first slug\nbecomes the owner. `admin_slug` is the older single-owner form, equal to\n`assignees: [admin_slug]`. Send one of the two; sending neither removes\nall assignees. Slugs that match no record are skipped; `updatedCount`\nsays how many records were found and assigned. A record takes at most 20\nassignees.\n\nScope: `assign.<object slug>`, for example `assign.contacts`.\n\nErrors:\n- `400` — both `admin_slug` and `assignees` are sent, or no active object\n  has that slug.\n- `403` — the credential may not edit one of the records.\n- `422` — more than 20 assignees (`code: TOO_MANY_ASSIGNEES`), an\n  assignee is not a user of this workspace (`code: INVALID_ASSIGNEE`), or\n  the body is invalid, for example more than 500 slugs.\n\nRequest:\n\n```json\n{\n  \"slugs\": [\"9f2c4b7a1e03\", \"c83e1a5f2d97\"],\n  \"assignees\": [\"a3f1b2c8e9d7\", \"e6d2b9f41c08\"]\n}\n```\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Bulk assign completed\",\n  \"data\": { \"updatedCount\": 2 }\n}\n```",
        "tags": [
          "Records"
        ],
        "operationId": "DataWriteController_bulkAssign",
        "parameters": [
          {
            "name": "objectSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "data": {}
                  },
                  "required": [
                    "message",
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "slugs": {
                    "type": "string"
                  },
                  "admin_slug": {
                    "description": "Single-owner form kept for existing clients: equivalent to\n`assignees: [admin_slug]`. Mutually exclusive with {@link assignees}."
                  },
                  "assignees": {
                    "description": "The desired FULL assignee set for every selected record (set-replace, same\nsemantics as `PATCH …/:recordSlug/assignees`). First slug becomes the\nprimary owner; `[]` unassigns everyone."
                  }
                },
                "required": [
                  "slugs"
                ]
              }
            }
          }
        }
      }
    },
    "/api/v1/object/data/{objectSlug}/{recordSlug}/assignees": {
      "patch": {
        "summary": "Replace the assignees of a record.",
        "description": "Replace the assignees of a record.\n\n`assignees` is the full list of user slugs: it replaces the current list,\nand `[]` removes everyone. The owner stays the owner when still in the\nlist; otherwise the first slug becomes the owner. Users newly added are\nnotified. The response lists the assignees owner first,\nwith `capabilities` saying what the credential may now do with the\nrecord.\n\nScope: `assign.<object slug>`, for example `assign.contacts`.\n\nErrors:\n- `404` — no record has that slug.\n- `400` — no active object has that slug.\n- `422` — more than 20 assignees (`code: TOO_MANY_ASSIGNEES`), or an\n  assignee is not a user of this workspace (`code: INVALID_ASSIGNEE`).\n\nRequest:\n\n```json\n{\n  \"assignees\": [\"a3f1b2c8e9d7\", \"e6d2b9f41c08\"]\n}\n```\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Assignees updated\",\n  \"data\": {\n    \"assignees\": [\n      {\n        \"type\": \"user\",\n        \"slug\": \"a3f1b2c8e9d7\",\n        \"name\": \"Omar Hassan\",\n        \"email\": \"omar@example.com\",\n        \"profile_image\": null\n      },\n      {\n        \"type\": \"user\",\n        \"slug\": \"e6d2b9f41c08\",\n        \"name\": \"Lina Farouk\",\n        \"email\": \"lina@example.com\",\n        \"profile_image\": null\n      }\n    ],\n    \"capabilities\": {\n      \"can_edit\": true,\n      \"can_delete\": true,\n      \"can_assign\": true\n    }\n  }\n}\n```",
        "tags": [
          "Records"
        ],
        "operationId": "DataWriteController_setAssignees",
        "parameters": [
          {
            "name": "objectSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "recordSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "data": {}
                  },
                  "required": [
                    "message",
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "assignees": {
                    "type": "string"
                  }
                },
                "required": [
                  "assignees"
                ],
                "description": "Body for `PATCH /object/data/:objectSlug/:recordSlug/assignees` — the desired\nfull set of assignee admin slugs (a set-replace). An empty array unassigns\neveryone; the cap + same-workspace validation are enforced server-side."
              }
            }
          }
        }
      }
    },
    "/api/v1/object/data/{objectSlug}/{recordSlug}/position": {
      "patch": {
        "summary": "Move a record between two others in manual board order.",
        "description": "Move a record between two others in manual board order.\n\nName the new neighbours: `before_slug` is the record just above, and\n`after_slug` the record just below. Omit `before_slug` to move to the\ntop, or `after_slug` to move to the bottom; send at least one. Returns the\nrecord's new `position`, a decimal number in a string; compare positions\nas numbers. This only changes the order: to move a record to another\nboard column, update its column field first with\n`PUT /object/data/{objectSlug}/{recordSlug}`.\n\nScope: `update.<object slug>`, for example `update.contacts`.\n\nErrors:\n- `400` — neither neighbour is sent, a neighbour is the record itself, or\n  no active object has that slug.\n- `403` — the credential may not edit this record.\n- `404` — the record or a neighbour does not exist.\n\nRequest:\n\n```json\n{\n  \"before_slug\": \"c83e1a5f2d97\",\n  \"after_slug\": \"7b0d3e9c4a18\"\n}\n```\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Position updated\",\n  \"data\": { \"position\": \"1500\" }\n}\n```",
        "tags": [
          "Records"
        ],
        "operationId": "DataWriteController_setPosition",
        "parameters": [
          {
            "name": "objectSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "recordSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "data": {}
                  },
                  "required": [
                    "message",
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "before_slug": {
                    "description": "Slug of the card the moved one now sits below."
                  },
                  "after_slug": {
                    "description": "Slug of the card the moved one now sits above."
                  }
                },
                "description": "Where a dragged card landed, named by the neighbours it sits between.\n\nNeighbour slugs rather than a numeric index: an index is stale the moment\nanother viewer's drag shifts the lane, and it would force the client to know\nthe lane's whole contents to compute one. Two slugs describe the same slot\nwithout either problem. Omit `before_slug` for the top of a lane, `after_slug`\nfor the bottom; omitting both names no slot and is rejected."
              }
            }
          }
        }
      }
    },
    "/api/v1/object/data/{objectSlug}/bulk-update": {
      "post": {
        "summary": "Apply the same change to up to 500 records.",
        "description": "Apply the same change to up to 500 records.\n\nWrites `data` (keyed by field slug), `relations` (keyed by relation slug),\n`admin_slug` and the linked record to every record in `slugs`, in one\ntransaction: when one record fails its check or validation, nothing is\nwritten. Slugs that match no record are skipped. `data.updatedCount` says\nhow many records changed, and `data.relatedSlugs` lists the other objects\nwhose records the relation changes touched. The same values are dropped\nas on create and listed in `dropped_fields`. For larger selections or a\nper-record report, use `POST /object/data/{objectSlug}/bulk-actions`.\n\nScope: `update.<object slug>`, for example `update.contacts`.\n\nErrors:\n- `400` — a value is invalid, a required relation is missing, the linked\n  record does not exist, or no active object has that slug.\n- `403` — the credential may not edit one of the records.\n- `422` — `slugs` is empty or holds more than 500 slugs.\n\nRequest:\n\n```json\n{\n  \"slugs\": [\"9f2c4b7a1e03\", \"c83e1a5f2d97\"],\n  \"data\": { \"stage-k2j9h4g7\": \"b2c4d6e8-1a3b-4c5d-8e7f-9a0b1c2d3e4f\" }\n}\n```\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Bulk update completed\",\n  \"data\": {\n    \"updatedCount\": 2,\n    \"relatedSlugs\": []\n  }\n}\n```",
        "tags": [
          "Records"
        ],
        "operationId": "DataWriteController_bulkUpdate",
        "parameters": [
          {
            "name": "objectSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "dropped_fields": {},
                    "message": {
                      "type": "string"
                    },
                    "data": {}
                  },
                  "required": [
                    "message",
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "slugs": {
                    "type": "string"
                  },
                  "data": {},
                  "relations": {},
                  "admin_slug": {},
                  "linked_data_slug": {},
                  "linked_object_slug": {
                    "description": "Slug of the object that owns `linked_data_slug`.\n\nOptional for back-compat: a client that sends only `linked_data_slug` still\nlinks, resolved by scanning object tables for that slug. Sending it here\nskips the scan and, more importantly, removes the ambiguity — slugs are\nunique per table, not per workspace, so two objects can hold the same one."
                  }
                },
                "required": [
                  "slugs"
                ]
              }
            }
          }
        }
      }
    },
    "/api/v1/object/data/{objectSlug}/bulk/all": {
      "delete": {
        "summary": "Delete all records of an object.",
        "description": "Delete all records of an object.\n\nDeletes every record of the object, or only those created after\n`afterDate`. The credential must have access to all records of the\nobject. `deletedCount` says how many records were deleted. When the run\nfails part way, the response is still `200`, with `failedCount` `1` and\na message in `errors`; records deleted before the failure stay deleted.\n\nQuery parameters:\n- `afterDate` — delete only records created after this date\n  (`YYYY-MM-DD`) or ISO 8601 timestamp.\n\nScope: `delete.<object slug>`, for example `delete.contacts`.\n\nErrors:\n- `400` — no active object has that slug.\n- `403` — the credential does not have access to all records of the\n  object.\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Bulk delete completed\",\n  \"data\": {\n    \"deletedCount\": 1284,\n    \"failedCount\": 0,\n    \"errors\": []\n  }\n}\n```",
        "tags": [
          "Records"
        ],
        "operationId": "DataWriteController_bulkDeleteAll",
        "parameters": [
          {
            "name": "objectSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "afterDate",
            "in": "query",
            "required": false,
            "schema": {}
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "data": {}
                  },
                  "required": [
                    "message",
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/files/signed-url": {
      "get": {
        "summary": "Get a short-lived URL for one stored file.",
        "description": "Get a short-lived URL for one stored file.\n\nPass the file's storage key in `key`: the `filePath` value of a file or\nphoto field on a record. The URL serves the file under the name and type\nit was uploaded with, so it can be used directly in a browser or an image\ntag. `expiresIn` is the number of seconds the URL stays valid, or `null`\nwhen it does not expire.\n\nThe key must belong to a record the credential can see. A key that does\nnot exist and a key you may not read both answer `404`.\n\nQuery parameters:\n- `key` (required) — the storage key of the file.\n\nErrors:\n- `400` — `key` is missing, or it is not a valid storage key.\n- `404` — `File not found`.\n\nResponse `200`:\n\n```json\n{\n  \"status\": 200,\n  \"message\": \"Signed URL generated\",\n  \"data\": {\n    \"url\": \"https://storage.example.com/contacts/1042/contract-k3v9x2mq/1726300000000-3f1c9a2e-7b4d-4e0a-9c61-2d8f5b0e41a7.pdf?X-Amz-Expires=3600&X-Amz-Signature=5d0e9f\",\n    \"expiresIn\": 3600\n  }\n}\n```",
        "tags": [
          "Records"
        ],
        "operationId": "FileAccessController_getSignedUrl",
        "parameters": [
          {
            "name": "key",
            "in": "query",
            "required": false,
            "schema": {}
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "data": {}
                  },
                  "required": [
                    "message",
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/files/download": {
      "get": {
        "summary": "Download one stored file.",
        "description": "Download one stored file.\n\nStreams the file bytes instead of returning a URL. The response is the\nfile itself, not a JSON envelope. `Content-Type` is the type recorded at\nupload, and `Content-Disposition` is `attachment` with the original file\nname, so a file uploaded as `contract.docx` downloads under that name.\n\nPass the file's storage key in `key`: the `filePath` value of a file or\nphoto field on a record. The key must belong to a record the credential\ncan see. A key that does not exist and a key you may not read both answer\n`404`.\n\nQuery parameters:\n- `key` (required) — the storage key of the file.\n\nErrors (JSON):\n- `400` — `key` is missing, or it is not a valid storage key.\n- `404` — `File not found`.\n\nResponse `200` headers:\n\n```http\nContent-Type: application/pdf\nContent-Disposition: attachment; filename=\"contract.pdf\"; filename*=UTF-8''contract.pdf\n```",
        "tags": [
          "Records"
        ],
        "operationId": "FileAccessController_downloadFile",
        "parameters": [
          {
            "name": "key",
            "in": "query",
            "required": false,
            "schema": {}
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "options": {},
                    "stream": {},
                    "logger": {},
                    "handleError": {},
                    "logError": {},
                    "getStream": {},
                    "getHeaders": {},
                    "errorHandler": {},
                    "setErrorHandler": {},
                    "errorLogger": {},
                    "setErrorLogger": {}
                  },
                  "required": [
                    "options",
                    "stream",
                    "logger",
                    "handleError",
                    "logError",
                    "getStream",
                    "getHeaders",
                    "errorHandler",
                    "setErrorHandler",
                    "errorLogger",
                    "setErrorLogger"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/files/signed-urls": {
      "post": {
        "summary": "Get short-lived URLs for up to 100 stored files in one call.",
        "description": "Get short-lived URLs for up to 100 stored files in one call.\n\nSend the storage keys in `keys`: the `filePath` values of file or photo\nfields on records. Each key gets its own result, in request order, so one\nbad key does not fail the batch. A result has a `url`, or `url: null` and\nan `error`. `not_found` covers both a missing file and a file on a record\nthe credential cannot see. `expiresIn` is the number of seconds the URLs\nstay valid, or `null` when they do not expire.\n\nErrors:\n- `422` — `keys` is not an array of non-empty strings, or holds more than\n  100 keys.\n\nRequest:\n\n```json\n{\n  \"keys\": [\n    \"contacts/1042/contract-k3v9x2mq/1726300000000-3f1c9a2e-7b4d-4e0a-9c61-2d8f5b0e41a7.pdf\",\n    \"contacts/1042/contract-k3v9x2mq/1726300000000-0b9e6d2a-51c4-4f7e-8a3d-6e2f1c7b9d40.pdf\"\n  ]\n}\n```\n\nResponse `201`:\n\n```json\n{\n  \"status\": 201,\n  \"message\": \"Signed URLs generated\",\n  \"data\": {\n    \"results\": [\n      {\n        \"key\": \"contacts/1042/contract-k3v9x2mq/1726300000000-3f1c9a2e-7b4d-4e0a-9c61-2d8f5b0e41a7.pdf\",\n        \"url\": \"https://storage.example.com/contacts/1042/contract-k3v9x2mq/1726300000000-3f1c9a2e-7b4d-4e0a-9c61-2d8f5b0e41a7.pdf?X-Amz-Expires=3600&X-Amz-Signature=5d0e9f\"\n      },\n      {\n        \"key\": \"contacts/1042/contract-k3v9x2mq/1726300000000-0b9e6d2a-51c4-4f7e-8a3d-6e2f1c7b9d40.pdf\",\n        \"url\": null,\n        \"error\": \"not_found\"\n      }\n    ],\n    \"expiresIn\": 3600\n  }\n}\n```",
        "tags": [
          "Records"
        ],
        "operationId": "FileAccessController_getSignedUrls",
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "data": {}
                  },
                  "required": [
                    "message",
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "keys": {
                    "type": "string"
                  }
                },
                "required": [
                  "keys"
                ],
                "description": "Batch signed-URL request body (`POST /files/signed-urls`, ≤100 keys)."
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "ErrorResponse": {
        "type": "object",
        "description": "The body of every error response.",
        "required": [
          "status",
          "message"
        ],
        "properties": {
          "status": {
            "type": "integer",
            "description": "The HTTP status code of the response."
          },
          "message": {
            "type": "string",
            "description": "What went wrong. Several validation messages are joined with \", \"."
          },
          "code": {
            "type": "string",
            "description": "A stable, machine-readable reason. Only some errors set it."
          },
          "data": {
            "type": "object",
            "description": "Extra detail about the error. Only some errors set it."
          }
        }
      }
    },
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "An API key (`crtk_live_…`) or an OAuth access token (`crtk_oauth_…`), sent as `Authorization: Bearer <token>`."
      },
      "apiKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Api-Key",
        "description": "An API key (`crtk_live_…`), sent in the `X-Api-Key` header."
      }
    }
  },
  "security": [
    {
      "bearerAuth": []
    },
    {
      "apiKeyHeader": []
    }
  ]
}
