type Query { """Get the current workspace""" workspace: Workspace! """List the workspace's members""" members: MemberPage! """List pending invitations""" invitations: InvitationPage! """List API keys""" apiKeys: ApiKeyPage! """Get a tenant""" tenant(id: String!): Tenant! """List tenants""" tenants( """Only these ids (up to 200).""" ids: [String!] """Matches name or domain (case-insensitive substring).""" query: String tierId: String """Page size, 1 to 200.""" limit: Float """Cursor from the previous page's nextCursor.""" cursor: String ): TenantPage! """List tiers""" tiers: TierPage! """Get a customer""" customer(id: String!): Customer! """List customers""" customers( """Only these ids (up to 200).""" ids: [String!] """Matches name or email (case-insensitive substring).""" query: String tenantId: String """Page size, 1 to 200.""" limit: Float """Cursor from the previous page's nextCursor.""" cursor: String ): CustomerPage! """Get a thread""" thread( id: String! """Related objects to include: customer, tenant.""" expand: [ThreadExpandItem!] ): Thread! """ List threads. Most recent activity first. Filters combine with AND; several values for one filter mean any of them. """ threads( status: [ThreadStatus!] statusDetail: [StatusDetail!] customerId: String """Deprecated: use assignee.""" assigneeId: String """User ids, "me" (the caller) or "none" (unassigned).""" assignee: [String!] """Threads with any of these labels.""" labelIds: [String!] priority: [Float!] tenantIds: [String!] tierIds: [String!] channel: [Channel!] """Related objects to include: customer, tenant.""" expand: [ThreadsExpandItem!] """Page size, 1 to 200.""" limit: Float """Cursor from the previous page's nextCursor.""" cursor: String ): ThreadPage! """ Search threads. Full-text search over thread titles and every message and note, in English (with stemming) and Chinese/Japanese/Korean (exact phrases). Best match first. """ searchThreads(query: String!, limit: Float): SearchResult! """List a thread's timeline. Oldest first.""" entries( threadId: String! """Page size, 1 to 200.""" limit: Float """Cursor from the previous page's nextCursor.""" cursor: String ): EntryPage! """List labels""" labels: LabelPage! """List saved views""" views: SavedViewPage! """ List snippets. Snippet text may use {customer.firstName}, {customer.fullName}, {customer.email}, {agent.firstName} and {agent.fullName}; the app fills them in when inserting. """ snippets: SnippetPage! """ Support metrics for a date range. Volume per day (created and done), median first-response and resolution times, the current queue, and new threads by label, tier and priority. """ insights( days: Float """IANA time zone for day boundaries, e.g. Europe/Berlin.""" timeZone: String ): Insights! """List webhook endpoints""" webhooks: WebhookEndpointPage! """ List an endpoint's deliveries. Newest first, with each attempt's response. """ webhookDeliveries( """Endpoint id.""" id: String! """Page size, 1 to 200.""" limit: Float """Cursor from the previous page's nextCursor.""" cursor: String ): WebhookDeliveryPage! } type Workspace { id: String! name: String! slug: String! """Image URL or data URL.""" logo: String region: String! plan: String! """ISO 8601 timestamp""" createdAt: String! } type MemberPage { data: [Member!]! nextCursor: String hasMore: Boolean! } type Member { id: String! userId: String! name: String! email: String! image: String role: MemberRole! """ISO 8601 timestamp""" createdAt: String! } enum MemberRole { owner admin member viewer } type InvitationPage { data: [Invitation!]! nextCursor: String hasMore: Boolean! } type Invitation { id: String! email: String! role: InvitationRole! status: String! inviterId: String! """ISO 8601 timestamp""" expiresAt: String! """ISO 8601 timestamp""" createdAt: String! } enum InvitationRole { owner admin member viewer } type ApiKeyPage { data: [ApiKey!]! nextCursor: String hasMore: Boolean! } type ApiKey { id: String! name: String! prefix: String! role: ApiKeyRole! lastUsedAt: String revokedAt: String """ISO 8601 timestamp""" createdAt: String! } enum ApiKeyRole { owner admin member viewer } type Tenant { id: String! name: String! domain: String externalId: String """Free-text tier from an external system. Prefer tierId.""" tier: String tierId: String version: Float! """ISO 8601 timestamp""" createdAt: String! """ISO 8601 timestamp""" updatedAt: String! } type TenantPage { data: [Tenant!]! nextCursor: String hasMore: Boolean! } type TierPage { data: [Tier!]! nextCursor: String hasMore: Boolean! } type Tier { id: String! name: String! color: Color! """ Priority for new threads from this tier's tenants; null keeps the default. """ defaultPriority: Float position: Float! version: Float! """ISO 8601 timestamp""" createdAt: String! """ISO 8601 timestamp""" updatedAt: String! } enum Color { gray red orange yellow green teal blue violet pink } type Customer { id: String! fullName: String! email: String avatarUrl: String externalId: String tenantId: String """A simulated customer created for a test thread.""" isTest: Boolean! version: Float! """ISO 8601 timestamp""" createdAt: String! """ISO 8601 timestamp""" updatedAt: String! tenant: Tenant } type CustomerPage { data: [Customer!]! nextCursor: String hasMore: Boolean! } type Thread { id: String! """Human number within the workspace, shown as T-.""" number: Float! title: String! status: ThreadStatus! """Who owes the next move while the thread is in Todo; null otherwise.""" statusDetail: StatusDetail """0 urgent, 1 high, 2 normal, 3 low.""" priority: Float! channel: Channel! customerId: String! tenantId: String assigneeId: String labelIds: [String!]! """Test thread: a simulated customer answers replies.""" isTest: Boolean! links: [ThreadLink!]! snoozedUntil: String """The team's first reply.""" firstResponseAt: String """The customer's latest message.""" lastInboundAt: String """The team's latest reply.""" lastOutboundAt: String """ISO 8601 timestamp""" lastActivityAt: String! """ISO 8601 timestamp""" statusChangedAt: String! version: Float! """ISO 8601 timestamp""" createdAt: String! """ISO 8601 timestamp""" updatedAt: String! """Present when expand includes customer.""" customer: Customer """Present when expand includes tenant.""" tenant: Tenant } enum ThreadStatus { todo snoozed done } enum StatusDetail { needs_first_response needs_next_response in_progress waiting_for_customer } enum Channel { slack email chat api teams discord feishu } type ThreadLink { id: String! url: String! title: String } enum ThreadExpandItem { customer tenant } type ThreadPage { data: [Thread!]! nextCursor: String hasMore: Boolean! } enum ThreadsExpandItem { customer tenant } type SearchResult { data: [SearchHit!]! } type SearchHit { thread: Thread! """Relevance; higher is better.""" score: Float! matchedIn: [SearchHitMatchedInItem!]! } enum SearchHitMatchedInItem { title message } type EntryPage { data: [Entry!]! nextCursor: String hasMore: Boolean! } type Entry { id: String! threadId: String! kind: EntryKind! actorType: String! actorId: String """Kind-specific fields; messages and notes carry `text`.""" body: JSON! """ISO 8601 timestamp""" createdAt: String! } enum EntryKind { message_in message_out note status priority assignment labels link } """Arbitrary JSON value.""" scalar JSON type LabelPage { data: [Label!]! nextCursor: String hasMore: Boolean! } type Label { id: String! name: String! color: Color! version: Float! """ISO 8601 timestamp""" createdAt: String! """ISO 8601 timestamp""" updatedAt: String! } type SavedViewPage { data: [SavedView!]! nextCursor: String hasMore: Boolean! } type SavedView { id: String! name: String! filters: ThreadFilters! position: Float! createdBy: String version: Float! """ISO 8601 timestamp""" createdAt: String! """ISO 8601 timestamp""" updatedAt: String! } type ThreadFilters { status: [ThreadStatus!] statusDetail: [StatusDetail!] """User ids, "me" or "none" (unassigned).""" assignee: [String!] labelIds: [String!] priority: [Float!] tenantIds: [String!] tierIds: [String!] channel: [Channel!] } type SnippetPage { data: [Snippet!]! nextCursor: String hasMore: Boolean! } type Snippet { id: String! name: String! text: String! createdBy: String version: Float! """ISO 8601 timestamp""" createdAt: String! """ISO 8601 timestamp""" updatedAt: String! } type Insights { """Start of the first day (ISO 8601).""" from: String! to: String! timeZone: String! days: Float! totals: InsightsTotals! """Threads created in the range that got a reply.""" medianFirstResponseSeconds: Float """From creation to done, for threads marked done in the range.""" medianResolutionSeconds: Float volume: [InsightsVolumeItem!]! byLabel: [InsightsByLabelItem!]! byTier: [InsightsByTierItem!]! byPriority: [InsightsByPriorityItem!]! } type InsightsTotals { """Threads created in the range.""" created: Float! """Times a thread was marked done in the range.""" done: Float! """Threads in Todo now.""" open: Float! """Todo threads waiting on the team now.""" needsResponse: Float! """Todo threads waiting on the customer now.""" waiting: Float! snoozed: Float! } type InsightsVolumeItem { """YYYY-MM-DD""" date: String! created: Float! done: Float! } type InsightsByLabelItem { id: String! name: String! color: String! count: Float! } type InsightsByTierItem { id: String name: String! color: String! count: Float! } type InsightsByPriorityItem { priority: Float! count: Float! } type WebhookEndpointPage { data: [WebhookEndpoint!]! nextCursor: String hasMore: Boolean! } type WebhookEndpoint { id: String! url: String! description: String """Subscribed event types, or ["*"] for all.""" eventTypes: [String!]! enabled: Boolean! version: Float! """ISO 8601 timestamp""" createdAt: String! """ISO 8601 timestamp""" updatedAt: String! } type WebhookDeliveryPage { data: [WebhookDelivery!]! nextCursor: String hasMore: Boolean! } type WebhookDelivery { id: String! endpointId: String! eventId: String! eventType: String! status: WebhookDeliveryStatus! attempts: Float! responseStatus: Float responseBody: String durationMs: Float nextAttemptAt: String lastAttemptAt: String """ISO 8601 timestamp""" createdAt: String! } enum WebhookDeliveryStatus { pending succeeded failed } type Mutation { """Rename the workspace or change its logo""" updateWorkspace( name: String """https URL or a base64 data URL (PNG, JPEG, WebP); null removes it.""" logo: String ): Workspace! """ Change a member's role. Only owners can make someone an owner or change an owner's role. A workspace always keeps at least one owner. """ updateMemberRole( """Member id.""" id: String! role: UpdateMemberRoleRole! ): Member! """ Remove a member. Their open threads become unassigned. They lose access at once. """ removeMember( """Member id.""" id: String! ): Deleted! """ Invite someone by email. Sends an email with a link to join. Inviting the same address again replaces the earlier invitation. Invitations expire after 7 days. """ inviteMember(email: String!, role: InviteMemberRole): Invitation! """Cancel an invitation""" cancelInvitation(id: String!): Invitation! """ Create an API key. The secret is returned once and never again; only its hash is stored. """ createApiKey(name: String!): ApiKeyCreated! """Revoke an API key""" revokeApiKey(id: String!): ApiKey! """ Create or update a tenant. Matched by externalId; creates the tenant when no match exists. """ upsertTenant( externalId: String! name: String! domain: String """Free-text tier from your system. Prefer tierId.""" tier: String tierId: String ): Tenant! """ Create a tenant. For tenants you add by hand. Integrations that sync from your system should use tenants.upsert with an externalId. """ createTenant(name: String!, domain: String, tierId: String, externalId: String): Tenant! """Update a tenant""" updateTenant(id: String!, name: String, domain: String, tierId: String, expectedVersion: Float): Tenant! """Create a tier""" createTier(name: String!, color: Color, defaultPriority: Float): Tier! """Update a tier""" updateTier(id: String!, name: String, color: Color, defaultPriority: Float, position: Float, expectedVersion: Float): Tier! """Delete a tier. Tenants on this tier keep working, with no tier.""" deleteTier(id: String!): Deleted! """Create or update a customer. Matched by email or externalId.""" upsertCustomer(customer: CustomerIdentityInput!): Customer! """ Update a customer. Edit name, email or tenant. Moving a customer to another tenant moves their threads with them. """ updateCustomer(id: String!, fullName: String, email: String, tenantId: String, expectedVersion: Float): Customer! """ Create a thread. Finds or creates the customer, then opens a thread, optionally with the customer's first message. Without a priority, the tenant's tier decides (or Normal). """ createThread( title: String! customer: CreateThreadCustomer! message: CreateThreadMessage """0 urgent … 3 low. Defaults to the tenant tier's priority, else 2.""" priority: Float channel: Channel assigneeId: String labelIds: [String!] ): Thread! """ Create a test thread. Opens a thread from a simulated customer (marked isTest). When you reply, the customer answers about ten seconds later, so you can try the whole flow before connecting a channel. """ createTestThread( """Which sample conversation; random when omitted.""" script: Float ): Thread! """ Update a thread. Change title, status, priority, assignee or snooze. statusDetail (Todo only) marks a thread as waiting for the customer or in progress; without it the detail follows the conversation. Each change is also written to the timeline. Pass expectedVersion to fail with 409 instead of overwriting someone else's change. """ updateThread( id: String! title: String status: ThreadStatus """Only with status todo (or on a Todo thread).""" statusDetail: StatusDetail priority: Float assigneeId: String snoozedUntil: String expectedVersion: Float ): Thread! """Add labels to a thread. Labels already on the thread are ignored.""" addThreadLabels(id: String!, labelIds: [String!]!): Thread! """Remove labels from a thread""" removeThreadLabels(id: String!, labelIds: [String!]!): Thread! """ Attach a link to a thread. A Linear or GitHub issue, a doc, anything with an https URL. """ addThreadLink(id: String!, url: String!, title: String): Thread! """Remove a link from a thread""" removeThreadLink(id: String!, linkId: String!): Thread! """ Add an internal note. Visible to the team only; never sent to the customer. """ createNote(threadId: String!, text: String!): Entry! """ Reply to the customer. Records the reply and queues it for delivery on the thread's channel. `then` decides what happens to the thread: none (stays where it is), wait (waiting for the customer until they answer) or done. """ sendMessage(threadId: String!, text: String!, then: SendMessageThen): Entry! """ Record a customer message. For custom channels: adds the customer's message and moves the thread back to Todo (needs first or next response). """ receiveMessage(threadId: String!, text: String!): Entry! """Create a label""" createLabel(name: String!, color: Color): Label! """Rename or recolour a label""" updateLabel(id: String!, name: String, color: Color, expectedVersion: Float): Label! """Delete a label. Also removes it from every thread that has it.""" deleteLabel(id: String!): Deleted! """Save a view. A named set of thread filters, shared with the workspace.""" createView(name: String!, filters: ThreadFiltersInput!): SavedView! """Update a saved view""" updateView(id: String!, name: String, filters: ThreadFiltersInput, position: Float, expectedVersion: Float): SavedView! """Delete a saved view""" deleteView(id: String!): Deleted! """Create a snippet""" createSnippet(name: String!, text: String!): Snippet! """Update a snippet""" updateSnippet(id: String!, name: String, text: String, expectedVersion: Float): Snippet! """Delete a snippet""" deleteSnippet(id: String!): Deleted! """ Add a webhook endpoint. Returns the signing secret once. Each delivery is a POST with a JSON body { id, type, createdAt, workspaceId, data } and a Weft-Signature header. """ createWebhook( url: String! description: String """Event types to receive, or ["*"] for all.""" eventTypes: [CreateWebhookEventTypesItem!]! ): WebhookEndpointWithSecret! """Update a webhook endpoint""" updateWebhook(id: String!, url: String, description: String, eventTypes: [UpdateWebhookEventTypesItem!], enabled: Boolean): WebhookEndpoint! """ Replace an endpoint's signing secret. The old secret stops working at once; the new one is returned once. """ rollWebhookSecret(id: String!): WebhookEndpointWithSecret! """Delete a webhook endpoint. Its delivery log is deleted with it.""" deleteWebhook(id: String!): Deleted! """Retry a delivery now""" retryWebhookDelivery( """Delivery id.""" id: String! ): WebhookDelivery! """ Send a test event. Delivers a webhook.test event to this endpoint only. """ testWebhook(id: String!): WebhookTestSent! } enum UpdateMemberRoleRole { owner admin member viewer } type Deleted { id: String! deleted: Boolean! } enum InviteMemberRole { owner admin member viewer } type ApiKeyCreated { apiKey: ApiKey! secret: String! } input CustomerIdentityInput { email: String externalId: String fullName: String avatarUrl: String tenantId: String } input CreateThreadCustomer { id: String email: String externalId: String fullName: String avatarUrl: String tenantId: String } input CreateThreadMessage { text: String! } enum SendMessageThen { none wait done } input ThreadFiltersInput { status: [ThreadStatus!] statusDetail: [StatusDetail!] """User ids, "me" or "none" (unassigned).""" assignee: [String!] labelIds: [String!] priority: [Float!] tenantIds: [String!] tierIds: [String!] channel: [Channel!] } type WebhookEndpointWithSecret { endpoint: WebhookEndpoint! """Shown once. Use it to verify Weft-Signature.""" secret: String! } enum CreateWebhookEventTypesItem { thread_created thread_updated entry_created customer_created customer_updated tenant_created tenant_updated label_created label_updated label_deleted } enum UpdateWebhookEventTypesItem { thread_created thread_updated entry_created customer_created customer_updated tenant_created tenant_updated label_created label_updated label_deleted } type WebhookTestSent { eventId: String! }