plain
$
npx mdskill add vm0-ai/vm0-skills/plainQuery Plain.com support threads and customer data via GraphQL.
- Retrieves thread lists and individual details by ID.
- Integrates with Plain.com GraphQL API for support management.
- Executes requests using Bearer token authentication.
- Returns structured JSON data for customer and thread info.
SKILL.md
.github/skills/plainView on GitHub ↗
---
name: plain
description: Plain.com GraphQL API for customer support. Use when user mentions "Plain", "Plain.com", "support threads", or managing customers in Plain.
---
## Troubleshooting
If requests fail, run `zero doctor check-connector --env-name PLAIN_TOKEN` or `zero doctor check-connector --url https://core-api.uk.plain.com/graphql/v1 --method POST`
## Threads
### List Threads
Write to `/tmp/plain_request.json`:
```json
{
"query": "query listThreads($first: Int, $filters: ThreadsFilter) { threads(first: $first, filters: $filters) { edges { node { id externalId title status priority createdAt { iso8601 } customer { id fullName email { email } } assignedTo { ... on User { id fullName } ... on MachineUser { id fullName } } } } pageInfo { hasNextPage endCursor } } }",
"variables": {
"first": 20,
"filters": {
"statuses": ["TODO", "SNOOZED"]
}
}
}
```
Then run:
```bash
curl -s -X POST "https://core-api.uk.plain.com/graphql/v1" --header "Authorization: Bearer $PLAIN_TOKEN" --header "Content-Type: application/json" -d @/tmp/plain_request.json | jq '.data.threads.edges[].node | {id, title, status, customer: .customer.email.email}'
```
### Get Thread by ID
Replace `<thread-id>` with the actual thread ID from the list threads response:
Write to `/tmp/plain_request.json`:
```json
{
"query": "query getThread($threadId: ID!) { thread(threadId: $threadId) { id externalId title status priority createdAt { iso8601 } updatedAt { iso8601 } customer { id fullName email { email } } assignedTo { ... on User { id fullName } ... on MachineUser { id fullName } } labels { id labelType { id name } } } }",
"variables": {
"threadId": "<thread-id>"
}
}
```
Then run:
```bash
curl -s -X POST "https://core-api.uk.plain.com/graphql/v1" --header "Authorization: Bearer $PLAIN_TOKEN" --header "Content-Type: application/json" -d @/tmp/plain_request.json
```
### Reply to Thread
Replace `<thread-id>` with the actual thread ID:
Write to `/tmp/plain_request.json`:
```json
{
"query": "mutation replyToThread($input: ReplyToThreadInput!) { replyToThread(input: $input) { timelineEntry { id timestamp { iso8601 } } error { message type code } } }",
"variables": {
"input": {
"threadId": "<thread-id>",
"textContent": "Thank you for reaching out. I'm looking into your issue and will follow up shortly."
}
}
}
```
Then run:
```bash
curl -s -X POST "https://core-api.uk.plain.com/graphql/v1" --header "Authorization: Bearer $PLAIN_TOKEN" --header "Content-Type: application/json" -d @/tmp/plain_request.json
```
## Customers
### Upsert Customer
Create a new customer or update an existing one by email:
Write to `/tmp/plain_request.json`:
```json
{
"query": "mutation upsertCustomer($input: UpsertCustomerInput!) { upsertCustomer(input: $input) { customer { id fullName email { email isVerified } externalId createdAt { iso8601 } } result error { message type code fields { field message type } } } }",
"variables": {
"input": {
"identifier": {
"emailAddress": "alice@example.com"
},
"onCreate": {
"fullName": "Alice Thompson",
"email": {
"email": "alice@example.com",
"isVerified": true
},
"externalId": "user_123"
},
"onUpdate": {
"fullName": { "value": "Alice Thompson" }
}
}
}
}
```
Then run:
```bash
curl -s -X POST "https://core-api.uk.plain.com/graphql/v1" --header "Authorization: Bearer $PLAIN_TOKEN" --header "Content-Type: application/json" -d @/tmp/plain_request.json
```
### Get Customer by Email
Write to `/tmp/plain_request.json`:
```json
{
"query": "query getCustomerByEmail($email: String!) { customerByEmail(email: $email) { id fullName email { email isVerified } externalId createdAt { iso8601 } } }",
"variables": {
"email": "alice@example.com"
}
}
```
Then run:
```bash
curl -s -X POST "https://core-api.uk.plain.com/graphql/v1" --header "Authorization: Bearer $PLAIN_TOKEN" --header "Content-Type: application/json" -d @/tmp/plain_request.json
```
## Labels
### List Label Types
Write to `/tmp/plain_request.json`:
```json
{
"query": "query listLabelTypes { labelTypes(first: 50) { edges { node { id name } } } }"
}
```
Then run:
```bash
curl -s -X POST "https://core-api.uk.plain.com/graphql/v1" --header "Authorization: Bearer $PLAIN_TOKEN" --header "Content-Type: application/json" -d @/tmp/plain_request.json | jq '.data.labelTypes.edges[].node | {id, name}'
```
### Add Labels to Thread
Replace `<thread-id>` and `<label-type-id>` with actual values from the list threads and list label types responses:
Write to `/tmp/plain_request.json`:
```json
{
"query": "mutation addLabels($input: AddLabelsInput!) { addLabels(input: $input) { labels { id labelType { id name } } error { message type code } } }",
"variables": {
"input": {
"threadId": "<thread-id>",
"labelTypeIds": ["<label-type-id>"]
}
}
}
```
Then run:
```bash
curl -s -X POST "https://core-api.uk.plain.com/graphql/v1" --header "Authorization: Bearer $PLAIN_TOKEN" --header "Content-Type: application/json" -d @/tmp/plain_request.json
```
## Error Handling
All mutations return an `error` field. Always check it — a `200` HTTP response can still contain an error:
```json
{
"data": {
"replyToThread": {
"timelineEntry": null,
"error": {
"message": "Thread not found",
"type": "NOT_FOUND",
"code": "thread_not_found"
}
}
}
}
```
## Guidelines
1. **GraphQL only**: All requests go to `POST https://core-api.uk.plain.com/graphql/v1`
2. **Check `error` fields**: Mutations return errors in the response body, not via HTTP status codes
3. **Pagination**: Use Relay-style cursor pagination — pass `first` and `after` (from `pageInfo.endCursor`) for subsequent pages
4. **Machine User permissions**: Grant the minimum permissions needed (e.g., `threads:read`, `customers:write`)
5. **Thread statuses**: `TODO` (needs action), `SNOOZED` (waiting), `DONE` (resolved)
6. **Full schema**: Available at `https://core-api.uk.plain.com/graphql/v1/schema.graphql`
## API Reference
- GraphQL endpoint: `https://core-api.uk.plain.com/graphql/v1`
- Schema: `https://core-api.uk.plain.com/graphql/v1/schema.graphql`
- Documentation: `https://www.plain.com/docs/graphql/introduction`