Plain
BUSINESS · CRM & SUPPORT
Support threads, customers, companies, and labels in the workspace they connected.
Acts as the person, not as itself
Each user connects their own account. Every call carries both identities — the agent and the person it is acting for — so the agent can never reach past what that individual can already do.
Credentials never touch the agent
Tokens live in the vault and attach server-side at call time. The agent holds a session, not a secret, and revoking access does not mean rotating a key.
Every call on the record
Who asked, which agent acted, which action ran, and the verdict that let it through — one audit trail across every integration, not one per vendor.
What an agent can do
Each action is granted on its own. An agent allowed to read is not thereby allowed to write, and the scope beside each row is what the acting user must have connected for it to run at all.
plain_mutation_accept_suggested_label_typesWRITEAccepts one or more pending suggested label types, creating a real label type for each and marking the suggestions as ACCEPTED. All ids must currently be PENDING.
plain_mutation_add_additional_assigneesWRITEAdd one or more users or machine users as additional (secondary) assignees on a thread. Additional assignees are looped in but are not the primary person responsible.
plain_mutation_add_customer_to_customer_groupsWRITEAdd a customer to one or more customer groups (up to 25 at once), identified by group ID, key, or external ID. Memberships that already exist are silently skipped, making this operation safe to call repeatedly.
plain_mutation_add_customer_to_tenantsWRITEAdds a customer to one or more tenants. The customer can be identified by their Plain ID, external ID, or email address. If the customer is already a member of a given tenant the operation is a no-op for that tenant.
plain_mutation_add_labelsWRITEAdd one or more labels to a thread. Labels that are already present on the thread are silently skipped. Archived label types are rejected with error code `cannot_add_label_using_archived_label_type`.
plain_mutation_add_labels_to_userWRITEAdd one or more team labels to a Plain user (agent). Only label types with type `TEAM` may be applied to users. Label types already present on the user are silently skipped. Requires the `label:create` permission.
plain_mutation_add_members_to_tierWRITEAdds one or more tenants or companies to a tier (up to 25 per call). Because each tenant or company can belong to only one tier at a time, adding a member that already belongs to another tier will move it to this tier.
plain_mutation_archive_label_typeWRITEArchives a label type so it can no longer be applied to threads, while preserving it on threads that already have it. To apply an archived label type again, unarchive it first. Requires the `labelType:edit` permission.
plain_mutation_assign_threadWRITEAssign a thread to a specific user or machine user, replacing any existing primary assignee. Requires the `thread:assign` and `thread:read` permissions.
plain_mutation_bulk_upsert_thread_fieldsWRITEUpserts up to 25 thread field values in a single call — useful when setting multiple fields on one or more threads at once.
plain_mutation_change_thread_customerWRITEReassign a thread to a different customer. The original customer retains all their other threads. Requires the `thread:edit` permission.
plain_mutation_change_thread_priorityWRITESet the priority of a thread. Priority is an integer from 0 (urgent) to 3 (low). Requires the `thread:edit` permission.
plain_mutation_create_attachment_download_urlWRITEGenerate a short-lived download URL for an existing attachment. The returned URL expires after 3 minutes. If your workspace has virus scanning enabled, the response also includes an `attachmentVirusScanResult`…
plain_mutation_create_attachment_upload_urlWRITEGenerate a presigned upload URL for an attachment. Use the returned `uploadFormUrl` and `uploadFormData` fields to POST the file as multipart/form-data directly to storage.
plain_mutation_create_customer_eventWRITECreates a customer event that appears in the timeline of every thread belonging to the customer. Use this to surface important product activity (e.g. a failed payment, a deleted API key) so your team has full context…
plain_mutation_create_customer_groupWRITECreate a new customer group with a unique key, display name, and color. Use `upsertCustomerGroup` instead if you need idempotent create-or-update behaviour. Requires `customerGroup:create` permission.
plain_mutation_create_label_typeWRITECreates a new label type in the workspace. Label types define the labels available to apply to threads and users. Requires the `labelType:create` permission.
plain_mutation_create_noteWRITECreates an internal note visible only to your team. Notes appear in the thread timeline alongside customer messages and are useful for sharing context, reminders, or annotations.
plain_mutation_create_snippetWRITECreates a new snippet in the workspace. The `name` is used to search for the snippet when composing a reply. Provide `markdown` in addition to `text` to supply a rich-text version used in channels that support markdown.
plain_mutation_create_tenantWRITECreates a new tenant. Fails with a validation error if a tenant already exists with the provided `externalId`. Use `upsertTenant` instead if you want to create or update a tenant idempotently.
plain_mutation_create_threadWRITECreate a new thread for a customer. Commonly used when a customer submits a contact form or when you want to start a proactive support interaction from your own product. The thread is created in `TODO` status.
plain_mutation_create_thread_eventWRITECreates a thread event that appears only in the timeline of the specified thread. Use this when an activity is specific to a single conversation rather than the customer as a whole.
plain_mutation_create_thread_field_schemaWRITECreates a new thread field schema, defining a custom field that can be attached to threads. The `key` must be unique within the workspace and cannot be changed after creation.
plain_mutation_create_thread_from_slack_messageWRITECreates a thread from a top-level message in a connected customer Slack channel, along with the replies Plain has already received for it.
plain_mutation_create_tierWRITECreates a new tier. You can optionally add tenant and company members at creation time. Requires the `tier:create` permission.
plain_mutation_delete_companyWRITEDeletes a company identified by `companyId` or `companyDomainName`. Deleting a company unlinks it from all of its customers — the customers themselves are not deleted. Requires the `company:delete` permission.
plain_mutation_delete_customerWRITEPermanently deletes a customer and all associated data (threads, timeline entries, etc.). Deletion is asynchronous and cannot be reversed. Requires the `customer:delete` permission.
plain_mutation_delete_customer_groupWRITEPermanently delete a customer group by ID. This will fail if the group still has members — remove all customers from the group first. Requires `customerGroup:delete` permission.
plain_mutation_delete_noteWRITESoft-deletes a note. The note is marked as deleted but its record is retained. Requires the `note:delete` permission.
plain_mutation_delete_snippetWRITESoft-deletes a snippet. Deleted snippets are hidden from the snippet picker but remain fetchable by ID with `isDeleted: true`, preserving the history of replies that referenced them.
plain_mutation_delete_tenantWRITEPermanently deletes a tenant, unlinking it from all customers and removing its fields. Threads that were associated with the tenant retain a tombstone reference but are no longer routed through it.
plain_mutation_delete_tenant_fieldWRITEClears a tenant's value for a specific field without removing the field schema itself. Use this to unset a field value while keeping the schema available for other tenants. Requires `tenant:edit` permission.
plain_mutation_delete_threadWRITEPermanently delete a thread and all its associated data from Plain. This action is irreversible — use with caution. Requires the `thread:delete` permission.
plain_mutation_delete_thread_fieldWRITERemoves a stored thread field value from a thread, identified by thread ID and field key. Has no effect if the field has no value. Requires the `threadField:delete` permission.
plain_mutation_delete_thread_field_schemaWRITEPermanently deletes a thread field schema and removes all field values stored against it on every thread. This action cannot be undone. Requires the `threadFieldSchema:delete` permission.
plain_mutation_delete_tierWRITEDeletes a tier permanently. All tenant and company memberships in that tier are also removed. Requires the `tier:delete` permission.
plain_mutation_dismiss_suggested_label_typesWRITEDismisses one or more pending suggested label types without creating label types. All ids must currently be PENDING. Requires the `labelType:create` permission.
plain_mutation_escalate_threadWRITEAdvance a thread to the next step in its escalation path. The thread must already have an escalation path attached (via `updateThreadEscalationPath`) — if not, this call returns an error.
plain_mutation_lock_threadWRITELock a thread to prevent further replies or changes by non-admin users. Use this when a resolution is final and you want to freeze the conversation. Requires the `thread:edit` permission.
plain_mutation_mark_customer_as_spamWRITEFlags a customer as spam, hiding their threads from the inbox and excluding them from metrics. The operation is idempotent — marking an already-spam customer leaves their `markedAsSpamAt` timestamp unchanged.
plain_mutation_mark_thread_as_doneWRITEMark a thread as Done, indicating there is nothing left for the support team to do right now. The thread will automatically revert to Todo when new activity arrives.
plain_mutation_mark_thread_as_todoWRITEExplicitly move a thread back to Todo status. Use this to unsnooze a thread early or to reopen a thread that was incorrectly marked as Done. Requires the `thread:edit` and `thread:read` permissions.
plain_mutation_move_label_typeWRITEChanges the position of a label type in the ordered list, or moves it to a different parent. Supply `afterLabelTypeId` or `beforeLabelTypeId` to place the label type relative to a sibling, and optionally…
plain_mutation_remove_additional_assigneesWRITERemove one or more additional assignees from a thread. Does not affect the primary assignee. Requires the `thread:unassign` and `thread:read` permissions.
plain_mutation_remove_customer_from_customer_groupsWRITERemove a customer from one or more customer groups (up to 25 at once), identified by group ID, key, or external ID. Returns an error if the customer is not currently a member of any of the specified groups.
plain_mutation_remove_customer_from_tenantsWRITERemoves a customer from one or more tenants. The customer can be identified by their Plain ID, external ID, or email address.
plain_mutation_remove_labelsWRITERemove one or more labels from a thread by label ID. All provided label IDs must belong to the same thread; mixing labels from different threads returns an error. Requires the `label:delete` permission.
plain_mutation_remove_labels_from_userWRITERemove one or more labels from a Plain user (agent) by label ID. Returns the remaining labels still applied to the user after the removal. Requires the `label:delete` permission.
plain_mutation_remove_members_from_tierWRITERemoves one or more tenants or companies from their current tier (up to 25 per call). After removal the member has no tier. Requires the `tierMembership:read` and `tierMembership:delete` permissions.
plain_mutation_reorder_thread_field_schemasWRITEUpdates the display order of thread field schemas. You only need to include schemas whose order is changing — omitted schemas are left unchanged.
plain_mutation_reply_to_threadWRITESend a reply to the customer on a thread using the most appropriate channel automatically. Supports threads where the last inbound message is an email, a Slack message, or a form submission.
plain_mutation_set_customer_tenantsWRITEReplaces the full set of tenant memberships for a customer in a single call. Any tenants not included in the input are removed; any new ones are added.
plain_mutation_snooze_threadWRITESnooze a thread for a number of seconds or until the customer replies (set `statusDetail` to `WAITING_FOR_CUSTOMER`). A snoozed thread is automatically unsnoozed when new activity arrives or when the timer expires.
plain_mutation_unarchive_label_typeWRITERestores an archived label type so it can be applied to threads again. Requires the `labelType:edit` permission.
plain_mutation_unassign_threadWRITERemove the primary assignee from a thread, leaving it unassigned. Requires the `thread:unassign` and `thread:read` permissions.
plain_mutation_unmark_customer_as_spamWRITEClears the spam flag from a customer, restoring their threads to the inbox and metrics. The `markedAsSpamAt` timestamp is cleared. Requires the `customer:edit` permission.
plain_mutation_update_company_tierWRITESets the tier for a single company, identified by its Plain ID or domain name. Pass a null `tierIdentifier` to remove the company from its current tier.
plain_mutation_update_customer_companyWRITEAssigns a customer to a different company, or clears their company association. Requires the `customer:edit` permission.
plain_mutation_update_customer_groupWRITEUpdate the name, key, color, or external ID of an existing customer group. At least one field must be provided in the input. Requires `customerGroup:edit` permission.
plain_mutation_update_label_typeWRITEUpdates properties of an existing label type. Uses field-level wrapper inputs: pass `{ value: ... }` to change a field, or omit the field entirely to leave it unchanged.
plain_mutation_update_noteWRITEUpdates the text, markdown, or attachments of an existing note. Requires the `note:edit` permission.
plain_mutation_update_snippetWRITEUpdates one or more fields of an existing snippet. Each field uses a wrapper input — pass `{ value: "..." }` to set it or omit it entirely to leave it unchanged.
plain_mutation_update_tenant_tierWRITESets the tier for a single tenant, identified by its Plain ID or external ID. Pass a null `tierIdentifier` to remove the tenant from its current tier.
plain_mutation_update_thread_agent_statusWRITEUpdate the AI agent status of a thread (`IN_PROGRESS`, `HANDED_OFF`, or `HANDLED`). Use this to signal that an AI agent has taken over, handed off to a human, or fully resolved the thread.
plain_mutation_update_thread_escalation_pathWRITEAttach a thread to a specific escalation path, or pass `escalationPathId: null` to detach it. An escalation path defines the sequence of users or teams the thread escalates through if nobody responds in time.
plain_mutation_update_thread_external_idWRITEUpdate the external ID of a thread. Pass `externalId: null` to clear it. Requires the `thread:edit` permission.
plain_mutation_update_thread_field_schemaWRITEUpdates an existing thread field schema. All fields except `key` and `type` are mutable. String fields use wrapper inputs (e.g. `{ value: "..." }`) to distinguish a deliberate null from an omitted value.
plain_mutation_update_thread_suggested_action_statusWRITEAccept or dismiss a specific AI-suggested action on a thread's catchup summary. Pass the `suggestedActionId` from the thread's `catchupDetail` and the new `status`. Requires the `thread:edit` permission.
plain_mutation_update_thread_tenantWRITEMove a thread to a different tenant, or pass `tenantIdentifier: null` to detach it from its current tenant. Requires the `thread:edit` permission.
plain_mutation_update_thread_tierWRITEAssign a thread to a tier, which governs the SLAs applied to it. Pass `tierIdentifier: null` to detach the thread from its current tier. Requires the `thread:edit` permission.
plain_mutation_update_thread_titleWRITEUpdate the title of a thread. Requires the `thread:edit` permission.
plain_mutation_update_tierWRITEUpdates the name, color, external ID, default priority, or default flag of an existing tier. Only the fields you provide are changed. Requires the `tier:update` permission.
plain_mutation_upsert_companyWRITECreates a new company or updates an existing one identified by `companyId` or `companyDomainName`. The output includes a `result` field of either `CREATED` or `UPDATED` so you can tell which happened.
plain_mutation_upsert_customerWRITECreates or updates a customer identified by email address, external ID, or Plain customer ID. Supply `onCreate` fields for values to set when creating and `onUpdate` fields for values to apply when the customer already…
plain_mutation_upsert_team_settingsWRITECreate or update the settings for a team (label type of kind TEAM). If settings do not yet exist for the given team, they are created with defaults (round-robin disabled, max capacity 5);
plain_mutation_upsert_tenantWRITECreates a new tenant or updates an existing one identified by `externalId` or `tenantId`. Use this to keep Plain's tenant records in sync with the groups or organisations in your own product.
plain_mutation_upsert_tenant_fieldWRITESets or updates a field value for a specific tenant. Identify the target field using `tenantFieldIdentifier` (tenant ID + external field ID) and pass exactly one value argument matching the schema's `type`…
plain_mutation_upsert_thread_fieldWRITESets (or updates) a single thread field value on a thread, identified by thread ID and field key. Creates the field if it does not exist, or overwrites the existing value if it does.
plain_query_companiesREADReturns a paginated list of all companies in your workspace. Supports cursor-based pagination and optional filtering by ID or deletion status. Requires the `company:read` permission.
plain_query_companyREADFetches a single company by its ID. Returns null if no company with that ID exists. Requires the `company:read` permission.
plain_query_customerREADFetch a single customer by their Plain customer ID. Returns null if no customer with that ID exists. Requires the `customer:read` permission.
plain_query_customer_by_emailREADFetch a customer by their email address. Returns null if no customer with that email exists. Requires the `customer:read` permission.
plain_query_customer_by_external_idREADGet a customer by its external ID. A customer's external ID is unique within a workspace.
plain_query_customer_groupREADFetch a single customer group by its ID. Returns null if no group with the given ID exists in the workspace.
plain_query_customer_groupsREADFetch a paginated list of all customer groups in the workspace. Optionally filter by external IDs using the `filters` argument. Uses cursor-based pagination.
plain_query_customersREADFetch a paginated list of all customers in the workspace. Supports filtering by group membership, company, spam status and more via `filters`, and ordering via `sortBy`.
plain_query_deleted_threadsREADPaginated list of threads that have been deleted. Only threads deleted after the deletion audit log was enabled for your workspace are included. Useful for auditing or syncing deletions to an external system.
plain_query_knowledge_source_citations_by_threadREADReturns the knowledge sources cited by AI agent replies across a thread, each linked to the timeline entry it was cited in and carrying a snapshot title (and best-effort url) plus the live cited document when it still…
plain_query_label_typeREADReturns a single label type by its ID. Returns null if no label type with that ID exists. Requires the `labelType:read` permission.
plain_query_label_type_by_external_idREADReturns a label type by its external ID. Returns null if no match is found. External IDs are unique within a workspace and are set when creating or updating a label type. Requires the `labelType:read` permission.
plain_query_label_typesREADReturns a paginated list of label types in the workspace. By default includes both active and archived label types; pass `filters: { isArchived: false }` to exclude archived ones.
plain_query_my_machine_userREADReturns the machine user that owns the current API key. Only callable with a machine user API key; returns a FORBIDDEN error when called with a human user session.
plain_query_my_permissionsREADReturns the full list of permission strings granted to the currently authenticated user or machine user in this workspace. Useful for inspecting what actions the caller is authorized to perform.
plain_query_my_workspaceREADReturns the Workspace associated with the current API key or session. Useful for confirming which workspace a request is scoped to. Returns null if no workspace is in context.
plain_query_related_threadsREADFind threads that are semantically similar to the given thread, ranked by relevance. Each result includes the thread and a distance score (lower means more similar).
plain_query_search_companiesREADSearches companies by name or domain using a case-insensitive partial match. The search term must be at least 2 characters long. Supports cursor-based pagination and optional filtering.
plain_query_search_customersREADSearch for customers using a case-insensitive partial match across name, short name, email, and external ID. Results are sorted by most recently active first.
plain_query_search_tenantsREADSearches tenants by name (case-insensitive partial match) or by external ID (exact match). The search term must be at least 2 characters long. Returns a paginated list of results. Requires the `tenant:read` permission.
plain_query_search_threadsREADFull-text search across thread titles, message contents, and customer names/emails. Accepts optional `ThreadsFilter` to narrow results further.
plain_query_snippetREADFetches a single snippet by its ID, or null if no snippet with that ID exists. Returns soft-deleted snippets (where `isDeleted` is true). Requires the `snippet:read` permission.
plain_query_snippetsREADReturns a paginated list of all snippets in the workspace. Use this to sync or display the full snippet library. Supports standard forward and backward cursor pagination. Requires the `snippet:read` permission.
plain_query_tenantREADFetches a single tenant by its Plain-assigned ID. Returns null if no matching tenant is found. Requires the `tenant:read` permission.
plain_query_tenantsREADReturns a paginated list of tenants in the workspace. Use the `filters` argument to narrow by ID, deleted status, or last-updated time. Requires the `tenant:read` permission.
plain_query_threadREADFetch a single thread by its Plain-assigned ID. Returns null if no thread with that ID exists. Requires the `thread:read` permission.
plain_query_thread_by_external_idREADFetch a thread by the external ID you assigned it, scoped to a specific customer. Because `externalId` is only unique per-customer, both `customerId` and `externalId` are required. Returns null if no match.
plain_query_thread_by_refREADFetch a thread by its human-readable ref (e.g. `T-1234`). Useful when the ref is more convenient to store than the internal ID. Returns null if no match. Requires the `thread:read` permission.
plain_query_thread_clusters_paginatedREADReturns a paginated list of AI-generated thread clusters for the current workspace. Use `filters` to narrow results by company, tenant, or clustering variant.
plain_query_thread_field_schemaREADFetches a single thread field schema by its ID. Returns null if no schema with the given ID exists. Selecting the `createdBy`/`updatedBy` actor down to its `user` additionally requires the `user:read` permission.
plain_query_thread_field_schemasREADReturns all thread field schemas defined in the workspace, paginated. Use this to discover which custom fields exist and their configuration before reading or writing thread field values.
plain_query_threadsREADList threads with optional filtering and sorting, returned as a paginated connection. Supports rich filters (status, assignee, customer, label, priority, date ranges, tenant, tier, thread fields, and more) and multiple…
plain_query_threads_by_external_idREADFetch all threads that share the given external ID, returned as a paginated connection. Unlike `threadByExternalId`, this is not scoped to a customer: because `externalId` is only unique per-customer, the same external…
plain_query_tierREADReturns a single tier by its Plain ID. Returns null if no tier with that ID exists. Requires the `tier:read` permission.
plain_query_tiersREADReturns all tiers in the workspace, sorted by creation date. Supports cursor-based pagination. Requires the `tier:read` permission.
plain_query_timeline_entriesREADReturns a paginated list of all timeline entries for a customer, ordered from oldest to newest. Timeline entries include every event, message, note, and automated activity that has occurred across all of the customer's…
plain_query_timeline_entryREADFetches a single timeline entry by its ID within a customer's timeline. Returns null if the entry does not exist.
plain_query_userREADFetch a single workspace member by their ID. Returns null if no user with that ID exists. Requires the `user:read` permission.
plain_query_user_by_emailREADFetch a workspace member by their email address. Returns null if no match is found. Deleted users are also returned — check the `isDeleted`, `deletedAt`, and `deletedBy` fields to determine whether the user has been…
plain_query_usersREADList all human members of the workspace, with optional filters to narrow results by role assignability. Supports cursor-based pagination. Requires the `user:read` permission.
Often connected alongside
Put Plain behind one governed endpoint.
Same permissions, same audit trail, whatever else you connect next.