type User { id: ID! """The full name e.g. Grace Hopper.""" fullName: String! """A short name for use in UI e.g. Grace.""" publicName: String! """The avatar URL of the user.""" avatarUrl: String """The email associated with this user. Email is unique per user.""" email: String! """(Legacy) Retrieve roles for a specific workspace + user.""" roles: [Role!]! """The role of the user in the workspace.""" role: Role """Additional legacy roles that the user has in the workspace.""" additionalLegacyRoles: [Role!]! status: UserStatus! statusChangedAt: DateTime! createdAt: DateTime! createdBy: InternalActor! updatedAt: DateTime! updatedBy: InternalActor! isDeleted: Boolean! deletedAt: DateTime deletedBy: Actor } type UserAccount { id: ID! """The full name e.g. Grace Hopper.""" fullName: String! """A short name for use in UI e.g. Grace.""" publicName: String! """The email associated with this user. Email is unique per user.""" email: String! } enum UserStatus { ONLINE OFFLINE BREAK } type Workspace { id: ID! name: String! publicName: String! isDemoWorkspace: Boolean! createdBy: InternalActor! createdAt: DateTime! updatedBy: InternalActor! updatedAt: DateTime! workspaceEmailSettings: WorkspaceEmailSettings! workspaceChatSettings: WorkspaceChatSettings! } type WorkspaceInvite { id: ID! """Who sent this invite.""" createdBy: InternalActor! """When the invite was created.""" createdAt: DateTime! """The email that is being invited.""" email: String! """The workspace they are being invited to.""" workspace: Workspace! """Whether the person has accepted the invite.""" isAccepted: Boolean! """The roles that the invite will assign on workspace joining.""" roles: [Role!]! """Who updated this invite.""" updatedBy: InternalActor! """When the invite was updated.""" updatedAt: DateTime! """Whether the user would be assigned a billing rota seat upon joining.""" usingBillingRotaSeat: Boolean! """ The role that the invite will assign on workspace joining. This will replace the roles field. """ role: Role } type Role { id: ID! name: String! description: String permissions: [String!]! isAssignableToCustomer: Boolean! @deprecated(reason: "Use isAssignableToThread instead") isAssignableToThread: Boolean! assignableBillingSeats: [BillingSeatType!]! @deprecated(reason: "Don't use. Will be removed soon.") requiresBillableSeat: Boolean! @deprecated(reason: "Don't use. Will be removed soon.") key: RoleKey } type LabelType { id: ID! name: String! icon: String isArchived: Boolean! archivedBy: InternalActor archivedAt: DateTime createdAt: DateTime! createdBy: InternalActor! updatedAt: DateTime! updatedBy: InternalActor! } type Label { id: ID! labelType: LabelType! createdAt: DateTime! createdBy: InternalActor! updatedAt: DateTime! updatedBy: InternalActor! } """An object modelling an email address and if it's been verified.""" type EmailAddress { """The email address.""" email: String! """ If the email address ownership has been verified (e.g. via sending an email with a code). If the email is not verified, Plain may not email this address. """ isVerified: Boolean! """When the email became verified in Plain.""" verifiedAt: DateTime } type Company { id: ID! name: String! logoUrl(size: Int): String domainName: String! createdAt: DateTime! createdBy: InternalActor! updatedAt: DateTime! updatedBy: InternalActor! } type CompanyEdge { cursor: String! node: Company! } type CompanyConnection { edges: [CompanyEdge!]! pageInfo: PageInfo! } type TenantEdge { cursor: String! node: Tenant! } type TenantConnection { edges: [TenantEdge!]! pageInfo: PageInfo! } enum CustomerStatus { IDLE @deprecated(reason: "Use ThreadStatus.DONE instead") ACTIVE @deprecated(reason: "Use ThreadStatus.TODO instead") SNOOZED @deprecated(reason: "Use ThreadStatus.SNOOZED instead") } """ The core customer entity. A customer only exists (ideally) once. Uniqueness is guaranteed on both of these fields: 1. `externalId` if provided 2. `email` """ type Customer { """Uniquely identifies a customer in Plain.""" id: ID! """Your system's ID for this customer.""" externalId: ID """The full name of the customer.""" fullName: String! """An optional short name of the customer, typically their first name.""" shortName: String """The customer's email address.""" email: EmailAddress! """The avatar URL of the customer.""" avatarUrl: String """The user the customer is assigned to.""" assignedToUser: UserActor """When the customer was assigned to a user.""" assignedAt: DateTime """A subquery to fetch the customer's group memberships.""" customerGroupMemberships(filters: CustomerGroupMembershipsFilter, first: Int, after: String, last: Int, before: String): CustomerGroupMembershipConnection! """A subquery to fetch the customer's tenants.""" tenantMemberships(first: Int, after: String, last: Int, before: String): CustomerTenantMembershipConnection! """The company the customer belongs to.""" company: Company createdAt: DateTime! createdBy: Actor! updatedAt: DateTime! updatedBy: Actor! markedAsSpamAt: DateTime markedAsSpamBy: InternalActor status: CustomerStatus @deprecated(reason: "Use Thread.status instead") statusChangedAt: DateTime @deprecated(reason: "Use Thread.statusChangedAt instead") lastIdleAt: DateTime @deprecated(reason: "Use Thread.statusChangedAt instead") } type CustomerGroup { id: ID! name: String! key: String! color: String! externalId: String createdAt: DateTime! createdBy: InternalActor! updatedAt: DateTime! updatedBy: InternalActor! } type CustomerGroupMembership { customerId: ID! customerGroup: CustomerGroup! createdAt: DateTime! createdBy: InternalActor! updatedAt: DateTime! updatedBy: InternalActor! } type CustomerGroupEdge { cursor: String! node: CustomerGroup! } type CustomerGroupConnection { edges: [CustomerGroupEdge!]! pageInfo: PageInfo! } type CustomerGroupMembershipEdge { cursor: String! node: CustomerGroupMembership! } type CustomerGroupMembershipConnection { edges: [CustomerGroupMembershipEdge!]! pageInfo: PageInfo! } type CustomerTenantMembership { tenant: Tenant! createdAt: DateTime! createdBy: InternalActor! updatedAt: DateTime! updatedBy: InternalActor! } type CustomerTenantMembershipEdge { cursor: String! node: CustomerTenantMembership! } type CustomerTenantMembershipConnection { edges: [CustomerTenantMembershipEdge!]! pageInfo: PageInfo! } enum ThreadFieldSchemaType { STRING BOOL ENUM } type DependsOnThreadFieldType { threadFieldSchemaId: ID! threadFieldSchemaValue: String! } type ThreadFieldSchema { id: ID! label: String! key: String! description: String! order: Int! type: ThreadFieldSchemaType! enumValues: [String!]! defaultStringValue: String defaultBooleanValue: Boolean isRequired: Boolean! isAiAutoFillEnabled: Boolean! dependsOnThreadField: DependsOnThreadFieldType createdAt: DateTime! createdBy: InternalActor! updatedAt: DateTime! updatedBy: InternalActor! } type ThreadField { id: ID! threadId: ID! key: String! type: ThreadFieldSchemaType! isAiGenerated: Boolean! stringValue: String booleanValue: Boolean createdAt: DateTime! createdBy: InternalActor! updatedAt: DateTime! updatedBy: InternalActor! } type ThreadDiscussion { id: ID! threadId: ID! title: String! slackTeamId: String! slackChannelId: String! slackChannelName: String! slackMessageLink: String! messages(first: Int, after: String, last: Int, before: String): ThreadDiscussionMessageConnection! resolvedAt: DateTime createdAt: DateTime! createdBy: Actor! updatedAt: DateTime! updatedBy: Actor! } type ThreadDiscussionMessageConnection { edges: [ThreadDiscussionMessageEdge!]! pageInfo: PageInfo! } type ThreadDiscussionMessageEdge { node: ThreadDiscussionMessage! cursor: String! } type ThreadDiscussionMessageReaction { name: String! actors: [Actor!]! imageUrl: String } type ThreadDiscussionMessage { id: ID! threadDiscussionId: ID! text: String! slackMessageLink: String! attachments: [Attachment!]! lastEditedOnSlackAt: DateTime deletedOnSlackAt: DateTime reactions: [ThreadDiscussionMessageReaction!]! createdAt: DateTime! createdBy: Actor! updatedAt: DateTime! updatedBy: Actor! } type Note { id: ID! text: String! markdown: String customer: Customer! isDeleted: Boolean! createdAt: DateTime! createdBy: InternalActor! deletedAt: DateTime deletedBy: InternalActor updatedAt: DateTime! updatedBy: InternalActor! } type Snippet { id: ID! name: String! text: String! markdown: String isDeleted: Boolean! createdAt: DateTime! createdBy: InternalActor! updatedAt: DateTime! updatedBy: InternalActor! deletedAt: DateTime deletedBy: InternalActor } type EmailSignature { text: String! markdown: String createdAt: DateTime! createdBy: InternalActor! updatedAt: DateTime! updatedBy: InternalActor! } type Chat { id: ID! text: String customerReadAt: DateTime attachments: [Attachment!]! createdAt: DateTime! createdBy: Actor! updatedAt: DateTime! updatedBy: Actor! } type PageInfo { hasPreviousPage: Boolean! hasNextPage: Boolean! startCursor: String endCursor: String } type DateTime { unixTimestamp: String! iso8601: String! } type Time { iso8601: String! } enum SortDirection { ASC DESC } type WorkspaceEdge { cursor: String! node: Workspace! } type WorkspaceConnection { edges: [WorkspaceEdge!]! pageInfo: PageInfo! } type WorkspaceInviteEdge { cursor: String! node: WorkspaceInvite! } type WorkspaceInviteConnection { edges: [WorkspaceInviteEdge!]! pageInfo: PageInfo! } input UsersFilter { isAssignableToCustomer: Boolean @deprecated(reason: "Use isAssignableToThread instead") isAssignableToThread: Boolean } type UserEdge { cursor: String! node: User! } type UserConnection { edges: [UserEdge!]! pageInfo: PageInfo! } type RoleEdge { cursor: String! node: Role! } type RoleConnection { edges: [RoleEdge!]! pageInfo: PageInfo! } type LabelTypeEdge { cursor: String! node: LabelType! } type LabelTypeConnection { edges: [LabelTypeEdge!]! pageInfo: PageInfo! } input LabelTypeFilter { isArchived: Boolean } type ThreadFieldSchemaEdge { cursor: String! node: ThreadFieldSchema! } type ThreadFieldSchemaConnection { edges: [ThreadFieldSchemaEdge!]! pageInfo: PageInfo! } input CustomersFilter { isMarkedAsSpam: Boolean """ Filters customers to those with at least one of the given customer group IDs. Customers with no groups will not be included. Can be combined with other group filters. """ customerGroupIds: [ID!] """ Filters customers to those with at least one of the given customer group keys. Customers with no groups will not be included. Can be combined with other group filters. """ customerGroupKeys: [String!] } enum CustomersSortField { FULL_NAME } input CustomersSort { field: CustomersSortField! direction: SortDirection! } type CustomerEdge { cursor: String! node: Customer! } type CustomerConnection { edges: [CustomerEdge!]! pageInfo: PageInfo! totalCount: Int! } type SnippetEdge { cursor: String! node: Snippet! } type SnippetConnection { edges: [SnippetEdge!]! pageInfo: PageInfo! } type UserActor { userId: ID! user: User! } type CustomerActor { customerId: ID! customer: Customer! } type DeletedCustomerActor { customerId: ID! } type SystemActor { systemId: ID! } type System { id: ID! } type MachineUserActor { machineUserId: ID! machineUser: MachineUser! } type NoteEntry { noteId: ID! text: String! markdown: String } type ChatEntry { chatId: ID! text: String customerReadAt: DateTime attachments: [Attachment!]! } interface TimelineEventEntry { timelineEventId: ID! title: String! components: [EventComponent!]! customerId: ID! externalId: ID } type ThreadEventEntry implements TimelineEventEntry { timelineEventId: ID! title: String! components: [EventComponent!]! customerId: ID! externalId: ID } type CustomerEventEntry implements TimelineEventEntry { timelineEventId: ID! title: String! components: [EventComponent!]! customerId: ID! externalId: ID } type SlackReaction { name: String! actors: [Actor!]! imageUrl: String } type SlackMessageEntry { slackMessageLink: String! text: String! customerId: ID! relatedThread: SlackMessageEntryRelatedThread attachments: [Attachment!]! lastEditedOnSlackAt: DateTime deletedOnSlackAt: DateTime reactions: [SlackReaction!]! } type SlackMessageEntryRelatedThread { threadId: ID! } type SlackReplyEntry { slackMessageLink: String! customerId: ID! text: String! attachments: [Attachment!]! lastEditedOnSlackAt: DateTime deletedOnSlackAt: DateTime reactions: [SlackReaction!]! } type MSTeamsMessage { id: ID! threadId: ID msTeamsTenantId: ID msTeamsConversationId: ID msTeamsMessageId: ID msTeamsTeamId: ID text: String! html: String! createdAt: DateTime! createdBy: Actor! updatedAt: DateTime! updatedBy: Actor! attachments: [Attachment!]! lastEditedOnMsTeamsAt: DateTime deletedOnMsTeamsAt: DateTime } type MSTeamsMessageEntry { text: String! customerId: ID! msTeamsMessageId: ID! attachments: [Attachment!]! lastEditedOnMsTeamsAt: DateTime deletedOnMsTeamsAt: DateTime } type ThreadDiscussionEntry { customerId: ID! threadDiscussionId: ID! slackChannelName: String! slackMessageLink: String! } type ThreadDiscussionResolvedEntry { customerId: ID! threadDiscussionId: ID! slackChannelName: String! slackMessageLink: String! resolvedAt: DateTime! } type FileSize { bytes: Int! kiloBytes: Float! megaBytes: Float! } type Attachment { id: ID! fileName: String! fileSize: FileSize! fileExtension: String fileMimeType: String! type: AttachmentType! createdAt: DateTime! createdBy: Actor! updatedAt: DateTime! updatedBy: Actor! } type CustomerEmailActor { customerId: ID! customer: Customer! } type DeletedCustomerEmailActor { customerId: ID! } type UserEmailActor { userId: ID! user: User! } type SupportEmailAddressEmailActor { supportEmailAddress: String! } union EmailActor = CustomerEmailActor | DeletedCustomerEmailActor | UserEmailActor | SupportEmailAddressEmailActor type EmailParticipant { name: String email: String! emailActor: EmailActor } enum EmailAuthenticity { PASS FAIL UNKNOWN } type EmailBounce { bouncedAt: DateTime! recipient: EmailParticipant! isSendRetriable: Boolean! } enum EmailSendStatus { """The email is being sent.""" PENDING """The email was sent successfully to all recipients.""" SENT """ Some (or all) of the recipients bounced the email, meaning they did not recieve it. Check 'bounces' for more details on which recipients bounced. """ BOUNCED """ The email failed to send. This will happen if the main recipient (To) bounced the email, or if there was an unexpected error sending the email. """ FAILED } type EmailEntry { emailId: ID! to: EmailParticipant! from: EmailParticipant! additionalRecipients: [EmailParticipant!]! hiddenRecipients: [EmailParticipant!]! subject: String """The most recent email's text content.""" textContent: String """ Boolean indicating whether there is more text content available that can be resolved via the `fullTextContent` field. """ hasMoreTextContent: Boolean! """The full email's text content, including all replies.""" fullTextContent: String """The most recent email's markdown content.""" markdownContent: String """ Boolean indicating whether there is more markdown content available that can be resolved via the `fullMarkdownContent` field. """ hasMoreMarkdownContent: Boolean! """The full email's markdown content, including all replies.""" fullMarkdownContent: String authenticity: EmailAuthenticity! """ When the email was sent. Only set for outbound emails and will be null until the email is sent. """ sentAt: DateTime """ Informs whether the email was sent successfully, bounced or failed. If the email is still being sent, the status will be 'PENDING'. Only set for outbound emails. """ sendStatus: EmailSendStatus """When the email was received by Plain.""" receivedAt: DateTime """All the attachments included in this email.""" attachments: [Attachment!]! """ Whether this email entry is the start of a new thread in Plain. Can be used to show the full email content. """ isStartOfThread: Boolean! """ If any of the recipients bounces the email, this will contain the list of bounces. """ bounces: [EmailBounce!]! } enum ComponentTextSize { S M L } enum ComponentTextColor { NORMAL MUTED SUCCESS WARNING ERROR } enum ComponentPlainTextSize { S M L } enum ComponentPlainTextColor { NORMAL MUTED SUCCESS WARNING ERROR } enum ComponentBadgeColor { GREY GREEN YELLOW RED BLUE } type ComponentText { textSize: ComponentTextSize textColor: ComponentTextColor text: String! color: ComponentTextColor @deprecated(reason: "Use textColor instead, which has the same type") size: ComponentTextSize @deprecated(reason: "Use textSize instead, which has the same type") } type ComponentPlainText { plainTextSize: ComponentPlainTextSize plainTextColor: ComponentPlainTextColor plainText: String! } enum ComponentSpacerSize { XS S M L XL } type ComponentSpacer { spacerSize: ComponentSpacerSize! size: ComponentSpacerSize! @deprecated(reason: "Use spacerSize instead, which has the same type") } enum ComponentDividerSpacingSize { XS S M L XL } type ComponentDivider { dividerSpacingSize: ComponentDividerSpacingSize spacingSize: ComponentDividerSpacingSize @deprecated(reason: "use dividerSpacingSize instead") } type ComponentLinkButton { linkButtonUrl: String! linkButtonLabel: String! url: String! @deprecated(reason: "use linkButtonUrl instead") label: String! @deprecated(reason: "use linkButtonLabel instead") } type ComponentCopyButton { copyButtonValue: String! copyButtonTooltipLabel: String } type ComponentBadge { badgeLabel: String! badgeColor: ComponentBadgeColor } type ComponentRow { rowMainContent: [ComponentRowContent!]! rowAsideContent: [ComponentRowContent!]! } type ComponentContainer { containerContent: [ComponentContainerContent!]! } union ComponentContainerContent = ComponentText | ComponentPlainText | ComponentSpacer | ComponentDivider | ComponentLinkButton | ComponentBadge | ComponentCopyButton | ComponentRow union ComponentRowContent = ComponentText | ComponentPlainText | ComponentSpacer | ComponentDivider | ComponentLinkButton | ComponentBadge | ComponentCopyButton union CustomTimelineEntryComponent = ComponentText | ComponentPlainText | ComponentSpacer | ComponentDivider | ComponentLinkButton | ComponentRow | ComponentContainer | ComponentBadge | ComponentCopyButton union EventComponent = ComponentText | ComponentPlainText | ComponentSpacer | ComponentDivider | ComponentLinkButton | ComponentRow | ComponentBadge | ComponentCopyButton union CustomerCardComponent = ComponentText | ComponentPlainText | ComponentSpacer | ComponentDivider | ComponentLinkButton | ComponentRow | ComponentContainer | ComponentBadge | ComponentCopyButton type CustomEntry { externalId: ID title: String! type: String components: [CustomTimelineEntryComponent!]! attachments: [Attachment!]! } type ThreadAssignmentTransitionedEntry { previousAssignee: ThreadAssignee nextAssignee: ThreadAssignee } type ThreadStatusTransitionedEntry { previousStatus: ThreadStatus! previousStatusDetail: ThreadStatusDetail nextStatus: ThreadStatus! nextStatusDetail: ThreadStatusDetail } type ThreadPriorityChangedEntry { previousPriority: Int! nextPriority: Int! } type ThreadLabelsChangedEntry { previousLabels: [Label!]! nextLabels: [Label!]! } type ServiceLevelAgreementStatusTransitionedEntry { previousStatus: ServiceLevelAgreementStatus! nextStatus: ServiceLevelAgreementStatus! serviceLevelAgreement: ServiceLevelAgreement } union Actor = UserActor | CustomerActor | DeletedCustomerActor | SystemActor | MachineUserActor union InternalActor = UserActor | SystemActor | MachineUserActor """A union of all possible entries that can appear in a timeline.""" union Entry = NoteEntry | ChatEntry | EmailEntry | CustomEntry | LinearIssueThreadLinkStateTransitionedEntry | ThreadAssignmentTransitionedEntry | ThreadStatusTransitionedEntry | ThreadPriorityChangedEntry | ThreadEventEntry | CustomerEventEntry | SlackMessageEntry | SlackReplyEntry | ThreadLabelsChangedEntry | ThreadDiscussionEntry | ThreadDiscussionResolvedEntry | ServiceLevelAgreementStatusTransitionedEntry | MSTeamsMessageEntry type LinearIssueThreadLinkStateTransitionedEntry { linearIssueId: ID! """ Refers to the id of the WorkflowState object in Linear. This can be used to fetch the WorkflowState from the Linear API. """ previousLinearStateId: ID! """ Refers to the id of the WorkflowState object in Linear. This can be used to fetch the WorkflowState from the Linear API. """ nextLinearStateId: ID! } type TimelineEntry { id: ID! customerId: ID! threadId: ID! timestamp: DateTime! entry: Entry! actor: Actor! } type CustomerEvent { """The ID of the event.""" id: ID! """The customer that this event belongs to.""" customerId: ID! """The title of the event.""" title: String! """The list of components of the event.""" components: [EventComponent!]! """The datetime when this event was created.""" createdAt: DateTime! """The actor who created this event.""" createdBy: Actor! """The datetime when this event was last updated.""" updatedAt: DateTime! """The actor who last updated this event.""" updatedBy: Actor! } type ThreadEvent { """The ID of the event.""" id: ID! """The customer that this event belongs to.""" customerId: ID! """The thread that this event belongs to.""" threadId: ID! """The title of the event.""" title: String! """The list of components of the event.""" components: [EventComponent!]! """The datetime when this event was created.""" createdAt: DateTime! """The actor who created this event.""" createdBy: Actor! """The datetime when this event was last updated.""" updatedAt: DateTime! """The actor who last updated this event.""" updatedBy: Actor! } type TimelineEntryEdge { cursor: String! node: TimelineEntry! } type TimelineEntryConnection { edges: [TimelineEntryEdge!]! pageInfo: PageInfo! } type MachineUser { id: ID! fullName: String! publicName: String! description: String apiKey(apiKeyId: ID!): ApiKey apiKeys(first: Int, after: String, last: Int, before: String): ApiKeyConnection! createdBy: InternalActor! createdAt: DateTime! updatedBy: InternalActor! updatedAt: DateTime! isDeleted: Boolean! deletedAt: DateTime deletedBy: Actor } type MachineUserEdge { cursor: String! node: MachineUser! } type MachineUserConnection { edges: [MachineUserEdge!]! pageInfo: PageInfo! } type ApiKey { id: ID! description: String permissions: [String!]! createdBy: InternalActor! createdAt: DateTime! updatedBy: InternalActor! updatedAt: DateTime! isDeleted: Boolean! deletedAt: DateTime deletedBy: Actor } type ApiKeyEdge { cursor: String! node: ApiKey! } type ApiKeyConnection { edges: [ApiKeyEdge!]! pageInfo: PageInfo! } type Permissions { permissions: [String!]! } type WorkspaceMSTeamsInstallationInfo { installationUrl: String! } type UserMSTeamsInstallationInfo { installationUrl: String } type UserMSTeamsIntegration { id: ID! msTeamsTenantId: ID! isReinstallRequired: Boolean! msTeamsPreferredUsername: String createdAt: DateTime! createdBy: InternalActor! updatedAt: DateTime! updatedBy: InternalActor! } type UserSlackInstallationInfo { installationUrl: String! } type WorkspaceSlackInstallationInfo { installationUrl: String! } type WorkspaceSlackChannelInstallationInfo { installationUrl: String! } type UserAuthSlackInstallationInfo { installationUrl: String! } type UserSlackIntegration { integrationId: ID! slackTeamName: String! isReinstallRequired: Boolean! createdAt: DateTime! createdBy: InternalActor! updatedAt: DateTime! updatedBy: InternalActor! } type UserAuthSlackIntegration { integrationId: ID! slackTeamId: String! slackTeamName: String! isReinstallRequired: Boolean! createdAt: DateTime! createdBy: InternalActor! updatedAt: DateTime! updatedBy: InternalActor! } type WorkspaceSlackIntegration { integrationId: ID! slackChannelName: String! slackTeamName: String! slackTeamImageUrl68px: String isReinstallRequired: Boolean! createdAt: DateTime! createdBy: InternalActor! updatedAt: DateTime! updatedBy: InternalActor! } type WorkspaceSlackIntegrationEdge { cursor: String! node: WorkspaceSlackIntegration! } type WorkspaceSlackIntegrationConnection { edges: [WorkspaceSlackIntegrationEdge!]! pageInfo: PageInfo! } type WorkspaceSlackChannelIntegrationEdge { cursor: String! node: WorkspaceSlackChannelIntegration! } type WorkspaceSlackChannelIntegrationConnection { edges: [WorkspaceSlackChannelIntegrationEdge!]! pageInfo: PageInfo! } type WorkspaceSlackChannelIntegration { integrationId: ID! slackTeamId: String! slackTeamName: String! slackTeamImageUrl68px: String isReinstallRequired: Boolean! createdAt: DateTime! createdBy: InternalActor! updatedAt: DateTime! updatedBy: InternalActor! } type WorkspaceDiscordIntegration { integrationId: ID! name: String! webhookUrl: String! createdAt: DateTime! createdBy: InternalActor! updatedAt: DateTime! updatedBy: InternalActor! } type WorkspaceDiscordIntegrationEdge { cursor: String! node: WorkspaceDiscordIntegration! } type WorkspaceDiscordIntegrationConnection { edges: [WorkspaceDiscordIntegrationEdge!]! pageInfo: PageInfo! } type UserLinearInstallationInfo { installationUrl: String! } type LinearIntegrationToken { token: String! } type UserLinearIntegration { integrationId: ID! linearOrganisationName: String! linearOrganisationId: ID! createdAt: DateTime! createdBy: InternalActor! updatedAt: DateTime! updatedBy: InternalActor! } """An API header that will be sent to the configured API URL.""" type CustomerCardConfigApiHeader { """ The name of the header, trimmed and treated case insensitively for deduplication purposes (min length: 1, max length: 100). Not all header names are allowed. """ name: String! """ The value of the header, treated case sensitively for deduplication purposes (min length: 1, max length: 500). """ value: String! } """ The configuration of a customer card that defines four important things: - The title of the card - The key of the card, which will be used in the request payload to the API URL - The order in which the cards should appear - Which API the card should be loaded from (and the required authentication headers) Configs that have the same API URL and API Headers will be loaded in batch. API header names are treated case insensitively. A maximum of 25 customer cards can be configured. """ type CustomerCardConfig { """The ID of the customer card config.""" id: ID! """ The order in which this customer card config should be shown. Duplicate order numbers are allowed, in case the order is the same they will be sorted based on `id`. The minimum is 0 and the maximum is 100000. """ order: Int! """The title of the card (max length: 500 characters).""" title: String! """ The key of the card (must be unique in a workspace, max length: 500 characters, must match regex: `[a-zA-Z0-9_-]+`). """ key: String! """ The default time the card should be cached for if no TTL is provided in the card response. (minimum: 15 seconds, maximum: 1 year or 31,536,000 seconds). """ defaultTimeToLiveSeconds: Int! """ The URL from which this card should be loaded (must start with `https://` and be a valid URL, max length: 600 characters). Requires the `customerCardConfigApiDetails:read` permission. """ apiUrl: String! """ An array of headers name-value pairs (maximum length of array: 20). Requires the `customerCardConfigApiDetails:read` permission. """ apiHeaders: [CustomerCardConfigApiHeader!]! """ Indicates if the customer card is enabled or not. Disabled customer card configs are not loaded or displayed for customers. """ isEnabled: Boolean! createdAt: DateTime! createdBy: InternalActor! updatedAt: DateTime! updatedBy: InternalActor! } type SettingScope { id: ID scopeType: SettingScopeType! } """A boolean setting""" type BooleanSetting { """The setting code.""" code: String! """ The value of the setting. This is named uniquely (instead of just `value`) so that the union has unique fields. """ booleanValue: Boolean! """The scope of the setting.""" scope: SettingScope! } """A string setting""" type StringSetting { """The setting code.""" code: String! """ The value of the setting. This is named uniquely (instead of just `value`) so that the union has unique fields. """ stringValue: String! """The scope of the setting.""" scope: SettingScope! } """A union of different types of settings.""" union Setting = BooleanSetting | StringSetting """An enum to describe the type of scope the setting is for.""" enum SettingScopeType { """ Scope for any user level settings An `id` is not needed as it will implicitly be the authenticated user's id. """ USER """ Scope for the authenticated user's email notification settings. An `id` is not needed as it will implicitly be the authenticated user's id. """ USER_EMAIL_NOTIFICATIONS """ Scope for the authenticated user's slack notification settings. An `id` is not needed as it will implicitly be the authenticated user's id. """ USER_SLACK_NOTIFICATIONS """ Scope for slack support channel settings. An `id` is mandatory and should be a workspace slack channel integration id (`wsSlackInt_123`) """ WORKSPACE_SLACK_CHANNEL """ Scope for slack notifications configured for the whole workspace. An `id` is mandatory and should be a workspace slack integration id (`wsSlackInt_123`) """ WORKSPACE_SLACK_NOTIFICATIONS """ Scope for discord notifications configured for the whole workspace. An `id` is mandatory and should be a workspace discord integration id (`wsDiscordInt_123`) """ WORKSPACE_DISCORD_NOTIFICATIONS """ Scope for workspace level settings for the whole workspace. An `id` is not needed as it will implicitly be the current workspace id. """ WORKSPACE } """ The different ways in which a string is matched. Exactly one of these must be provided in a single search expression. """ input StringSearchExpression { """Case-insensitive match values containing the provided string.""" caseInsensitiveContains: String } """ The customer attributes available for search, each of them mapped to a search expression. Exactly one of them must be provided in a single search condition. """ input CustomerSearchCondition { """Search expression on the customer's full name.""" fullName: StringSearchExpression """Search expression on the customer's short name.""" shortName: StringSearchExpression """Search expression on the customer's email address.""" email: StringSearchExpression """Search expression on the customer's external id.""" externalId: StringSearchExpression """ Search expression on specific timeline entries' text (email, chat) sent or received by the customer. Common English stop-words will be removed from the text to search. """ timelineEntryText: StringSearchExpression } """ A query to search for customers. Search queries are combinations of search conditions, as defined below. At least one search condition must be provided. """ input CustomersSearchQuery { """ An array of search conditions that will be combined using a 'logical OR' to search for customers. """ or: [CustomerSearchCondition!] } type CustomerSearchEdge { cursor: String! node: Customer! } type CustomerSearchConnection { edges: [CustomerSearchEdge!]! pageInfo: PageInfo! } """ A shared interface for all common properties customer card instances can have. A customer can only have one customer card instance for each customer card config at any point in time. Has 3 implementations: - `CustomerCardInstanceLoading` - `CustomerCardInstanceLoaded` - `CustomerCardInstanceError` """ interface CustomerCardInstance { """ The ID of the customer card instance. A new ID is generated for each load. """ id: ID! """The customer the instance is for.""" customerId: ID! """ The thread the instance is for. Null if this card is not loaded in a thread context. """ threadId: ID """The customer card config this instance is for.""" customerCardConfig: CustomerCardConfig! createdAt: DateTime! createdBy: Actor! updatedAt: DateTime! updatedBy: Actor! } """ A loading customer card. The createdAt timestamp indicates when the load was started. Will be updated to be a CustomerCardInstanceLoaded or CustomerCardInstanceError. """ type CustomerCardInstanceLoading implements CustomerCardInstance { """ The ID of the customer card instance. A new ID is generated for each load. """ id: ID! """The customer the instance is for.""" customerId: ID! """ The thread the instance is for. Null if this card is not loaded in a thread context. """ threadId: ID """The customer card config this instance is for.""" customerCardConfig: CustomerCardConfig! createdAt: DateTime! createdBy: Actor! updatedAt: DateTime! updatedBy: Actor! } """A loaded customer card.""" type CustomerCardInstanceLoaded implements CustomerCardInstance { """ The ID of the customer card instance. A new ID is generated for each load. """ id: ID! """The customer the instance is for.""" customerId: ID! """ The thread the instance is for. Null if this card is not loaded in a thread context. """ threadId: ID """The customer card config this instance is for.""" customerCardConfig: CustomerCardConfig! """ The list of components of the customer card. If this is null it means the customer card was returned on the API, but the components array was empty. """ components: [CustomerCardComponent!] """When the customer card was received from the API.""" loadedAt: DateTime! expiresAt: DateTime! createdAt: DateTime! createdBy: Actor! updatedAt: DateTime! updatedBy: Actor! } """The configured API URL didn't return a requested card key.""" type CustomerCardInstanceMissingCardErrorDetail { message: String! cardKey: String! } """An invalid response body was returned from the configured API URL.""" type CustomerCardInstanceResponseBodyErrorDetail { message: String! responseBody: String! } """A non-200 status code was returned from the configured API URL.""" type CustomerCardInstanceStatusCodeErrorDetail { message: String! statusCode: Int! responseBody: String! } """Plain failed to make the request to the configured API URL.""" type CustomerCardInstanceRequestErrorDetail { message: String! errorCode: String! } """ An unknown error occurred. If this error is persistent, please contact our support. """ type CustomerCardInstanceUnknownErrorDetail { message: String! } """The card failed to load within the timeout.""" type CustomerCardInstanceTimeoutErrorDetail { message: String! timeoutSeconds: Int! } """Details for the reasons why the customer card failed to load.""" union CustomerCardInstanceErrorDetail = CustomerCardInstanceMissingCardErrorDetail | CustomerCardInstanceResponseBodyErrorDetail | CustomerCardInstanceStatusCodeErrorDetail | CustomerCardInstanceRequestErrorDetail | CustomerCardInstanceUnknownErrorDetail | CustomerCardInstanceTimeoutErrorDetail type CustomerCardInstanceError implements CustomerCardInstance { """ The ID of the customer card instance. A new ID is generated for each load. """ id: ID! """The customer the instance is for.""" customerId: ID! """ The thread the instance is for. Null if this card is not loaded in a thread context. """ threadId: ID """The customer card config this instance is for.""" customerCardConfig: CustomerCardConfig! """The details of the customer card load error.""" errorDetail: CustomerCardInstanceErrorDetail! createdAt: DateTime! createdBy: Actor! updatedAt: DateTime! updatedBy: Actor! } type Query { myUserAccount: UserAccount myUser: User myMachineUser: MachineUser myWorkspace: Workspace myPermissions: Permissions! myWorkspaces(first: Int, after: String, last: Int, before: String): WorkspaceConnection! myWorkspaceInvites(first: Int, after: String, last: Int, before: String): WorkspaceInviteConnection! mySlackIntegration: UserSlackIntegration mySlackInstallationInfo(redirectUrl: String!): UserSlackInstallationInfo! myLinearIntegration: UserLinearIntegration myLinearInstallationInfo(redirectUrl: String!): UserLinearInstallationInfo! myLinearIntegrationToken: LinearIntegrationToken myEmailSignature: EmailSignature billingPlans(first: Int, after: String, last: Int, before: String): BillingPlanConnection! myBillingSubscription: BillingSubscription myBillingRota: BillingRota myPaymentMethod: PaymentMethod labelTypes(filters: LabelTypeFilter, first: Int, after: String, last: Int, before: String): LabelTypeConnection! labelType(labelTypeId: ID!): LabelType roles(first: Int, after: String, last: Int, before: String): RoleConnection! timelineEntries(customerId: ID!, first: Int, after: String, last: Int, before: String): TimelineEntryConnection! timelineEntry(customerId: ID!, timelineEntryId: ID!): TimelineEntry workspace(workspaceId: ID!): Workspace user(userId: ID!): User """ Returns a user by email or null if the user is not found. Deleted users are also returned, see isDeleted, deletedAt and deletedBy fields on the User type. """ userByEmail(email: String!): User users(filters: UsersFilter, first: Int, after: String, last: Int, before: String): UserConnection! workspaceInvites(first: Int, after: String, last: Int, before: String): WorkspaceInviteConnection! customer(customerId: ID!): Customer customers(filters: CustomersFilter, sortBy: CustomersSort, first: Int, after: String, last: Int, before: String): CustomerConnection! customerByEmail(email: String!): Customer """ Get a customer by its external ID. A customer's external ID is unique within a workspace. """ customerByExternalId(externalId: ID!): Customer """Get a customer group by ID.""" customerGroup(customerGroupId: ID!): CustomerGroup """Get a paginated list of customer groups.""" customerGroups(filters: CustomerGroupsFilter, first: Int, after: String, last: Int, before: String): CustomerGroupConnection! threadFieldSchemas(first: Int, after: String, last: Int, before: String): ThreadFieldSchemaConnection! threadFieldSchema(threadFieldSchemaId: ID!): ThreadFieldSchema """ Loads the customer's card instances. This query will return any cards that are loaded and within their expiry time. For cards that are past their expiry or are errored it will request a load of the cards and return a `CustomerCardInstanceLoading`. A maximum of 25 card instances will be returned, due to only allowing 25 customer card configs. """ customerCardInstances(customerId: ID!, threadId: ID): [CustomerCardInstance!]! """ Search for customers based on the provided query. Returned customers are sorted by how recently they changed status (most recent first). """ searchCustomers(searchQuery: CustomersSearchQuery!, first: Int, after: String, last: Int, before: String): CustomerSearchConnection! snippets(first: Int, after: String, last: Int, before: String): SnippetConnection! snippet(snippetId: ID!): Snippet workspaceEmailSettings: WorkspaceEmailSettings! workspaceChatSettings: WorkspaceChatSettings! machineUser(machineUserId: ID!): MachineUser machineUsers(first: Int, after: String, last: Int, before: String): MachineUserConnection! permissions: Permissions! workspaceMSTeamsInstallationInfo(redirectUrl: String!): WorkspaceMSTeamsInstallationInfo! workspaceMSTeamsIntegration: WorkspaceMSTeamsIntegration myMSTeamsInstallationInfo(redirectUrl: String!): UserMSTeamsInstallationInfo! myMSTeamsIntegration: UserMSTeamsIntegration workspaceSlackInstallationInfo(redirectUrl: String!): WorkspaceSlackInstallationInfo! workspaceSlackIntegrations(first: Int, after: String, last: Int, before: String): WorkspaceSlackIntegrationConnection! workspaceSlackIntegration(integrationId: ID!): WorkspaceSlackIntegration workspaceSlackChannelInstallationInfo(redirectUrl: String!): WorkspaceSlackChannelInstallationInfo! workspaceSlackChannelIntegration(integrationId: ID!): WorkspaceSlackChannelIntegration workspaceSlackChannelIntegrations(first: Int, after: String, last: Int, before: String): WorkspaceSlackChannelIntegrationConnection! """ Searches for slack users in a thread based on a search term. The search term can be part of either the slack's handle or full name. """ searchThreadSlackUsers(threadId: ID!, searchQuery: String!, first: Int, after: String, last: Int, before: String): SlackUserConnection! """ Searches for slack users in a slack channel based on a search term. The search term can be part of either the slack's handle or full name. """ searchSlackUsers(slackTeamId: String!, slackChannelId: String!, searchQuery: String!, first: Int, after: String, last: Int, before: String): SlackUserConnection! """ Gets all slack channels for this workspace, which match the specified filters. """ connectedSlackChannels(filters: ConnectedSlackChannelsFilter, first: Int, after: String, last: Int, before: String): ConnectedSlackChannelConnection! """Gets a single slack user within a thread based on their slack user ID.""" threadSlackUser(threadId: ID!, slackUserId: ID!): SlackUser """ Gets a single slack user within a channel based on their slack user ID. """ slackUser(slackTeamId: String!, slackChannelId: String!, slackUserId: ID!): SlackUser userAuthSlackIntegration(slackTeamId: String!): UserAuthSlackIntegration userAuthSlackIntegrationByThreadId(threadId: ID!): UserAuthSlackIntegration userAuthSlackInstallationInfo(redirectUrl: String!, slackTeamId: String): UserAuthSlackInstallationInfo! workspaceDiscordIntegrations(first: Int, after: String, last: Int, before: String): WorkspaceDiscordIntegrationConnection! workspaceDiscordIntegration(integrationId: ID!): WorkspaceDiscordIntegration customerCardConfigs: [CustomerCardConfig!]! customerCardConfig(customerCardConfigId: ID!): CustomerCardConfig """Gets a single setting based on the code and the scope.""" setting(code: String!, scope: SettingScopeInput!): Setting """List webhook versions.""" webhookVersions(first: Int, after: String, last: Int, before: String): WebhookVersionConnection! """Gets a webhook target.""" webhookTarget(webhookTargetId: ID!): WebhookTarget """List webhook targets.""" webhookTargets(first: Int, after: String, last: Int, before: String): WebhookTargetConnection! """List all the events types you can subscribe to.""" subscriptionEventTypes: [SubscriptionEventType!]! """Get a thread by its ID.""" thread(threadId: ID!): Thread """ Get a thread by its external ID. A thread's external ID is unique within a customer, hence why the customer ID is required. """ threadByExternalId(customerId: ID!, externalId: ID!): Thread threads(filters: ThreadsFilter, sortBy: ThreadsSort, first: Int, after: String, last: Int, before: String): ThreadConnection! searchThreads(searchQuery: ThreadsSearchQuery!, first: Int, after: String, last: Int, before: String): ThreadSearchResultConnection! autoresponder(autoresponderId: ID!): Autoresponder autoresponders(first: Int, after: String, last: Int, before: String): AutoresponderConnection! timeSeriesMetric(name: String!, options: TimeSeriesMetricOptions): TimeSeriesMetric singleValueMetric(name: String!, options: SingleValueMetricOptions): SingleValueMetric companies(first: Int, after: String, last: Int, before: String, filters: CompaniesFilter): CompanyConnection! company(companyId: ID!): Company searchCompanies(searchQuery: CompaniesSearchQuery!, first: Int, after: String, last: Int, before: String): CompanySearchResultConnection! tenants(first: Int, after: String, last: Int, before: String): TenantConnection! tenant(tenantId: ID!): Tenant searchTenants(searchQuery: TenantsSearchQuery!, first: Int, after: String, last: Int, before: String): TenantSearchResultConnection! threadDiscussion(threadDiscussionId: ID!): ThreadDiscussion serviceAuthorization(serviceAuthorizationId: ID!): ServiceAuthorization serviceAuthorizations(first: Int, after: String, last: Int, before: String): ServiceAuthorizationConnection! tiers(first: Int, after: String, last: Int, before: String): TierConnection! tier(tierId: ID!): Tier businessHours: BusinessHours workspaceHmac: WorkspaceHmac } type Tier { id: ID! """The name of this tier.""" name: String! """ The external ID of this tier. You can use this field to store your own unique identifier for this tier. This must be unique in your workspace. """ externalId: String """ The color to assign to this tier, given by its hex code (e.g. #FABADA). This color is used in Plain's UI to represent this tier. """ color: String! """ If true, this tier will be applied to all threads that do not match any other tier. Only one tier can be the default tier. """ isDefault: Boolean! """ Any thread created in this tier will have this priority by default, unless a different priority is specified while creating it. """ defaultPriority: Int! @deprecated(reason: "Use defaultThreadPriority instead.") """ Any thread created in this tier will have this priority by default, unless a different priority is specified while creating it. """ defaultThreadPriority: Int! memberships(first: Int, after: String, last: Int, before: String): TierMembershipConnection! serviceLevelAgreements: [ServiceLevelAgreement!]! createdAt: DateTime! createdBy: InternalActor! updatedAt: DateTime! updatedBy: InternalActor! } type TenantTierMembership { id: ID! tierId: ID! tenantId: ID! createdAt: DateTime! createdBy: InternalActor! updatedAt: DateTime! updatedBy: InternalActor! } type CompanyTierMembership { id: ID! tierId: ID! companyId: ID! createdAt: DateTime! createdBy: InternalActor! updatedAt: DateTime! updatedBy: InternalActor! } union TierMembership = TenantTierMembership | CompanyTierMembership type TierMembershipEdge { cursor: String! node: TierMembership! } type TierMembershipConnection { edges: [TierMembershipEdge!]! pageInfo: PageInfo! totalCount: Int! } interface ServiceLevelAgreement { id: ID! """ If true, the SLA will only be tracked during your workspace's business hours. If false, the SLA will tracked 24/7. """ useBusinessHoursOnly: Boolean! """ This SLA can only be applied to a thread if it has one of these priority values. """ threadPriorityFilter: [Int!]! """ The actions to take when the SLA is about to breach and when it breaches. """ breachActions: [BreachAction!]! createdAt: DateTime! createdBy: InternalActor! updatedAt: DateTime! updatedBy: InternalActor! } type FirstResponseTimeServiceLevelAgreement implements ServiceLevelAgreement { id: ID! """ This SLA will breach if it does not receive a first response within this many minutes. """ firstResponseTimeMinutes: Int! useBusinessHoursOnly: Boolean! threadPriorityFilter: [Int!]! breachActions: [BreachAction!]! createdAt: DateTime! createdBy: InternalActor! updatedAt: DateTime! updatedBy: InternalActor! } type NextResponseTimeServiceLevelAgreement implements ServiceLevelAgreement { id: ID! """ This SLA will breach if it does not receive a next response within this many minutes. """ nextResponseTimeMinutes: Int! useBusinessHoursOnly: Boolean! threadPriorityFilter: [Int!]! breachActions: [BreachAction!]! createdAt: DateTime! createdBy: InternalActor! updatedAt: DateTime! updatedBy: InternalActor! } union BreachAction = BeforeBreachAction type BeforeBreachAction { beforeBreachMinutes: Int! createdAt: DateTime! createdBy: InternalActor! updatedAt: DateTime! updatedBy: InternalActor! } type TierEdge { cursor: String! node: Tier! } type TierConnection { edges: [TierEdge!]! pageInfo: PageInfo! } input CustomerGroupsFilter { externalIds: [String!] } input CustomerGroupMembershipsFilter { customerGroupExternalIds: [String!] } enum MetricDimensionType { AGENT COMPANY CUSTOMER_GROUP LABEL_TYPE MESSAGE_SOURCE PRIORITY THREAD_FIELD TIER } type MetricDimension { type: MetricDimensionType! value: String! } input SingleValueMetricOptions { """Defaults to 24 hours ago.""" from: String to: String dimension: MetricDimensionType subDimension: String } type SingleValueMetricValue { value: Float dimension: MetricDimension } type SingleValueMetric { values: [SingleValueMetricValue!]! } enum TimeSeriesMetricDimensionType { AGENT COMPANY CUSTOMER_GROUP LABEL_TYPE MESSAGE_SOURCE PRIORITY THREAD_FIELD TIER } enum TimeSeriesMetricIntervalUnit { HOUR DAY } input TimeSeriesMetricInterval { unit: TimeSeriesMetricIntervalUnit } input TimeSeriesMetricOptions { """Defaults to 24 hours ago.""" from: String to: String dimension: TimeSeriesMetricDimensionType subDimension: String interval: TimeSeriesMetricInterval } type TimeSeriesMetric { timestamps: [DateTime!]! series: [TimeSeriesSeries!]! } type TimeSeriesSeries { values: [Float]! dimension: TimeSeriesMetricDimension } type TimeSeriesMetricDimension { type: TimeSeriesMetricDimensionType! value: String! } input TierIdentifierInput { tierId: ID externalId: String } input ThreadFieldFilter { key: String! stringValue: String booleanValue: Boolean } input ThreadsFilter { statuses: [ThreadStatus!] threadIds: [ID!] labelTypeIds: [ID!] priorities: [Int!] customerIds: [ID!] isAssigned: Boolean assignedToUser: [ID!] isMarkedAsSpam: Boolean supportEmailAddresses: [String!] customerGroupIdentifiers: [CustomerGroupIdentifier!] serviceLevelAgreements: ServiceLevelAgreementFilter tierIdentifiers: [TierIdentifierInput!] threadFields: [ThreadFieldFilter!] tenantIdentifiers: [TenantIdentifierInput!] companyIdentifiers: [CompanyIdentifierInput!] messageSource: [MessageSource!] } enum ServiceLevelAgreementType { FIRST_RESPONSE_TIME NEXT_RESPONSE_TIME } input ServiceLevelAgreementFilter { types: [ServiceLevelAgreementType!] statuses: [ServiceLevelAgreementStatus!] } enum ThreadsSortField { STATUS_CHANGED_AT CREATED_AT CLOSEST_TO_BREACH_SLA } input ThreadsSort { field: ThreadsSortField! direction: SortDirection! } type ThreadConnection { pageInfo: PageInfo! edges: [ThreadEdge!]! totalCount: Int! } type ThreadEdge { cursor: String! node: Thread! } """An enum for why the mutation failed overall.""" enum MutationErrorType { """ Input validation failed, see the `fields` for details on why the input was invalid. """ VALIDATION """ The user is not authorized to do this mutation. See `message` for details on which permissions are missing. """ FORBIDDEN """ An unknown internal server error occurred. Retry the mutation and if it persists, please email help@plain.com """ INTERNAL } """An enum specific to each field, explaining why validation failed.""" enum MutationFieldErrorType { """ The field was provided, but didn't pass the requirements of the field. See `message` for details on why. """ VALIDATION """ The field is required to be provided. String inputs may be trimmed and checked for emptiness. """ REQUIRED """The input field referenced an entity that wasn't found.""" NOT_FOUND } """ A type indicating an error has occurred with a specific field in the input. """ type MutationFieldError { """The name of the field for which the error happened.""" field: String! """ An English technical description of the error. This error is usually meant to be read by a developer and not an end user. """ message: String! """ The type of the error. Can be used to display a user friendly error message. """ type: MutationFieldErrorType! } """A type indicating an error has occurred while making a mutation.""" type MutationError { """ An English technical description of the error. This error is usually meant to be read by a developer and not an end user. """ message: String! """ The type of error. Can be used to display a user friendly error message. """ type: MutationErrorType! """ A fixed error code that can be used to handle this error, see https://www.plain.com/docs/graphql-api/error-codes for a description of each code. """ code: String! """The array of fields that are impacted by this error.""" fields: [MutationFieldError!]! } input StringInput { value: String! } input IntInput { value: Int! } input BooleanInput { value: Boolean! } input OptionalStringInput { value: String } input OptionalBooleanInput { value: Boolean } input CreateUserAccountInput { fullName: String! publicName: String! marketingConsent: Boolean } type CreateUserAccountOutput { userAccount: UserAccount error: MutationError } input CreateWorkspaceInput { name: String! publicName: String! } type CreateWorkspaceOutput { workspace: Workspace error: MutationError } input InviteUserToWorkspaceInput { email: String! roleIds: [ID!] @deprecated(reason: "Use roleKey instead.") roleKey: RoleKey usingBillingRotaSeat: BooleanInput } type InviteUserToWorkspaceOutput { invite: WorkspaceInvite error: MutationError } input AcceptWorkspaceInviteInput { inviteId: ID! } type AcceptWorkspaceInviteOutput { invite: WorkspaceInvite error: MutationError } input DeleteWorkspaceInviteInput { inviteId: ID! } type DeleteWorkspaceInviteOutput { invite: WorkspaceInvite error: MutationError } input AssignRolesToUserInput { userId: ID! roleIds: [ID!] @deprecated(reason: "Use roleKey instead.") roleKey: RoleKey usingBillingRotaSeat: BooleanInput } type AssignRolesToUserOutput { error: MutationError } input CreateLabelTypeInput { name: String! icon: String } type CreateLabelTypeOutput { labelType: LabelType error: MutationError } input ArchiveLabelTypeInput { labelTypeId: ID! } type ArchiveLabelTypeOutput { labelType: LabelType error: MutationError } input UnarchiveLabelTypeInput { labelTypeId: ID! } type UnarchiveLabelTypeOutput { labelType: LabelType error: MutationError } input UpdateLabelTypeInput { labelTypeId: ID! name: StringInput icon: OptionalStringInput } type UpdateLabelTypeOutput { labelType: LabelType error: MutationError } input AddLabelsInput { labelTypeIds: [ID!]! threadId: ID! } type AddLabelsOutput { labels: [Label!]! error: MutationError } input RemoveLabelsInput { labelIds: [ID!]! } type RemoveLabelsOutput { error: MutationError } input DependsOnThreadFieldInput { threadFieldSchemaId: ID! threadFieldSchemaValue: String! } input OptionalDependsOnThreadFieldInput { value: DependsOnThreadFieldInput } input CreateThreadFieldSchemaInput { label: String! key: String! description: String! order: Int! type: ThreadFieldSchemaType! enumValues: [String!]! isRequired: Boolean! defaultStringValue: String defaultBooleanValue: Boolean isAiAutoFillEnabled: Boolean! dependsOnThreadField: DependsOnThreadFieldInput } type CreateThreadFieldSchemaOutput { threadFieldSchema: ThreadFieldSchema error: MutationError } input UpdateThreadFieldSchemaInput { threadFieldSchemaId: ID! label: StringInput description: StringInput order: Int enumValues: [String!] isRequired: Boolean defaultStringValue: OptionalStringInput defaultBooleanValue: OptionalBooleanInput isAiAutoFillEnabled: Boolean dependsOnThreadField: OptionalDependsOnThreadFieldInput } type UpdateThreadFieldSchemaOutput { threadFieldSchema: ThreadFieldSchema error: MutationError } input DeleteThreadFieldSchemaInput { threadFieldSchemaId: ID! } type DeleteThreadFieldSchemaOutput { error: MutationError } input ThreadFieldSchemaOrderInput { threadFieldSchemaId: ID! order: Int! } input ReorderThreadFieldSchemasInput { threadFieldSchemaOrders: [ThreadFieldSchemaOrderInput!]! } type ReorderThreadFieldSchemasOutput { threadFieldSchemas: [ThreadFieldSchema!] error: MutationError } input UpsertThreadFieldIdentifier { threadId: ID! key: String! } input UpsertThreadFieldInput { identifier: UpsertThreadFieldIdentifier! type: ThreadFieldSchemaType! stringValue: String booleanValue: Boolean } input CreateThreadFieldOnThreadInput { key: String! type: ThreadFieldSchemaType! stringValue: String booleanValue: Boolean } type UpsertThreadFieldOutput { threadField: ThreadField result: UpsertResult error: MutationError } type BulkUpsertThreadFieldResult { threadField: ThreadField result: UpsertResult } input BulkUpsertThreadFieldsInput { inputs: [UpsertThreadFieldInput!]! } type BulkUpsertThreadFieldsOutput { results: [BulkUpsertThreadFieldResult!]! error: MutationError } input DeleteThreadFieldInput { threadFieldId: ID! } type DeleteThreadFieldOutput { error: MutationError } interface ThreadLink { id: ID! threadId: ID! createdAt: DateTime! createdBy: InternalActor! updatedAt: DateTime! updatedBy: InternalActor! } type LinearIssueThreadLink implements ThreadLink { id: ID! threadId: ID! createdAt: DateTime! createdBy: InternalActor! updatedAt: DateTime! updatedBy: InternalActor! linearIssueId: ID! linearIssueUrl: String! } input LinearIssueThreadLinkInput { linearIssueId: ID! linearIssueUrl: String! } input CreateThreadLinkInput { threadId: ID! linearIssue: LinearIssueThreadLinkInput } type CreateThreadLinkOutput { threadLink: ThreadLink error: MutationError } input DeleteThreadLinkInput { threadLinkId: ID! } type DeleteThreadLinkOutput { error: MutationError } type ThreadLinkEdge { cursor: String! node: ThreadLink! } type ThreadLinkConnection { edges: [ThreadLinkEdge!]! pageInfo: PageInfo! } input CreateNoteInput { customerId: ID! threadId: ID text: String! markdown: String } type CreateNoteOutput { note: Note error: MutationError } input DeleteNoteInput { noteId: ID! } type DeleteNoteOutput { note: Note error: MutationError } input CreateSnippetInput { name: String! text: String! markdown: String } type CreateSnippetOutput { snippet: Snippet error: MutationError } input DeleteSnippetInput { snippetId: ID! } type DeleteSnippetOutput { snippet: Snippet error: MutationError } input UpdateSnippetInput { snippetId: ID! name: StringInput text: StringInput markdown: StringInput } type UpdateSnippetOutput { snippet: Snippet error: MutationError } input SendChatInput { customerId: ID! text: String attachmentIds: [ID!] threadId: ID } type SendChatOutput { chat: Chat error: MutationError } input SendCustomerChatInput { customerId: ID! text: String attachmentIds: [ID!] threadId: ID! } type SendCustomerChatOutput { chat: Chat error: MutationError } input ChangeUserStatusInput { userId: ID! status: UserStatus! } type ChangeUserStatusOutput { user: User error: MutationError } input UpdateWorkspaceInput { publicName: StringInput name: StringInput } type UpdateWorkspaceOutput { workspace: Workspace error: MutationError } input DeleteUserInput { userId: ID! } type DeleteUserOutput { error: MutationError } type DnsRecord { type: String! name: String! value: String! isVerified: Boolean! verifiedAt: DateTime lastCheckedAt: DateTime } type WorkspaceEmailDomainSettings { domainName: String! supportEmailAddress: String! """ The list of alternate email addresses that can be used to send emails to and from the workspace. Limited to 5. e.g. [info@plain.com, help@plain.com]. """ alternateSupportEmailAddresses: [String!]! isForwardingConfigured: Boolean! inboundForwardingEmail: String! isDomainConfigured: Boolean! dkimDnsRecord: DnsRecord! returnPathDnsRecord: DnsRecord! } type WorkspaceEmailSettings { isEnabled: Boolean! workspaceEmailDomainSettings: WorkspaceEmailDomainSettings bccEmail: String } input AddWorkspaceAlternateSupportEmailAddressInput { alternateSupportEmailAddress: String! } type AddWorkspaceAlternateSupportEmailAddressOutput { workspaceEmailDomainSettings: WorkspaceEmailDomainSettings error: MutationError } input RemoveWorkspaceAlternateSupportEmailAddressInput { alternateSupportEmailAddress: String! } type RemoveWorkspaceAlternateSupportEmailAddressOutput { workspaceEmailDomainSettings: WorkspaceEmailDomainSettings error: MutationError } input CreateWorkspaceEmailDomainSettingsInput { supportEmailAddress: String! } type CreateWorkspaceEmailDomainSettingsOutput { workspaceEmailDomainSettings: WorkspaceEmailDomainSettings error: MutationError } type DeleteWorkspaceEmailDomainSettingsOutput { error: MutationError } input VerifyWorkspaceEmailForwardingSettingsInput { isForwardingConfigured: Boolean! } type VerifyWorkspaceEmailForwardingSettingsOutput { workspaceEmailDomainSettings: WorkspaceEmailDomainSettings error: MutationError } type VerifyWorkspaceEmailDnsSettingsOutput { workspaceEmailDomainSettings: WorkspaceEmailDomainSettings error: MutationError } input UpdateWorkspaceEmailSettingsInput { isEnabled: Boolean! } type UpdateWorkspaceEmailSettingsOutput { workspaceEmailSettings: WorkspaceEmailSettings error: MutationError } type WorkspaceChatSettings { isEnabled: Boolean! } input EmailParticipantInput { name: String email: String! } input SendNewEmailInput { customerId: ID! subject: String! textContent: String! markdownContent: String attachmentIds: [ID!] additionalRecipients: [EmailParticipantInput!] hiddenRecipients: [EmailParticipantInput!] """ Optional field for alternate from email address. If provided, it will be used as the from address in the email. It must match one of the workspace support email addresses (default or alternate). """ fromAlternateSupportEmail: EmailParticipantInput """ If provided this will add the new email to an existing thread. If not provided, a new thread will be created. """ threadId: ID } input SendBulkEmailInput { threadIds: [ID!]! textContent: String! markdownContent: String } input ReplyToEmailInput { customerId: ID! inReplyToEmailId: ID! textContent: String! markdownContent: String attachmentIds: [ID!] additionalRecipients: [EmailParticipantInput!] hiddenRecipients: [EmailParticipantInput!] """ Optional field for alternate from email address. If provided, it will be used as the from address in the email. It must match one of the workspace support email addresses (default or alternate). """ fromAlternateSupportEmail: EmailParticipantInput } type Email { id: ID! thread: Thread customer: Customer! inReplyToEmailId: ID from: EmailParticipant! to: EmailParticipant! subject: String textContent: String markdownContent: String attachments: [Attachment!]! additionalRecipients: [EmailParticipant!]! hiddenRecipients: [EmailParticipant!]! createdAt: DateTime! createdBy: Actor! updatedAt: DateTime! updatedBy: Actor! } type SendNewEmailOutput { email: Email error: MutationError } type ReplyToEmailOutput { email: Email error: MutationError } type SendBulkEmailOutput { error: MutationError } input CreateEmailPreviewUrlInput { emailId: ID! customerId: ID! } type EmailPreviewUrl { previewUrl: String! expiresAt: DateTime! } type CreateEmailPreviewUrlOutput { emailPreviewUrl: EmailPreviewUrl error: MutationError } input CreateAttachmentDownloadUrlInput { attachmentId: ID! } type AttachmentDownloadUrl { attachment: Attachment! downloadUrl: String! expiresAt: DateTime! } enum AttachmentVirusScanResult { """The attachment is clean and safe to download.""" CLEAN """The attachment is infected and should not be downloaded.""" INFECTED """The virus scan failed.""" FAILED """The virus scan is still pending.""" PENDING } type CreateAttachmentDownloadUrlOutput { attachmentDownloadUrl: AttachmentDownloadUrl """ The result of the virus scan on this attachment. If this is null, it means that your workspace does not have virus scan checks enabled. """ attachmentVirusScanResult: AttachmentVirusScanResult error: MutationError } enum AttachmentType { EMAIL CUSTOM_TIMELINE_ENTRY CHAT SLACK THREAD_DISCUSSION MS_TEAMS } input CreateAttachmentUploadUrlInput { customerId: ID! fileName: String! fileSizeBytes: Int! attachmentType: AttachmentType! } type UploadFormData { key: String! value: String! } type AttachmentUploadUrl { attachment: Attachment! uploadFormUrl: String! uploadFormData: [UploadFormData!]! expiresAt: DateTime! } type CreateAttachmentUploadUrlOutput { attachmentUploadUrl: AttachmentUploadUrl error: MutationError } input ComponentCopyButtonInput { copyButtonValue: String! copyButtonTooltipLabel: String } input ComponentBadgeInput { badgeLabel: String! badgeColor: ComponentBadgeColor } input ComponentRowInput { rowMainContent: [ComponentRowContentInput!]! rowAsideContent: [ComponentRowContentInput!]! } input ComponentContainerInput { containerContent: [ComponentContainerContentInput!]! } input ComponentContainerContentInput { componentText: ComponentTextInput componentPlainText: ComponentPlainTextInput componentDivider: ComponentDividerInput componentLinkButton: ComponentLinkButtonInput componentSpacer: ComponentSpacerInput componentBadge: ComponentBadgeInput componentCopyButton: ComponentCopyButtonInput componentRow: ComponentRowInput } input ComponentRowContentInput { componentText: ComponentTextInput componentPlainText: ComponentPlainTextInput componentDivider: ComponentDividerInput componentLinkButton: ComponentLinkButtonInput componentSpacer: ComponentSpacerInput componentBadge: ComponentBadgeInput componentCopyButton: ComponentCopyButtonInput } input ComponentTextInput { textSize: ComponentTextSize textColor: ComponentTextColor text: String! color: ComponentTextColor @deprecated(reason: "use textColor instead") size: ComponentTextSize @deprecated(reason: "use textSize instead") } input ComponentPlainTextInput { plainTextSize: ComponentPlainTextSize plainTextColor: ComponentPlainTextColor plainText: String! } input ComponentDividerInput { dividerSpacingSize: ComponentDividerSpacingSize spacingSize: ComponentDividerSpacingSize @deprecated(reason: "use dividerSpacingSize instead") } input ComponentLinkButtonInput { """ Required input, will be made required after deprecated fields are removed. """ linkButtonUrl: String """ Required input, will be made required after deprecated fields are removed. """ linkButtonLabel: String url: String @deprecated(reason: "use linkButtonUrl instead") label: String @deprecated(reason: "use linkButtonLabel instead") } input ComponentSpacerInput { """ Required input, will be made required after deprecated fields are removed. """ spacerSize: ComponentSpacerSize size: ComponentSpacerSize @deprecated(reason: "user spacerSize instead") } input ComponentInput { componentText: ComponentTextInput componentPlainText: ComponentPlainTextInput componentDivider: ComponentDividerInput componentLinkButton: ComponentLinkButtonInput componentSpacer: ComponentSpacerInput componentBadge: ComponentBadgeInput componentCopyButton: ComponentCopyButtonInput componentRow: ComponentRowInput componentContainer: ComponentContainerInput } input EventComponentInput { componentText: ComponentTextInput componentPlainText: ComponentPlainTextInput componentDivider: ComponentDividerInput componentLinkButton: ComponentLinkButtonInput componentSpacer: ComponentSpacerInput componentBadge: ComponentBadgeInput componentCopyButton: ComponentCopyButtonInput componentRow: ComponentRowInput } enum UpsertResult { UPDATED CREATED NOOP } input EmailAddressInput { email: String! isVerified: Boolean! } input UpsertCustomerIdentifierInput { externalId: ID emailAddress: String customerId: ID } input UpsertCustomerOnCreateInput { externalId: ID fullName: String! shortName: String email: EmailAddressInput! customerGroupIdentifiers: [CustomerGroupIdentifier!] tenantIdentifiers: [TenantIdentifierInput!] } input UpsertCustomerOnUpdateInput { externalId: OptionalStringInput fullName: StringInput shortName: OptionalStringInput email: EmailAddressInput } input UpsertCustomerInput { identifier: UpsertCustomerIdentifierInput! onCreate: UpsertCustomerOnCreateInput! onUpdate: UpsertCustomerOnUpdateInput! } type UpsertCustomerOutput { result: UpsertResult customer: Customer error: MutationError } input CreateMachineUserInput { publicName: String! fullName: String! description: String } type CreateMachineUserOutput { machineUser: MachineUser error: MutationError } input DeleteMachineUserInput { machineUserId: ID! } type DeleteMachineUserOutput { machineUser: MachineUser error: MutationError } input UpdateMachineUserInput { machineUserId: ID! fullName: StringInput publicName: StringInput description: StringInput } type UpdateMachineUserOutput { machineUser: MachineUser error: MutationError } input CreateApiKeyInput { machineUserId: ID! description: String permissions: [String!]! } type CreateApiKeyOutput { apiKey: ApiKey apiKeySecret: String error: MutationError } input DeleteApiKeyInput { apiKeyId: ID! } type DeleteApiKeyOutput { apiKey: ApiKey error: MutationError } input DeleteCustomerInput { customerId: ID! } type DeleteCustomerOutput { error: MutationError } input CreateCustomerEventInput { """The customer id of the customer that the event is for.""" customerIdentifier: CustomerIdentifierInput! """ The external ID of this event. You can use this field to store your own unique identifier for this event. This must be unique. """ externalId: ID """The title of the event.""" title: String! """The components used to create the event's contents.""" components: [EventComponentInput!]! } type CreateCustomerEventOutput { customerEvent: CustomerEvent error: MutationError } input CreateThreadEventInput { """The thread id of the thread that the event is for.""" threadId: ID! """ The external ID of this event. You can use this field to store your own unique identifier for this event. This must be unique. """ externalId: ID """The title of the event.""" title: String! """The components used to create the event's contents.""" components: [EventComponentInput!]! } type CreateThreadEventOutput { threadEvent: ThreadEvent error: MutationError } enum AutoresponderMessageSource { EMAIL API SLACK } input AutoresponderConditionInput { tierId: ID isOutsideBusinessHours: Boolean supportEmailAddresses: [String!] } input CreateAutoresponderInput { name: String! order: Int! textContent: String! markdownContent: String isEnabled: Boolean! messageSources: [AutoresponderMessageSource!]! conditions: [AutoresponderConditionInput!]! responseDelaySeconds: Int } type CreateAutoresponderOutput { autoresponder: Autoresponder error: MutationError } input UpdateAutoresponderInput { autoresponderId: ID! name: StringInput order: IntInput textContent: StringInput markdownContent: OptionalStringInput isEnabled: BooleanInput messageSources: [AutoresponderMessageSource!] conditions: [AutoresponderConditionInput!] responseDelaySeconds: IntInput } type UpdateAutoresponderOutput { autoresponder: Autoresponder error: MutationError } input DeleteAutoresponderInput { autoresponderId: ID! } type DeleteAutoresponderOutput { autoresponder: Autoresponder error: MutationError } input AutoresponderOrderInput { autoresponderId: ID! order: Int! } input ReorderAutorespondersInput { autorespondersOrder: [AutoresponderOrderInput!]! } type ReorderAutorespondersOutput { autoresponders: [Autoresponder!] error: MutationError } type AutoresponderTierCondition { tierId: ID! } type AutoresponderBusinessHoursCondition { isOutsideBusinessHours: Boolean! } type AutoresponderSupportEmailsCondition { supportEmailAddresses: [String!]! } union AutoresponderCondition = AutoresponderTierCondition | AutoresponderBusinessHoursCondition | AutoresponderSupportEmailsCondition type Autoresponder { id: ID! name: String! order: Int! messageSources: [AutoresponderMessageSource!]! conditions: [AutoresponderCondition!]! textContent: String! markdownContent: String isEnabled: Boolean! responseDelaySeconds: Int! createdAt: DateTime! createdBy: InternalActor! updatedAt: DateTime! updatedBy: InternalActor! } type AutoresponderConnection { edges: [AutoresponderEdge!]! pageInfo: PageInfo! } type AutoresponderEdge { cursor: String! node: Autoresponder! } type Mutation { createUserAccount(input: CreateUserAccountInput!): CreateUserAccountOutput! changeUserStatus(input: ChangeUserStatusInput!): ChangeUserStatusOutput! createWorkspace(input: CreateWorkspaceInput!): CreateWorkspaceOutput! updateWorkspace(input: UpdateWorkspaceInput!): UpdateWorkspaceOutput! inviteUserToWorkspace(input: InviteUserToWorkspaceInput!): InviteUserToWorkspaceOutput! acceptWorkspaceInvite(input: AcceptWorkspaceInviteInput!): AcceptWorkspaceInviteOutput! deleteWorkspaceInvite(input: DeleteWorkspaceInviteInput!): DeleteWorkspaceInviteOutput! deleteUser(input: DeleteUserInput!): DeleteUserOutput! assignRolesToUser(input: AssignRolesToUserInput!): AssignRolesToUserOutput! createLabelType(input: CreateLabelTypeInput!): CreateLabelTypeOutput! archiveLabelType(input: ArchiveLabelTypeInput!): ArchiveLabelTypeOutput! unarchiveLabelType(input: UnarchiveLabelTypeInput!): UnarchiveLabelTypeOutput! updateLabelType(input: UpdateLabelTypeInput!): UpdateLabelTypeOutput! addLabels(input: AddLabelsInput!): AddLabelsOutput! removeLabels(input: RemoveLabelsInput!): RemoveLabelsOutput! createThreadLink(input: CreateThreadLinkInput!): CreateThreadLinkOutput! deleteThreadLink(input: DeleteThreadLinkInput!): DeleteThreadLinkOutput! createNote(input: CreateNoteInput!): CreateNoteOutput! deleteNote(input: DeleteNoteInput!): DeleteNoteOutput! createSnippet(input: CreateSnippetInput!): CreateSnippetOutput! deleteSnippet(input: DeleteSnippetInput!): DeleteSnippetOutput! updateSnippet(input: UpdateSnippetInput!): UpdateSnippetOutput! """Creates or updates a customer.""" upsertCustomer(input: UpsertCustomerInput!): UpsertCustomerOutput! """Changes the company of a customer.""" updateCustomerCompany(input: UpdateCustomerCompanyInput!): UpdateCustomerCompanyOutput! """ Deletes a customer and all of their data stored on Plain. This action cannot be reversed. """ deleteCustomer(input: DeleteCustomerInput!): DeleteCustomerOutput! """Marks a customer as spam.""" markCustomerAsSpam(input: MarkCustomerAsSpamInput!): MarkCustomerAsSpamOutput! """Removes the spam mark from a customer.""" unmarkCustomerAsSpam(input: UnmarkCustomerAsSpamInput!): UnmarkCustomerAsSpamOutput! """Creates or updates a customer group.""" upsertCustomerGroup(input: UpsertCustomerGroupInput!): UpsertCustomerGroupOutput! """Create a new customer group.""" createCustomerGroup(input: CreateCustomerGroupInput!): CreateCustomerGroupOutput! """Update a customer group.""" updateCustomerGroup(input: UpdateCustomerGroupInput!): UpdateCustomerGroupOutput! """Delete a customer group by ID.""" deleteCustomerGroup(input: DeleteCustomerGroupInput!): DeleteCustomerGroupOutput! """Add a customer to a customer group.""" addCustomerToCustomerGroups(input: AddCustomerToCustomerGroupsInput!): AddCustomerToCustomerGroupsOutput! """Remove a customer from a customer group.""" removeCustomerFromCustomerGroups(input: RemoveCustomerFromCustomerGroupsInput!): RemoveCustomerFromCustomerGroupsOutput! createThreadFieldSchema(input: CreateThreadFieldSchemaInput!): CreateThreadFieldSchemaOutput! updateThreadFieldSchema(input: UpdateThreadFieldSchemaInput!): UpdateThreadFieldSchemaOutput! deleteThreadFieldSchema(input: DeleteThreadFieldSchemaInput!): DeleteThreadFieldSchemaOutput! reorderThreadFieldSchemas(input: ReorderThreadFieldSchemasInput!): ReorderThreadFieldSchemasOutput! upsertThreadField(input: UpsertThreadFieldInput!): UpsertThreadFieldOutput! bulkUpsertThreadFields(input: BulkUpsertThreadFieldsInput!): BulkUpsertThreadFieldsOutput! deleteThreadField(input: DeleteThreadFieldInput!): DeleteThreadFieldOutput! sendChat(input: SendChatInput!): SendChatOutput! sendCustomerChat(input: SendCustomerChatInput!): SendCustomerChatOutput! sendMSTeamsMessage(input: SendMSTeamsMessageInput!): SendMSTeamsMessageOutput! sendSlackMessage(input: SendSlackMessageInput!): SendSlackMessageOutput! shareThreadToUserInSlack(input: ShareThreadToUserInSlackInput!): ShareThreadToUserInSlackOutput! """Adds or removes a reaction from a slack message timeline entry.""" toggleSlackMessageReaction(input: ToggleSlackMessageReactionInput!): ToggleSlackMessageReactionOutput! forkThread(input: ForkThreadInput!): ForkThreadOutput! updateConnectedSlackChannel(input: UpdateConnectedSlackChannelInput!): UpdateConnectedSlackChannelOutput! """Create a new customer event.""" createCustomerEvent(input: CreateCustomerEventInput!): CreateCustomerEventOutput! """Create a new thread event.""" createThreadEvent(input: CreateThreadEventInput!): CreateThreadEventOutput! createWorkspaceEmailDomainSettings(input: CreateWorkspaceEmailDomainSettingsInput!): CreateWorkspaceEmailDomainSettingsOutput! deleteWorkspaceEmailDomainSettings: DeleteWorkspaceEmailDomainSettingsOutput! verifyWorkspaceEmailForwardingSettings(input: VerifyWorkspaceEmailForwardingSettingsInput!): VerifyWorkspaceEmailForwardingSettingsOutput! verifyWorkspaceEmailDnsSettings: VerifyWorkspaceEmailDnsSettingsOutput! updateWorkspaceEmailSettings(input: UpdateWorkspaceEmailSettingsInput!): UpdateWorkspaceEmailSettingsOutput! addWorkspaceAlternateSupportEmailAddress(input: AddWorkspaceAlternateSupportEmailAddressInput!): AddWorkspaceAlternateSupportEmailAddressOutput! removeWorkspaceAlternateSupportEmailAddress(input: RemoveWorkspaceAlternateSupportEmailAddressInput!): RemoveWorkspaceAlternateSupportEmailAddressOutput! sendNewEmail(input: SendNewEmailInput!): SendNewEmailOutput! replyToEmail(input: ReplyToEmailInput!): ReplyToEmailOutput! createEmailPreviewUrl(input: CreateEmailPreviewUrlInput!): CreateEmailPreviewUrlOutput! sendBulkEmail(input: SendBulkEmailInput!): SendBulkEmailOutput! createAttachmentDownloadUrl(input: CreateAttachmentDownloadUrlInput!): CreateAttachmentDownloadUrlOutput! createAttachmentUploadUrl(input: CreateAttachmentUploadUrlInput!): CreateAttachmentUploadUrlOutput! createMachineUser(input: CreateMachineUserInput!): CreateMachineUserOutput! deleteMachineUser(input: DeleteMachineUserInput!): DeleteMachineUserOutput! updateMachineUser(input: UpdateMachineUserInput!): UpdateMachineUserOutput! createApiKey(input: CreateApiKeyInput!): CreateApiKeyOutput! deleteApiKey(input: DeleteApiKeyInput!): DeleteApiKeyOutput! createMySlackIntegration(input: CreateMySlackIntegrationInput!): CreateMySlackIntegrationOutput! deleteMySlackIntegration: DeleteMySlackIntegrationOutput! createUserAuthSlackIntegration(input: CreateUserAuthSlackIntegrationInput!): CreateUserAuthSlackIntegrationOutput! deleteUserAuthSlackIntegration(input: DeleteUserAuthSlackIntegrationInput!): DeleteUserAuthSlackIntegrationOutput! createWorkspaceSlackIntegration(input: CreateWorkspaceSlackIntegrationInput!): CreateWorkspaceSlackIntegrationOutput! deleteWorkspaceSlackIntegration(input: DeleteWorkspaceSlackIntegrationInput!): DeleteWorkspaceSlackIntegrationOutput! createWorkspaceSlackChannelIntegration(input: CreateWorkspaceSlackChannelIntegrationInput!): CreateWorkspaceSlackChannelIntegrationOutput! deleteWorkspaceSlackChannelIntegration(input: DeleteWorkspaceSlackChannelIntegrationInput!): DeleteWorkspaceSlackChannelIntegrationOutput! createWorkspaceDiscordIntegration(input: CreateWorkspaceDiscordIntegrationInput!): CreateWorkspaceDiscordIntegrationOutput! deleteWorkspaceDiscordIntegration(input: DeleteWorkspaceDiscordIntegrationInput!): DeleteWorkspaceDiscordIntegrationOutput! createMyLinearIntegration(input: CreateMyLinearIntegrationInput!): CreateMyLinearIntegrationOutput! deleteMyLinearIntegration: DeleteMyLinearIntegrationOutput! createMyMSTeamsIntegration(input: CreateMyMSTeamsIntegrationInput!): CreateMyMSTeamsIntegrationOutput! deleteMyMSTeamsIntegration: DeleteMyMSTeamsIntegrationOutput! createWorkspaceMSTeamsIntegration(input: CreateWorkspaceMSTeamsIntegrationInput!): CreateWorkspaceMSTeamsIntegrationOutput! deleteWorkspaceMSTeamsIntegration(input: DeleteWorkspaceMSTeamsIntegrationInput!): DeleteWorkspaceMSTeamsIntegrationOutput! """Updates a setting.""" updateSetting(input: UpdateSettingInput!): UpdateSettingOutput! """ Creates a customer card config. A maximum of 25 card configs can be created. """ createCustomerCardConfig(input: CreateCustomerCardConfigInput!): CreateCustomerCardConfigOutput! """Partially updates a customer card config.""" updateCustomerCardConfig(input: UpdateCustomerCardConfigInput!): UpdateCustomerCardConfigOutput! """Deletes a customer card config.""" deleteCustomerCardConfig(input: DeleteCustomerCardConfigInput!): DeleteCustomerCardConfigOutput! """ Reorders customer card configs. The input can be a partial input and in that case not all configs will be reordered. For example this allows two configs to be swapped with each other. Note: Duplicate orders are allowed by the API. """ reorderCustomerCardConfigs(input: ReorderCustomerCardConfigsInput!): ReorderCustomerCardConfigsOutput! """ Reloads a customer card for a customer. Will discard whatever is in the cache and reload it from the configured API URL. """ reloadCustomerCardInstance(input: ReloadCustomerCardInstanceInput!): ReloadCustomerCardInstanceOutput! """Creates a webhook target.""" createWebhookTarget(input: CreateWebhookTargetInput!): CreateWebhookTargetOutput! """Updates a webhook target.""" updateWebhookTarget(input: UpdateWebhookTargetInput!): UpdateWebhookTargetOutput! """Deletes a webhook target.""" deleteWebhookTarget(input: DeleteWebhookTargetInput!): DeleteWebhookTargetOutput! createThread(input: CreateThreadInput!): CreateThreadOutput! assignThread(input: AssignThreadInput!): AssignThreadOutput! unassignThread(input: UnassignThreadInput!): UnassignThreadOutput! snoozeThread(input: SnoozeThreadInput!): SnoozeThreadOutput! markThreadAsDone(input: MarkThreadAsDoneInput!): MarkThreadAsDoneOutput! markThreadAsTodo(input: MarkThreadAsTodoInput!): MarkThreadAsTodoOutput! changeThreadPriority(input: ChangeThreadPriorityInput!): ChangeThreadPriorityOutput! updateThreadTitle(input: UpdateThreadTitleInput!): UpdateThreadTitleOutput! updateThreadTenant(input: UpdateThreadTenantInput!): UpdateThreadTenantOutput! createThreadDiscussion(input: CreateThreadDiscussionInput!): CreateThreadDiscussionOutput! sendThreadDiscussionMessage(input: SendThreadDiscussionMessageInput!): SendThreadDiscussionMessageOutput! markThreadDiscussionAsResolved(input: MarkThreadDiscussionAsResolvedInput!): MarkThreadDiscussionAsResolvedOutput! """ Reply to the last message in a thread. This mutation supports replying to threads where the last message is a Slack message, an email or a form submission. If the thread is empty, it will send an email to the customer. """ replyToThread(input: ReplyToThreadInput!): ReplyToThreadOutput! upsertMyEmailSignature(input: UpsertMyEmailSignatureInput!): UpsertMyEmailSignatureOutput! createAutoresponder(input: CreateAutoresponderInput!): CreateAutoresponderOutput! updateAutoresponder(input: UpdateAutoresponderInput!): UpdateAutoresponderOutput! deleteAutoresponder(input: DeleteAutoresponderInput!): DeleteAutoresponderOutput! reorderAutoresponders(input: ReorderAutorespondersInput!): ReorderAutorespondersOutput! upsertTenant(input: UpsertTenantInput!): UpsertTenantOutput! addCustomerToTenants(input: AddCustomerToTenantsInput!): AddCustomerToTenantsOutput! removeCustomerFromTenants(input: RemoveCustomerFromTenantsInput!): RemoveCustomerFromTenantsOutput! setCustomerTenants(input: SetCustomerTenantsInput!): SetCustomerTenantsOutput! upsertCompany(input: UpsertCompanyInput!): UpsertCompanyOutput! startServiceAuthorization(input: StartServiceAuthorizationInput!): StartServiceAuthorizationOutput! completeServiceAuthorization(input: CompleteServiceAuthorizationInput!): CompleteServiceAuthorizationOutput! deleteServiceAuthorization(input: DeleteServiceAuthorizationInput!): DeleteServiceAuthorizationOutput! createTier(input: CreateTierInput!): CreateTierOutput! updateTier(input: UpdateTierInput!): UpdateTierOutput! deleteTier(input: DeleteTierInput!): DeleteTierOutput! createServiceLevelAgreement(input: CreateServiceLevelAgreementInput!): CreateServiceLevelAgreementOutput! updateServiceLevelAgreement(input: UpdateServiceLevelAgreementInput!): UpdateServiceLevelAgreementOutput! deleteServiceLevelAgreement(input: DeleteServiceLevelAgreementInput!): DeleteServiceLevelAgreementOutput! addMembersToTier(input: AddMembersToTierInput!): AddMembersToTierOutput! removeMembersFromTier(input: RemoveMembersFromTierInput!): RemoveMembersFromTierOutput! updateCompanyTier(input: UpdateCompanyTierInput!): UpdateCompanyTierOutput! updateTenantTier(input: UpdateTenantTierInput!): UpdateTenantTierOutput! upsertBusinessHours(input: UpsertBusinessHoursInput!): UpsertBusinessHoursOutput! deleteBusinessHours: DeleteBusinessHoursOutput! createCheckoutSession(input: CreateCheckoutSessionInput!): CreateCheckoutSessionOutput! createBillingPortalSession: CreateBillingPortalSessionOutput! calculateRoleChangeCost(input: CalculateRoleChangeCostInput!): CalculateRoleChangeCostOutput! addUserToActiveBillingRota(input: AddUserToActiveBillingRotaInput!): AddUserToActiveBillingRotaOutput! removeUserFromActiveBillingRota(input: RemoveUserFromActiveBillingRotaInput!): RemoveUserFromActiveBillingRotaOutput! updateActiveBillingRota(input: UpdateActiveBillingRotaInput!): UpdateActiveBillingRotaOutput! changeBillingPlan(input: ChangeBillingPlanInput!): ChangeBillingPlanOutput! previewBillingPlanChange(input: PreviewBillingPlanChangeInput!): PreviewBillingPlanChangeOutput! regenerateWorkspaceHmac: RegenerateWorkspaceHmacOutput! } input CreateThreadDiscussionInput { threadId: ID! connectedSlackChannelId: ID! markdownContent: String! } type CreateThreadDiscussionOutput { threadDiscussion: ThreadDiscussion error: MutationError } input SendThreadDiscussionMessageInput { threadDiscussionId: ID! markdownContent: String! attachmentIds: [ID!] } type SendThreadDiscussionMessageOutput { threadDiscussionMessage: ThreadDiscussionMessage error: MutationError } input MarkThreadDiscussionAsResolvedInput { threadDiscussionId: ID! } type MarkThreadDiscussionAsResolvedOutput { error: MutationError } input UpdateCompanyTierInput { tierIdentifier: TierIdentifierInput companyIdentifier: CompanyIdentifierInput! } type UpdateCompanyTierOutput { companyTierMembership: CompanyTierMembership error: MutationError } input UpdateTenantTierInput { tierIdentifier: TierIdentifierInput tenantIdentifier: TenantIdentifierInput! } type UpdateTenantTierOutput { tenantTierMembership: TenantTierMembership error: MutationError } input AddMembersToTierInput { tierIdentifier: TierIdentifierInput! memberIdentifiers: [TierMemberIdentifierInput!]! } type AddMembersToTierOutput { memberships: [TierMembership!]! error: MutationError } input RemoveMembersFromTierInput { memberIdentifiers: [TierMemberIdentifierInput!]! } type RemoveMembersFromTierOutput { memberships: [TierMembership!]! error: MutationError } input CreateTierInput { """The name of this tier.""" name: String! """ The external ID of this tier. You can use this field to store your own unique identifier for this tier. This must be unique in your workspace. """ externalId: String! """ The color to assign to this tier, given by its hex code (e.g. #FABADA). This color is used in Plain's UI to represent this tier. """ color: String! """ Any thread created in this tier will have this priority by default, unless a different priority is specified while creating it. If not provided, it defaults to 2 (normal priority). """ defaultThreadPriority: Int memberIdentifiers: [TierMemberIdentifierInput!]! """ If set to true, this tier will be applied to all threads that do not match any other tier. Only one tier can be the default tier. Default: false """ isDefault: Boolean } input ServiceLevelAgreementInput { """Set this to configure the firt response time SLA.""" firstResponseTimeMinutes: Int """Set this to configure an SLA for next responses.""" nextResponseTimeMinutes: Int """ This SLA can only be applied to a thread if it has one of these priority values. If not provided, it defaults to all priorities (0, 1, 2 and 3). """ threadPriorityFilter: [Int!] """ If true, the SLA will only be tracked during your workspace's business hours. If false, the SLA will tracked 24/7. """ useBusinessHoursOnly: Boolean! """ The actions to take when the SLA is about to breach and when it breaches. """ breachActions: [BreachActionInput!]! } input BreachActionInput { beforeBreachAction: BeforeBreachActionInput } input BeforeBreachActionInput { beforeBreachMinutes: Int! } input TierMemberIdentifierInput { companyId: ID tenantId: ID } type CreateTierOutput { tier: Tier error: MutationError } input UpdateTierInput { tierId: ID! name: StringInput externalId: OptionalStringInput color: StringInput defaultThreadPriority: IntInput isDefault: BooleanInput } type UpdateTierOutput { tier: Tier error: MutationError } input DeleteTierInput { tierId: ID! } type DeleteTierOutput { tier: Tier error: MutationError } input CreateServiceLevelAgreementInput { tierId: ID! serviceLevelAgreement: ServiceLevelAgreementInput! } type CreateServiceLevelAgreementOutput { serviceLevelAgreement: ServiceLevelAgreement error: MutationError } input IntArrayInput { value: [Int!]! } input UpdateServiceLevelAgreementInput { """The ID of the SLA to update.""" serviceLevelAgreementId: ID! """ This SLA will breach if it does not receive a first response within this many minutes. May only be provided if the service level agreement is a first response time SLA. """ firstResponseTimeMinutes: IntInput """ This SLA will breach if it does not receive a next response within this many minutes. May only be provided if the service level agreement is a next response time SLA. """ nextResponseTimeMinutes: IntInput """ This SLA can only be applied to a thread if it has one of these priority values. If not provided, it defaults to all priorities (0, 1, 2 and 3). """ threadPriorityFilter: IntArrayInput """ If true, the SLA will only be tracked during your workspace's business hours. If false, the SLA will tracked 24/7. """ useBusinessHoursOnly: BooleanInput """ The actions to take when the SLA is about to breach and when it breaches. """ breachActions: [BreachActionInput!] } type UpdateServiceLevelAgreementOutput { serviceLevelAgreement: ServiceLevelAgreement error: MutationError } input DeleteServiceLevelAgreementInput { serviceLevelAgreementId: ID! } type DeleteServiceLevelAgreementOutput { serviceLevelAgreement: ServiceLevelAgreement error: MutationError } input UpsertCompanyInput { identifier: CompanyIdentifierInput! name: String! domainName: String! } type UpsertCompanyOutput { company: Company result: UpsertResult error: MutationError } type Tenant { id: ID! name: String! externalId: String! url: String createdAt: DateTime! createdBy: InternalActor! updatedAt: DateTime! updatedBy: InternalActor! } input TenantIdentifierInput { tenantId: ID externalId: String } input UpsertTenantInput { identifier: TenantIdentifierInput! name: String! externalId: String! url: OptionalStringInput } type UpsertTenantOutput { tenant: Tenant result: UpsertResult error: MutationError } input CompanyIdentifierInput { """Plain's internal identifier for the company.""" companyId: ID """ The domain name of the company (e.g. plain.com). Alternatively, you can provide a full URL (e.g. https://www.plain.com) and we will do our best to extract the domain name. """ companyDomainName: String } input UpdateCustomerCompanyInput { """The identifier of the customer we want to update the company for.""" customerId: ID! """ The identifier of the company we want to update the customer with. Pass null if you want to remove the company from the customer. """ companyIdentifier: CompanyIdentifierInput } type UpdateCustomerCompanyOutput { customer: Customer error: MutationError } input SendMSTeamsMessageInput { threadId: ID! markdownContent: String attachmentIds: [ID!] } type SendMSTeamsMessageOutput { msTeamsMessage: MSTeamsMessage error: MutationError } input SendSlackMessageInput { threadId: ID! textContent: String @deprecated(reason: "Use markdownContent instead") markdownContent: String attachmentIds: [ID!] } type SendSlackMessageOutput { error: MutationError } input ShareThreadToUserInSlackInput { threadId: ID! userId: ID! } type ShareThreadToUserInSlackOutput { error: MutationError } input ToggleSlackMessageReactionInput { threadId: ID! timelineEntryId: ID! reactionName: String! } type ToggleSlackMessageReactionOutput { error: MutationError } input ForkThreadInput { threadId: ID! timelineEntryId: ID! } type ForkThreadOutput { thread: Thread error: MutationError } input CustomerImpersonationInput { customerIdentifier: CustomerIdentifierInput! } input ImpersonationInput { asCustomer: CustomerImpersonationInput! } input ReplyToThreadEmailChannelSpecificOptionsInput { additionalRecipients: [EmailParticipantInput!] hiddenRecipients: [EmailParticipantInput!] } input ReplyToThreadChannelSpecificOptionsInput { email: ReplyToThreadEmailChannelSpecificOptionsInput! } input ReplyToThreadInput { threadId: ID! textContent: String! markdownContent: String impersonation: ImpersonationInput channelSpecificOptions: ReplyToThreadChannelSpecificOptionsInput } type ReplyToThreadOutput { error: MutationError } """ Query to search for threads. The search term provided is used to match against different parts of the thread: - its title - its messages - the customer's name - the customer's email """ input ThreadsSearchQuery { """ The term to search for. It must be at least 2 characters long. The search is case-insensitive. """ term: String! } type ThreadSearchResult { thread: Thread! } type ThreadSearchResultEdge { cursor: String! node: ThreadSearchResult! } type ThreadSearchResultConnection { edges: [ThreadSearchResultEdge!]! pageInfo: PageInfo! } input UpsertMyEmailSignatureInput { text: String! markdown: String } type UpsertMyEmailSignatureOutput { emailSignature: EmailSignature result: UpsertResult error: MutationError } enum DoneStatusDetail { IGNORED DONE_MANUALLY_SET TIMER_EXPIRED } input MarkThreadAsDoneInput { threadId: ID! statusDetail: DoneStatusDetail } type MarkThreadAsDoneOutput { thread: Thread error: MutationError } enum TodoStatusDetail { CREATED IN_PROGRESS NEW_REPLY THREAD_LINK_UPDATED THREAD_DISCUSSION_RESOLVED } input MarkThreadAsTodoInput { threadId: ID! statusDetail: TodoStatusDetail } type MarkThreadAsTodoOutput { thread: Thread error: MutationError } input ChangeThreadPriorityInput { threadId: ID! priority: Int! } type ChangeThreadPriorityOutput { thread: Thread error: MutationError } input UpdateThreadTitleInput { threadId: ID! title: String! } type UpdateThreadTitleOutput { thread: Thread error: MutationError } enum SnoozeStatusDetail { WAITING_FOR_CUSTOMER WAITING_FOR_DURATION } input SnoozeThreadInput { threadId: ID! durationSeconds: Int statusDetail: SnoozeStatusDetail } type SnoozeThreadOutput { thread: Thread error: MutationError } input AssignThreadInput { threadId: ID! userId: ID machineUserId: ID } type AssignThreadOutput { thread: Thread error: MutationError } input UnassignThreadInput { threadId: ID! } type UnassignThreadOutput { thread: Thread error: MutationError } enum ThreadStatus { TODO SNOOZED DONE } type ThreadStatusDetailCreated { statusChangedAt: DateTime! createdAt: DateTime! } type ThreadStatusDetailNewReply { statusChangedAt: DateTime! newReplyAt: DateTime } type ThreadStatusDetailReplied { repliedAt: DateTime! @deprecated(reason: "ThreadStatusDetailReplied is no longer supported.") statusChangedAt: DateTime! @deprecated(reason: "ThreadStatusDetailReplied is no longer supported.") } type ThreadStatusDetailThreadLinkUpdated { statusChangedAt: DateTime! updatedAt: DateTime! @deprecated(reason: "Use statusChangedAt instead") linearIssueId: ID } type ThreadStatusDetailLinearUpdated { statusChangedAt: DateTime! @deprecated(reason: "ThreadStatusDetailLinearUpdated is no longer supported, query ThreadStatusDetailThreadLinkUpdated instead.") updatedAt: DateTime! @deprecated(reason: "ThreadStatusDetailLinearUpdated is no longer supported, query ThreadStatusDetailThreadLinkUpdated instead.") linearIssueId: ID! @deprecated(reason: "ThreadStatusDetailLinearUpdated is no longer supported, query ThreadStatusDetailThreadLinkUpdated instead.") } type ThreadStatusDetailInProgress { statusChangedAt: DateTime! } type ThreadStatusDetailThreadDiscussionResolved { statusChangedAt: DateTime! threadDiscussionId: ID } type ThreadStatusDetailUnsnoozed { snoozedAt: DateTime! @deprecated(reason: "ThreadStatusDetailUnsnoozed is no longer supported.") statusChangedAt: DateTime! @deprecated(reason: "ThreadStatusDetailUnsnoozed is no longer supported.") } type ThreadStatusDetailSnoozed { snoozedAt: DateTime! @deprecated(reason: "ThreadStatusDetailSnoozed is no longer supported.") snoozedUntil: DateTime! @deprecated(reason: "ThreadStatusDetailSnoozed is no longer supported.") statusChangedAt: DateTime! @deprecated(reason: "ThreadStatusDetailSnoozed is no longer supported.") } type ThreadStatusDetailWaitingForDuration { statusChangedAt: DateTime! waitingUntil: DateTime! } type ThreadStatusDetailWaitingForCustomer { statusChangedAt: DateTime! } type ThreadStatusDetailIgnored { statusChangedAt: DateTime! } type ThreadStatusDetailDoneManuallySet { statusChangedAt: DateTime! } type ThreadStatusDetailDoneAutomaticallySet { statusChangedAt: DateTime! afterSeconds: Int } union ThreadStatusDetail = ThreadStatusDetailCreated | ThreadStatusDetailSnoozed | ThreadStatusDetailUnsnoozed | ThreadStatusDetailNewReply | ThreadStatusDetailReplied | ThreadStatusDetailLinearUpdated | ThreadStatusDetailInProgress | ThreadStatusDetailWaitingForCustomer | ThreadStatusDetailWaitingForDuration | ThreadStatusDetailThreadLinkUpdated | ThreadStatusDetailIgnored | ThreadStatusDetailDoneManuallySet | ThreadStatusDetailDoneAutomaticallySet | ThreadStatusDetailThreadDiscussionResolved enum MessageSource { CHAT EMAIL API SLACK MS_TEAMS } type ThreadMessageInfo { """The datetime when the last message was received.""" timestamp: DateTime! """The source through which the message came through.""" messageSource: MessageSource! } union ThreadAssignee = User | MachineUser | System type SlackThreadExternalChannelDetails { slackChannelId: String! slackChannelName: String! slackTeamId: String! slackTeamName: String! } union ThreadExternalChannelDetails = SlackThreadExternalChannelDetails type SlackThreadChannelDetails { slackChannelId: String! slackChannelName: String! slackTeamId: String! slackTeamName: String! } union ThreadChannelDetails = SlackThreadChannelDetails enum ThreadChannel { EMAIL SLACK CHAT API MS_TEAMS } """ A thread represents a conversation with a customer, around a specific topic. """ type Thread { """The unique identifier of the thread.""" id: ID! """The customer involved in this thread.""" customer: Customer! """ The title of this thread, which allows to quickly identify what it is about. """ title: String! """The description of this thread.""" description: String """ The preview text of the thread reflects the current state of the thread. As such, it might be updated when new activity happens in the thread. """ previewText: String """ The priority of the thread. Valid values are 0, 1, 2, 3, from most to least urgent. """ priority: Int! """ The external ID of this thread. You can use this field to store your own unique identifier for this thread. """ externalId: ID """The status of this thread.""" status: ThreadStatus! """The datetime when the status of this thread was last changed.""" statusChangedAt: DateTime! """The actor who last changed the status of this thread.""" statusChangedBy: Actor! """ Additional details about the current thread status. For instance, how long it will be snoozed for. """ statusDetail: ThreadStatusDetail """Who or what this thread is assigned to.""" assignedTo: ThreadAssignee """ The datetime when this thread was last assigned to someone or something. """ assignedAt: DateTime """The labels attached to this thread.""" labels: [Label!]! """The links attached to this thread.""" links(first: Int, after: String, last: Int, before: String): ThreadLinkConnection! """The thread fields attached to this thread.""" threadFields: [ThreadField!]! """The thread discussions attached to this thread.""" threadDiscussions: [ThreadDiscussion!]! """All of the timeline entries in this thread.""" timelineEntries(first: Int, after: String, last: Int, before: String): TimelineEntryConnection! """First inbound message on the thread.""" firstInboundMessageInfo: ThreadMessageInfo """First outbound message on the thread.""" firstOutboundMessageInfo: ThreadMessageInfo """Last inbound message received.""" lastInboundMessageInfo: ThreadMessageInfo """Last outbound message received.""" lastOutboundMessageInfo: ThreadMessageInfo """The datetime when this thread was created.""" createdAt: DateTime! """The actor who created this thread.""" createdBy: Actor! """The datetime when this thread was last updated.""" updatedAt: DateTime! """The actor who last updated this thread.""" updatedBy: Actor! """ The support email addresses involved in this thread. A support email address is either the default support email address or an alternate support email address. A support email address is considered to be involved in a thread when any participant in the thread uses it as their email recipient. """ supportEmailAddresses: [String!]! """The tenant this thread is associated with.""" tenant: Tenant """ The tier this thread is associated with. Tiers mandate the SLAs for this thread. """ tier: Tier """ If this thread has a linked SLA, this will inform on the status of its objectives. """ serviceLevelAgreementStatusSummary: ServiceLevelAgreementStatusSummary! """The channel this thread belongs to.""" channel: ThreadChannel! """Details about the channel this thread is on.""" channelDetails: ThreadChannelDetails externalChannelDetails: ThreadExternalChannelDetails @deprecated(reason: "Use channelDetails instead") } enum ServiceLevelAgreementStatus { PENDING IMMINENT_BREACH BREACHING BREACHED ACHIEVED CANCELLED } type ServiceLevelAgreementStatusDetailPending { """The time when this SLA will breach.""" breachingAt: DateTime! } type ServiceLevelAgreementStatusDetailImminentBreach { """The time when this SLA will breach.""" breachingAt: DateTime! } type ServiceLevelAgreementStatusDetailBreaching { """The time when this SLA breached.""" breachedAt: DateTime! } type ServiceLevelAgreementStatusDetailAchieved { """The time when this SLA was achieved.""" achievedAt: DateTime! } type ServiceLevelAgreementStatusDetailBreached { """The time when this SLA breached.""" breachedAt: DateTime! """The time when we completed this breached SLA.""" completedAt: DateTime! } type ServiceLevelAgreementStatusDetailCancelled { """The time when this SLA was cancelled.""" cancelledAt: DateTime! } union ServiceLevelAgreementStatusDetail = ServiceLevelAgreementStatusDetailPending | ServiceLevelAgreementStatusDetailImminentBreach | ServiceLevelAgreementStatusDetailBreaching | ServiceLevelAgreementStatusDetailAchieved | ServiceLevelAgreementStatusDetailBreached | ServiceLevelAgreementStatusDetailCancelled type ServiceLevelAgreementStatusSummary { firstResponseTime: ServiceLevelAgreementStatusDetail nextResponseTime: ServiceLevelAgreementStatusDetail } """Only one of the fields can be set.""" input CustomerIdentifierInput { externalId: ID emailAddress: String customerId: ID } """Only one of the fields can be set.""" input CreateThreadAssignedToInput { userId: ID machineUserId: ID } input CreateThreadInput { """ The identifier of the customer being either the existing customer ID, the customer's email address or and external ID. """ customerIdentifier: CustomerIdentifierInput! """The title of the thread.""" title: String """The components used to create the first timeline entry in the thread.""" components: [ComponentInput!]! """An array of attachments for the first timeline entry in the thread.""" attachmentIds: [ID!] """An array of label types to attach to the thread upon creation.""" labelTypeIds: [ID!] """An array of thread fields to attach to the thread upon creation.""" threadFields: [CreateThreadFieldOnThreadInput!] """User or machine user this thread should be assigned to.""" assignedTo: CreateThreadAssignedToInput """ The external ID of this thread. You can use this field to store your own unique identifier for this thread. """ externalId: ID """ The optional description for this thread. This is used to display a preview of the thread in the UI. If not provided, we will automatically infer it from the components you provided. """ description: String """ The priority of the thread. Valid values are 0, 1, 2, 3, from most to least urgent, defaults to 2 (normal). """ priority: Int """A thread may be assigned to a specific tenant.""" tenantIdentifier: TenantIdentifierInput } type CreateThreadOutput { thread: Thread error: MutationError } input MarkCustomerAsSpamInput { customerId: ID! } input UnmarkCustomerAsSpamInput { customerId: ID! } type MarkCustomerAsSpamOutput { customer: Customer error: MutationError } type UnmarkCustomerAsSpamOutput { customer: Customer error: MutationError } type SubscriptionEventType { eventType: String! description: String! } type WebhookVersionEdge { cursor: String! node: WebhookVersion! } type WebhookVersionConnection { edges: [WebhookVersionEdge!]! pageInfo: PageInfo! } type WebhookTargetEdge { cursor: String! node: WebhookTarget! } type WebhookTargetConnection { edges: [WebhookTargetEdge!]! pageInfo: PageInfo! } type WebhookVersion { version: String! isDeprecated: Boolean! isLatest: Boolean! } type WebhookTarget { id: ID! url: String! description: String! eventSubscriptions: [WebhookTargetEventSubscription!]! version: String! isEnabled: Boolean! createdAt: DateTime! createdBy: InternalActor! updatedAt: DateTime! updatedBy: InternalActor! } type WebhookTargetEventSubscription { eventType: String! } input WebhookTargetEventSubscriptionInput { eventType: String! } input CreateWebhookTargetInput { url: String! eventSubscriptions: [WebhookTargetEventSubscriptionInput!]! isEnabled: Boolean! description: String! version: String } input UpdateWebhookTargetInput { webhookTargetId: ID! url: StringInput eventSubscriptions: [WebhookTargetEventSubscriptionInput!] isEnabled: BooleanInput description: StringInput version: StringInput } input DeleteWebhookTargetInput { webhookTargetId: ID! } type CreateWebhookTargetOutput { webhookTarget: WebhookTarget error: MutationError } type UpdateWebhookTargetOutput { webhookTarget: WebhookTarget error: MutationError } type DeleteWebhookTargetOutput { error: MutationError } input CustomerCardConfigOrderInput { """The ID of the customer card config to be reordered.""" customerCardConfigId: ID! """The order the customer card config should have.""" order: Int! } input ReorderCustomerCardConfigsInput { """An array of ordering updates.""" customerCardConfigOrders: [CustomerCardConfigOrderInput!]! } type ReorderCustomerCardConfigsOutput { """The reordered customer card configs.""" customerCardConfigs: [CustomerCardConfig!] error: MutationError } """An API header that will be sent to the configured API URL.""" input CustomerCardConfigApiHeaderInput { """ The name of the header, trimmed and treated case insensitively for deduplication purposes (min length: 1, max length: 100). Not all header names are allowed. """ name: String! """ The value of the header, treated case sensitively for deduplication purposes (min length: 1, max length: 500). """ value: String! } """ Input type to create a new customer card config. By default new customer cards will have an ordering of 100000 (to place them at the bottom). """ input CreateCustomerCardConfigInput { """The title of the card (max length: 500 characters).""" title: String! """ The key of the card (must be unique in a workspace, max length: 500 characters, must match regex: `[a-zA-Z0-9_-]+`). """ key: String! """ The default time the card should be cached for if no TTL is provided in the card response. (minimum: 15 seconds, maximum: 1 year or 31,536,000 seconds). """ defaultTimeToLiveSeconds: Int! """ The URL from which this card should be loaded (must start with `https://` and be a valid URL, max length: 600 characters). """ apiUrl: String! """An array of headers name-value pairs (maximum length of array: 20).""" apiHeaders: [CustomerCardConfigApiHeaderInput!]! } type CreateCustomerCardConfigOutput { """The created customer card config.""" customerCardConfig: CustomerCardConfig error: MutationError } """ For constraints and details on the fields see the `CustomerCardConfig` type. """ input UpdateCustomerCardConfigInput { """The customer card config to update.""" customerCardConfigId: ID! """If provided, will update the order.""" order: IntInput """If provided, will update the title.""" title: StringInput """If provided, will update the key. Keys must be unique in a workspace.""" key: StringInput """If provided, will update the default time to live seconds.""" defaultTimeToLiveSeconds: IntInput """ If provided, will update the API URL. Requires the `customerCardConfigApiDetails:edit` permission. """ apiUrl: StringInput """ If provided, will replace the existing API headers. Requires the `customerCardConfigApiDetails:edit` permission. """ apiHeaders: [CustomerCardConfigApiHeaderInput!] """If provided, will update the enabled flag.""" isEnabled: BooleanInput } type UpdateCustomerCardConfigOutput { """The updated customer card config.""" customerCardConfig: CustomerCardConfig error: MutationError } input DeleteCustomerCardConfigInput { """The customer card config ID to delete.""" customerCardConfigId: ID! } type DeleteCustomerCardConfigOutput { error: MutationError } input ReloadCustomerCardInstanceInput { customerId: ID! customerCardConfigId: ID! threadId: ID } type ReloadCustomerCardInstanceOutput { """ The reloaded customer card instance. Currently this will always be a `CustomerCardInstanceLoading` type. """ customerCardInstance: CustomerCardInstance error: MutationError } """An input to specify the scope for a setting.""" input SettingScopeInput { """ An optional ID input. Depends on the type of scope if this is required. """ id: ID """Determines the type of the scope.""" scopeType: SettingScopeType! } """ An input "union" where exactly one field may be be provided as an input. Current API only supports booleans but as the API expands more optional fields will be added. """ input SettingValueInput { """If the setting is a boolean value then this field should be set.""" boolean: Boolean """If the setting is a string value then this field should be set.""" string: String } """An input provided to the `updateSetting` mutation.""" input UpdateSettingInput { """A code for the setting.""" code: String! """A valid scope for the setting code.""" scope: SettingScopeInput! """The setting value.""" value: SettingValueInput! } """ An output type provided by the `updateSetting` mutation. Returns the updated setting or an error. """ type UpdateSettingOutput { """The updated setting.""" setting: Setting error: MutationError } input CreateMySlackIntegrationInput { authCode: String! redirectUrl: String! } type CreateMySlackIntegrationOutput { integration: UserSlackIntegration error: MutationError } input CreateUserAuthSlackIntegrationInput { authCode: String! redirectUrl: String! } type CreateUserAuthSlackIntegrationOutput { integration: UserAuthSlackIntegration error: MutationError } input CreateWorkspaceSlackIntegrationInput { authCode: String! redirectUrl: String! } type CreateWorkspaceSlackIntegrationOutput { integration: WorkspaceSlackIntegration error: MutationError } input DeleteWorkspaceSlackIntegrationInput { integrationId: ID! } type DeleteWorkspaceSlackIntegrationOutput { integration: WorkspaceSlackIntegration error: MutationError } input CreateWorkspaceSlackChannelIntegrationInput { authCode: String! redirectUrl: String! } type CreateWorkspaceSlackChannelIntegrationOutput { integration: WorkspaceSlackChannelIntegration error: MutationError } input DeleteWorkspaceSlackChannelIntegrationInput { integrationId: ID! } type DeleteWorkspaceSlackChannelIntegrationOutput { integration: WorkspaceSlackChannelIntegration error: MutationError } type DeleteMySlackIntegrationOutput { error: MutationError } input DeleteUserAuthSlackIntegrationInput { slackTeamId: String! } type DeleteUserAuthSlackIntegrationOutput { error: MutationError } type SlackUserConnection { edges: [SlackUserEdge!]! pageInfo: PageInfo! } type SlackUserEdge { cursor: String! node: SlackUser! } type SlackUser { id: ID! slackUserId: ID! slackAvatarUrl72px: String slackHandle: String! fullName: String! isInChannel: Boolean! createdAt: DateTime! createdBy: InternalActor! updatedAt: DateTime! updatedBy: InternalActor! } input UpdateConnectedSlackChannelInput { connectedSlackChannelId: ID! channelType: ConnectedSlackChannelType isEnabled: BooleanInput } type UpdateConnectedSlackChannelOutput { connectedSlackChannel: ConnectedSlackChannel error: MutationError } input ConnectedSlackChannelsFilter { slackTeamIds: [String!] channelTypes: [ConnectedSlackChannelType!] isEnabled: BooleanInput } type ConnectedSlackChannelConnection { pageInfo: PageInfo! edges: [ConnectedSlackChannelEdge!]! totalCount: Int! } type ConnectedSlackChannelEdge { cursor: String! node: ConnectedSlackChannel! } enum ConnectedSlackChannelType { """A channel that Plain tracks for customer support requests.""" CUSTOMER """A channel that Plain tracks for internal team discussions.""" DISCUSSION } type ConnectedSlackChannel { id: ID! slackTeamId: String! slackChannelId: String! name: String! channelType: ConnectedSlackChannelType! isEnabled: Boolean! createdAt: DateTime! createdBy: InternalActor! updatedAt: DateTime! updatedBy: InternalActor! } input CreateWorkspaceDiscordIntegrationInput { name: String! webhookUrl: String! } type CreateWorkspaceDiscordIntegrationOutput { integration: WorkspaceDiscordIntegration error: MutationError } input DeleteWorkspaceDiscordIntegrationInput { integrationId: ID! } type DeleteWorkspaceDiscordIntegrationOutput { integration: WorkspaceDiscordIntegration error: MutationError } input CreateMyLinearIntegrationInput { authCode: String! redirectUrl: String! } type CreateMyLinearIntegrationOutput { integration: UserLinearIntegration error: MutationError } type DeleteMyLinearIntegrationOutput { error: MutationError } input CreateMyMSTeamsIntegrationInput { authCode: ID! redirectUrl: String! } type CreateMyMSTeamsIntegrationOutput { integration: UserMSTeamsIntegration error: MutationError } type DeleteMyMSTeamsIntegrationOutput { integration: UserMSTeamsIntegration error: MutationError } input CreateWorkspaceMSTeamsIntegrationInput { msTeamsTenantId: ID! } type CreateWorkspaceMSTeamsIntegrationOutput { integration: WorkspaceMSTeamsIntegration error: MutationError } input DeleteWorkspaceMSTeamsIntegrationInput { integrationId: ID! } type DeleteWorkspaceMSTeamsIntegrationOutput { integration: WorkspaceMSTeamsIntegration error: MutationError } type WorkspaceMSTeamsIntegration { id: ID! msTeamsTenantId: ID! isReinstallRequired: Boolean! createdAt: DateTime! createdBy: InternalActor! updatedAt: DateTime! updatedBy: InternalActor! } enum ChangeType { ADDED UPDATED REMOVED } type TimelineEntryChange { changeType: ChangeType! timelineEntry: TimelineEntry! } type CustomerChange { changeType: ChangeType! customer: Customer! } type ThreadChange { changeType: ChangeType! thread: Thread! } type UserChange { changeType: ChangeType! user: User! } type CustomerCardInstanceChange { changeType: ChangeType! customerCardInstance: CustomerCardInstance! } type ThreadFieldSchemaChange { changeType: ChangeType! threadFieldSchema: ThreadFieldSchema! } type SubscriptionAcknowledgement { subscriptionId: ID! } union CustomerCardInstanceChangesResult = CustomerCardInstanceChange | SubscriptionAcknowledgement input CustomerChangesFilter { assignedToUser: [ID!] } type Subscription { timelineChanges(customerId: ID!): TimelineEntryChange! threadTimelineChanges(threadId: ID!): TimelineEntryChange! customerChanges(filters: CustomerChangesFilter @deprecated): CustomerChange! threadChanges: ThreadChange! customerCardInstanceChanges(customerId: ID!): CustomerCardInstanceChangesResult! userChanges: UserChange! threadFieldSchemaChanges: ThreadFieldSchemaChange! } input UpsertCustomerGroupInput { identifier: CustomerGroupIdentifier! name: String! key: String! color: String! externalId: String } type UpsertCustomerGroupOutput { customerGroup: CustomerGroup result: UpsertResult error: MutationError } input CreateCustomerGroupInput { name: String! key: String! color: String! externalId: String } type CreateCustomerGroupOutput { customerGroup: CustomerGroup error: MutationError } input UpdateCustomerGroupInput { customerGroupId: ID! name: StringInput key: StringInput color: StringInput externalId: OptionalStringInput } type UpdateCustomerGroupOutput { customerGroup: CustomerGroup error: MutationError } input DeleteCustomerGroupInput { customerGroupId: ID! } type DeleteCustomerGroupOutput { error: MutationError } input CustomerGroupIdentifier { customerGroupId: ID customerGroupKey: String externalId: String } input AddCustomerToCustomerGroupsInput { customerId: ID! customerGroupIdentifiers: [CustomerGroupIdentifier!]! } type AddCustomerToCustomerGroupsOutput { customerGroupMemberships: [CustomerGroupMembership!] error: MutationError } input RemoveCustomerFromCustomerGroupsInput { customerId: ID! customerGroupIdentifiers: [CustomerGroupIdentifier!]! } type RemoveCustomerFromCustomerGroupsOutput { error: MutationError } """Query to search for companies.""" input CompaniesSearchQuery { """ The term to search for. It must be at least 2 characters long. The search is case-insensitive on these two fields: - the company name (partial match) - the company domain name (partial match) """ term: String! } type CompanySearchResult { company: Company! } type CompanySearchResultEdge { cursor: String! node: CompanySearchResult! } type CompanySearchResultConnection { edges: [CompanySearchResultEdge!]! pageInfo: PageInfo! } """Query to search for tenants.""" input TenantsSearchQuery { """ The term to search for. It must be at least 2 characters long. The search is case-insensitive on these two fields: - the tenant name (partial match) - the tenant external id (exact match) """ term: String! } type TenantSearchResult { tenant: Tenant! } type TenantSearchResultEdge { cursor: String! node: TenantSearchResult! } type TenantSearchResultConnection { edges: [TenantSearchResultEdge!]! pageInfo: PageInfo! } input StartServiceAuthorizationInput { serviceIntegrationKey: String! } type ServiceAuthorizationConnectionDetails { serviceIntegrationKey: String! serviceAuthorizationId: ID! hmacDigest: String! } type StartServiceAuthorizationOutput { connectionDetails: ServiceAuthorizationConnectionDetails error: MutationError } input CompleteServiceAuthorizationInput { serviceAuthorizationId: ID! } type CompleteServiceAuthorizationOutput { serviceAuthorization: ServiceAuthorization error: MutationError } input DeleteServiceAuthorizationInput { serviceAuthorizationId: ID! } type DeleteServiceAuthorizationOutput { error: MutationError } type ServiceIntegration { name: String! key: String! } type ServiceAuthorization { id: ID! serviceIntegration: ServiceIntegration! createdAt: DateTime! createdBy: InternalActor! updatedAt: DateTime! updatedBy: InternalActor! } type ServiceAuthorizationEdge { cursor: String! node: ServiceAuthorization! } type ServiceAuthorizationConnection { edges: [ServiceAuthorizationEdge!]! pageInfo: PageInfo! } input BusinessHoursWeekDayInput { """ The time you open for business on this day as an UTC ISO time. For example: 09:00Z . """ startTime: String! """ The time you close for business on this day as an UTC ISO time. For example: 17:00Z . """ endTime: String! } """ Represents the times in which you are open for business during a week. Only provide the days you are open for business. """ input BusinessHoursWeekDaysInput { monday: BusinessHoursWeekDayInput tuesday: BusinessHoursWeekDayInput wednesday: BusinessHoursWeekDayInput thursday: BusinessHoursWeekDayInput friday: BusinessHoursWeekDayInput saturday: BusinessHoursWeekDayInput sunday: BusinessHoursWeekDayInput } input UpsertBusinessHoursInput { weekDays: BusinessHoursWeekDaysInput } type UpsertBusinessHoursOutput { businessHours: BusinessHours result: UpsertResult error: MutationError } type DeleteBusinessHoursOutput { error: MutationError } type BusinessHoursWeekDays { monday: BusinessHoursWeekDay tuesday: BusinessHoursWeekDay wednesday: BusinessHoursWeekDay thursday: BusinessHoursWeekDay friday: BusinessHoursWeekDay saturday: BusinessHoursWeekDay sunday: BusinessHoursWeekDay } """ Represents the times in which you are open for business during a week. If a day is null, it means that day you are not open for business. """ type BusinessHours { weekDays: BusinessHoursWeekDays! createdAt: DateTime! createdBy: InternalActor! updatedAt: DateTime! updatedBy: InternalActor! } type BusinessHoursWeekDay { startTime: Time! endTime: Time! } input UpdateThreadTenantInput { threadId: ID! tenantIdentifier: TenantIdentifierInput } type UpdateThreadTenantOutput { thread: Thread error: MutationError } input AddCustomerToTenantsInput { customerIdentifier: CustomerIdentifierInput! tenantIdentifiers: [TenantIdentifierInput!]! } input RemoveCustomerFromTenantsInput { customerIdentifier: CustomerIdentifierInput! tenantIdentifiers: [TenantIdentifierInput!]! } input SetCustomerTenantsInput { customerIdentifier: CustomerIdentifierInput! tenantIdentifiers: [TenantIdentifierInput!]! } type AddCustomerToTenantsOutput { customer: Customer error: MutationError } type RemoveCustomerFromTenantsOutput { customer: Customer error: MutationError } type SetCustomerTenantsOutput { customer: Customer error: MutationError } input CompaniesFilter { companyIds: [ID!] } enum BillingSubscriptionStatus { ACTIVE INACTIVE } enum BillingPlanKey { LEGACY EVALUATE LAUNCH GROW SCALE } enum BillingSeatType { VIEWER @deprecated MEMBER @deprecated ENG_ROTA @deprecated } enum BillingInterval { MONTH @deprecated(reason: "Use BillingIntervalUnit.MONTH instead") YEAR @deprecated(reason: "Use BillingIntervalUnit.YEAR instead") } enum BillingIntervalUnit { MONTH YEAR } input CreateCheckoutSessionInput { plan: BillingPlanKey! interval: BillingInterval @deprecated(reason: "Use intervalUnit instead") intervalUnit: BillingIntervalUnit } type CreateCheckoutSessionOutput { sessionClientSecret: String error: MutationError } enum CurrencyCode { USD } type Price { amount: Int! currency: CurrencyCode! } type PriceTier { maxSeats: Int! perSeatAmount: Int! flatAmount: Int! } interface RecurringPrice { billingIntervalUnit: BillingIntervalUnit! billingIntervalCount: Int! currency: CurrencyCode! } type PerSeatRecurringPrice implements RecurringPrice { billingIntervalUnit: BillingIntervalUnit! billingIntervalCount: Int! currency: CurrencyCode! perSeatAmount: Int! } type TieredRecurringPrice implements RecurringPrice { billingIntervalUnit: BillingIntervalUnit! billingIntervalCount: Int! currency: CurrencyCode! tiers: [PriceTier!]! } type BillingPlan { key: BillingPlanKey! name: String! description: String! features: [String!]! highlightedLabel: String isSelfCheckoutEligible: Boolean! monthlyPrice: Price @deprecated(reason: "Use prices instead") yearlyPrice: Price @deprecated(reason: "Use prices instead") prices: [RecurringPrice!]! } type BillingPlanConnection { edges: [BillingPlanEdge!]! pageInfo: PageInfo! } type BillingPlanEdge { cursor: String! node: BillingPlan! } type CreateBillingPortalSessionOutput { billingPortalSessionUrl: String error: MutationError } enum FeatureKey { BUSINESS_HOURS SLACK_DISCUSSIONS SERVICE_LEVEL_AGREEMENTS DATA_IMPORTERS MS_TEAMS_INTEGRATION CONNECTED_CUSTOMER_SLACK_CHANNELS CONNECTED_SUPPORT_EMAIL_ADDRESSES INSIGHTS_LOOKBACK_DAYS BILLING_ROTA_SEATS MORE_ACTIVE_ENG_ROTA_SEATS } interface BillingFeatureEntitlement { feature: FeatureKey! isEntitled: Boolean! } type ToggleFeatureEntitlement implements BillingFeatureEntitlement { feature: FeatureKey! isEntitled: Boolean! } type MeteredFeatureEntitlement implements BillingFeatureEntitlement { feature: FeatureKey! isEntitled: Boolean! current: Int! limit: Int! } type BillingSubscription { status: BillingSubscriptionStatus! planKey: BillingPlanKey! planName: String! interval: BillingInterval! @deprecated cancelsAt: DateTime trialEndsAt: DateTime entitlements: [BillingFeatureEntitlement!]! endedAt: DateTime } input CalculateRoleChangeCostInput { roleKey: RoleKey! userId: ID usingBillingRotaSeat: BooleanInput } type RoleChangeCost { totalPrice: Price! intervalUnit: BillingIntervalUnit! intervalCount: Int! addingSeatType: BillingSeatType! removingSeatType: BillingSeatType } type CalculateRoleChangeCostOutput { roleChangeCost: RoleChangeCost error: MutationError } input AddUserToActiveBillingRotaInput { userId: ID! } type AddUserToActiveBillingRotaOutput { billingRota: BillingRota error: MutationError } input RemoveUserFromActiveBillingRotaInput { userId: ID! } type RemoveUserFromActiveBillingRotaOutput { billingRota: BillingRota error: MutationError } type BillingRota { onRotaUserIds: [ID!]! offRotaUserIds: [ID!]! } input UpdateActiveBillingRotaInput { userIdsToAdd: [ID!] userIdsToRemove: [ID!] } type UpdateActiveBillingRotaOutput { billingRota: BillingRota error: MutationError } enum RoleKey { OWNER ADMIN SUPPORT VIEWER } input ChangeBillingPlanInput { planKey: BillingPlanKey! intervalUnit: BillingIntervalUnit } type ChangeBillingPlanOutput { error: MutationError } input PreviewBillingPlanChangeInput { planKey: BillingPlanKey! intervalUnit: BillingIntervalUnit } type PreviewBillingPlanChangeOutput { preview: BillingPlanChangePreview error: MutationError } type BillingPlanChangePreview { immediateCost: Price! earliestEffectiveAt: DateTime! } type PaymentMethod { isAvailable: Boolean! } type WorkspaceHmac { hmacSecret: String createdAt: DateTime! createdBy: InternalActor! updatedAt: DateTime! updatedBy: InternalActor! } type RegenerateWorkspaceHmacOutput { workspaceHmac: WorkspaceHmac error: MutationError }