Skip to content

GraphQL Schema

ThinkWork exposes a GraphQL API with two endpoints:

  • API Gateway — Queries and mutations. HTTP POST. Protected by Cognito JWT.
  • AppSync — Real-time subscriptions. WebSocket. Protected by Cognito JWT.

Both endpoints share the same schema. Use API Gateway for most operations, and connect to AppSync when you need live updates (streaming agent responses, thread status changes).

Terminal window
# From CLI outputs:
thinkwork outputs -s dev
# API Gateway (queries + mutations)
POST https://<id>.execute-api.<region>.amazonaws.com/graphql
# AppSync (subscriptions)
wss://<id>.appsync-realtime-api.<region>.amazonaws.com/graphql
# HTTP (for queries/mutations via AppSync if preferred)
https://<id>.appsync-api.<region>.amazonaws.com/graphql

All API calls require a Cognito JWT in the Authorization header:

Terminal window
curl -X POST https://<api-gw-url>/graphql \
-H "Authorization: Bearer <cognito-id-token>" \
-H "Content-Type: application/json" \
-d '{"query": "{ listAgents { items { id name } } }"}'

Obtain a token by signing in to Cognito:

Terminal window
aws cognito-idp initiate-auth \
--auth-flow USER_PASSWORD_AUTH \
--client-id <cognito-client-id> \
--auth-parameters USERNAME=user@example.com,PASSWORD=password \
--query 'AuthenticationResult.IdToken' \
--output text
type Agent {
id: ID!
name: String!
description: String
status: AgentStatus! # created | active | suspended | archived
systemPrompt: String
templateId: ID
template: AgentTemplate
skillPackIds: [ID!]
knowledgeBaseId: ID
rateLimits: RateLimits
createdAt: AWSDateTime!
updatedAt: AWSDateTime!
}
type Thread {
id: ID! # e.g. "CHAT-0042"
threadUUID: ID! # Internal UUID
prefix: String! # e.g. "CHAT"
channel: ThreadChannel! # CHAT | AUTO | EMAIL | SLACK | GITHUB | TASK | EVAL
status: ThreadStatus! # open | closed | failed | waiting
title: String
agentId: ID!
agent: Agent
messages(limit: Int, nextToken: String): MessageConnection!
metadata: AWSJSON
tokenCount: Int # Approximate total tokens in thread
createdAt: AWSDateTime!
updatedAt: AWSDateTime!
}
type Message {
id: ID!
threadId: ID!
role: MessageRole! # user | assistant | system | tool
body: String! # Markdown text
toolCalls: [ToolCall!]
toolResults: [ToolResult!]
tokenCount: Int
model: String # Bedrock model ID (assistant messages only)
createdAt: AWSDateTime!
}
# List all active agents
query ListAgents {
listAgents(filter: { status: active }, limit: 20) {
items {
id
name
status
template {
modelId
}
}
nextToken
}
}
# Get a single agent
query GetAgent {
getAgent(id: "agent-abc123") {
id
name
systemPrompt
skillPackIds
knowledgeBaseId
rateLimits {
turnsPerMinute
turnsPerHour
turnsPerDay
}
}
}
# List threads
query ListThreads {
listThreads(filter: {
channel: CHAT
status: open
agentId: "agent-abc123"
}, limit: 20) {
items {
id
title
status
updatedAt
}
nextToken
}
}
# Get thread with messages
query GetThread {
getThread(id: "CHAT-0042") {
id
title
status
channel
metadata
messages(limit: 50) {
items {
id
role
body
createdAt
tokenCount
}
}
}
}
# Create a thread
mutation CreateThread {
createThread(input: {
channel: CHAT
title: "Support request"
agentId: "agent-support"
metadata: "{\"source\": \"web\"}"
}) {
id
prefix
status
}
}
# Send a message (triggers agent invoke)
mutation SendMessage {
sendMessage(input: {
threadId: "CHAT-0042"
body: "How do I reset my password?"
}) {
id
role
body
createdAt
}
}
# Close a thread
mutation CloseThread {
updateThread(id: "CHAT-0042", input: { status: closed }) {
id
status
updatedAt
}
}
mutation CreateAgent {
createAgent(input: {
name: "Support Bot"
systemPrompt: "You are a helpful support agent."
templateId: "tpl-claude-sonnet"
skillPackIds: ["sp-jira", "sp-confluence"]
knowledgeBaseId: "kb-support"
}) {
id
status
}
}
mutation UpdateAgent {
updateAgent(id: "agent-abc123", input: {
systemPrompt: "Updated system prompt."
skillPackIds: ["sp-jira"]
}) {
id
updatedAt
}
}

Connect to AppSync WebSocket to receive real-time events. All subscriptions require Cognito JWT authentication via the header connection parameter.

subscription OnThreadEvent {
onThreadEvent(threadId: "CHAT-0042") {
... on NewMessageEvent {
message {
id
role
body
createdAt
}
}
... on AgentStatusEvent {
agentId
status # thinking | calling_tool | streaming | idle
activity # Human-readable activity description
}
... on ThreadUpdateEvent {
thread {
id
status
updatedAt
}
}
... on StreamChunkEvent {
messageId
chunk # Partial text chunk (for streaming responses)
done # True when streaming is complete
}
}
}
subscription OnAgentActivity {
onAgentActivity(agentId: "agent-support") {
threadId
status
activity
timestamp
}
}
EventPayloadWhen emitted
NewMessageEventFull message objectWhen a message is added to the thread (user or agent)
AgentStatusEventagentId, status, activityWhen agent state changes (thinking, tool call, done)
ThreadUpdateEventThread objectWhen thread status, title, or metadata changes
StreamChunkEventmessageId, chunk, doneAs the agent streams each text chunk
StatusMeaning
idleAgent is not processing
thinkingAgent is waiting for Bedrock response
calling_toolAgent is executing a tool call
streamingAgent is streaming the final response
errorAgent encountered an error

List queries return a Connection type with items and nextToken:

type ThreadConnection {
items: [Thread!]!
nextToken: String # Pass to next request to get the next page
}

Pass nextToken to paginate:

query ListThreadsPage2 {
listThreads(limit: 20, nextToken: "eyJpZCI6Ikc...") {
items { id title }
nextToken # null when no more pages
}
}

GraphQL errors follow the standard format:

{
"errors": [
{
"message": "Thread CHAT-9999 not found",
"errorType": "NotFound",
"locations": [{ "line": 2, "column": 3 }],
"path": ["getThread"]
}
]
}

Common error types:

errorTypeHTTP statusMeaning
Unauthorized401Missing or invalid JWT
Forbidden403JWT valid but insufficient permissions
NotFound404Resource doesn’t exist
ValidationError400Input validation failed
RateLimitExceeded429Agent rate limit hit
BudgetExceeded429Agent budget exceeded
InternalError500Unexpected server error