This document provides the necessary information to implement a client for the ARP (Agent-native ERP) GraphQL server.
The ARP server exposes a GraphQL API for managing users, roles, permissions, services, tasks, notes, channels, and messages. All operations except login require authentication via a JWT bearer token.
/query (GraphQL endpoint)application/jsonPOST for queries and mutations{
"query": "string (GraphQL query or mutation)",
"variables": "object (optional variables)",
"operationName": "string (optional operation name)"
}
login mutation with email and passwordAuthorization headermutation Login($email: String!, $password: String!) {
login(email: $email, password: $password) {
token
user {
id
email
roles {
id
name
permissions {
id
code
description
}
}
}
}
}
Variables:
{
"email": "user@example.com",
"password": "your-password"
}
Response:
{
"data": {
"login": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": "1",
"email": "user@example.com",
"roles": [...]
}
}
}
}
Include the token in all subsequent requests:
Authorization: Bearer <token>
| Property | Value |
|---|---|
| Algorithm | HS256 |
| Expiration | 10 years from issuance |
| Claims | user_id, email, roles, permissions |
Many operations require specific permissions. The permission format is resource:action.
| Operation | Required Permission |
|---|---|
updateUser |
user:update |
deleteUser |
user:delete |
updateNote |
note:update |
deleteNote |
note:delete |
updateRole |
role:update |
deleteRole |
role:delete |
updatePermission |
permission:update |
deletePermission |
permission:delete |
updateService |
service:update |
deleteService |
service:delete |
updateTask |
task:update |
deleteTask |
task:delete |
updateTaskStatus |
taskstatus:update |
deleteTaskStatus |
taskstatus:delete |
updateChannel |
channel:update |
deleteChannel |
channel:delete |
updateMessage |
message:update |
deleteMessage |
message:delete |
If authentication or authorization fails, the API returns an error:
{
"errors": [
{
"message": "unauthorized: authentication required"
}
]
}
Or for missing permissions:
{
"errors": [
{
"message": "unauthorized: missing user:update permission"
}
]
}
All queries require authentication.
# Get all users
query Users {
users {
id
email
roles {
id
name
}
createdAt
updatedAt
}
}
# Get single user by ID
query User($id: ID!) {
user(id: $id) {
id
email
roles {
id
name
permissions {
id
code
description
}
}
createdAt
updatedAt
}
}
# Get all notes
query Notes {
notes {
id
title
content
userId
user { id email }
serviceId
service { id name }
createdAt
updatedAt
}
}
# Get single note by ID
query Note($id: ID!) {
note(id: $id) {
id
title
content
userId
serviceId
createdAt
updatedAt
}
}
# Get all roles
query Roles {
roles {
id
name
description
permissions {
id
code
description
}
}
}
# Get single role by ID
query Role($id: ID!) {
role(id: $id) {
id
name
description
permissions {
id
code
}
}
}
# Get all permissions
query Permissions {
permissions {
id
code
description
}
}
# Get single permission by ID
query Permission($id: ID!) {
permission(id: $id) {
id
code
description
}
}
# Get all services
query Services {
services {
id
name
description
createdById
createdBy { id email }
participants { id email }
tasks { id title }
createdAt
updatedAt
}
}
# Get single service by ID
query Service($id: ID!) {
service(id: $id) {
id
name
description
participants { id email }
tasks { id title status { code label } }
createdAt
updatedAt
}
}
# Get all tasks
query Tasks {
tasks {
id
title
content
createdById
createdBy { id email }
updatedById
updatedBy { id email }
assigneeId
assignee { id email }
statusId
status { id code label }
dueDate
priority
createdAt
updatedAt
}
}
# Get single task by ID
query Task($id: ID!) {
task(id: $id) {
id
title
content
createdById
createdBy { id email }
updatedById
updatedBy { id email }
assignee { id email }
status { code label }
dueDate
priority
createdAt
updatedAt
}
}
# Get all task statuses
query TaskStatuses {
taskStatuses {
id
code
label
tasks { id title }
createdAt
updatedAt
}
}
# Get single task status by ID
query TaskStatus($id: ID!) {
taskStatus(id: $id) {
id
code
label
tasks { id title }
createdAt
updatedAt
}
}
# Get all channels
query Channels {
channels {
id
participants { id email }
createdAt
updatedAt
}
}
# Get single channel by ID
query Channel($id: ID!) {
channel(id: $id) {
id
participants { id email }
createdAt
updatedAt
}
}
# Get all messages
query Messages {
messages {
id
conversationId
senderId
sender { id email }
content
sentAt
createdAt
updatedAt
}
}
# Get single message by ID
query Message($id: ID!) {
message(id: $id) {
id
conversationId
senderId
sender { id email }
content
sentAt
createdAt
updatedAt
}
}
All mutations require authentication. Some require additional permissions (see Authorization section).
mutation Login($email: String!, $password: String!) {
login(email: $email, password: $password) {
token
user { id email }
}
}
# Create user
mutation CreateUser($input: NewUser!) {
createUser(input: $input) {
id
email
roles { id name }
createdAt
}
}
# Variables: { "input": { "email": "...", "password": "...", "roles": ["1", "2"] } }
# Update user (requires user:update permission)
mutation UpdateUser($id: ID!, $input: UpdateUserInput!) {
updateUser(id: $id, input: $input) {
id
email
roles { id name }
updatedAt
}
}
# Variables: { "id": "1", "input": { "email": "new@example.com" } }
# Delete user (requires user:delete permission)
mutation DeleteUser($id: ID!) {
deleteUser(id: $id)
}
# Create note
mutation CreateNote($input: NewNote!) {
createNote(input: $input) {
id
title
content
userId
serviceId
createdAt
}
}
# Variables: { "input": { "title": "...", "content": "...", "userId": "1", "serviceId": "1" } }
# Update note (requires note:update permission)
mutation UpdateNote($id: ID!, $input: UpdateNoteInput!) {
updateNote(id: $id, input: $input) {
id
title
content
updatedAt
}
}
# Delete note (requires note:delete permission)
mutation DeleteNote($id: ID!) {
deleteNote(id: $id)
}
# Create role
mutation CreateRole($input: NewRole!) {
createRole(input: $input) {
id
name
description
permissions { id code }
createdAt
}
}
# Variables: { "input": { "name": "...", "description": "...", "permissions": ["1", "2"] } }
# Update role (requires role:update permission)
mutation UpdateRole($id: ID!, $input: UpdateRoleInput!) {
updateRole(id: $id, input: $input) {
id
name
permissions { id code }
updatedAt
}
}
# Delete role (requires role:delete permission)
mutation DeleteRole($id: ID!) {
deleteRole(id: $id)
}
# Create permission
mutation CreatePermission($input: NewPermission!) {
createPermission(input: $input) {
id
code
description
createdAt
}
}
# Variables: { "input": { "code": "resource:action", "description": "..." } }
# Update permission (requires permission:update permission)
mutation UpdatePermission($id: ID!, $input: UpdatePermissionInput!) {
updatePermission(id: $id, input: $input) {
id
code
updatedAt
}
}
# Delete permission (requires permission:delete permission)
mutation DeletePermission($id: ID!) {
deletePermission(id: $id)
}
# Create service
mutation CreateService($input: NewService!) {
createService(input: $input) {
id
name
description
createdById
participants { id email }
createdAt
}
}
# Variables: { "input": { "name": "...", "description": "...", "createdById": "1", "participants": ["1", "2"] } }
# Update service (requires service:update permission)
mutation UpdateService($id: ID!, $input: UpdateServiceInput!) {
updateService(id: $id, input: $input) {
id
name
participants { id email }
updatedAt
}
}
# Delete service (requires service:delete permission)
mutation DeleteService($id: ID!) {
deleteService(id: $id)
}
# Create task
mutation CreateTask($input: NewTask!) {
createTask(input: $input) {
id
title
content
createdById
assigneeId
statusId
dueDate
priority
createdAt
}
}
# Variables: { "input": { "title": "...", "content": "...", "createdById": "1", "priority": "high" } }
# Update task (requires task:update permission)
# Note: updatedBy is automatically set to the current authenticated user
mutation UpdateTask($id: ID!, $input: UpdateTaskInput!) {
updateTask(id: $id, input: $input) {
id
title
status { code label }
assignee { id email }
updatedBy { id email }
dueDate
priority
updatedAt
}
}
# Delete task (requires task:delete permission)
mutation DeleteTask($id: ID!) {
deleteTask(id: $id)
}
# Create task status
mutation CreateTaskStatus($input: NewTaskStatus!) {
createTaskStatus(input: $input) {
id
code
label
createdAt
}
}
# Variables: { "input": { "code": "in_progress", "label": "In Progress" } }
# Update task status (requires taskstatus:update permission)
mutation UpdateTaskStatus($id: ID!, $input: UpdateTaskStatusInput!) {
updateTaskStatus(id: $id, input: $input) {
id
code
label
updatedAt
}
}
# Delete task status (requires taskstatus:delete permission)
mutation DeleteTaskStatus($id: ID!) {
deleteTaskStatus(id: $id)
}
# Create channel
mutation CreateChannel($input: NewChannel!) {
createChannel(input: $input) {
id
participants { id email }
createdAt
}
}
# Variables: { "input": { "participants": ["1", "2"] } }
# Update channel (requires channel:update permission)
mutation UpdateChannel($id: ID!, $input: UpdateChannelInput!) {
updateChannel(id: $id, input: $input) {
id
participants { id email }
updatedAt
}
}
# Delete channel (requires channel:delete permission)
mutation DeleteChannel($id: ID!) {
deleteChannel(id: $id)
}
# Create message
mutation CreateMessage($input: NewMessage!) {
createMessage(input: $input) {
id
conversationId
senderId
sender { id email }
content
sentAt
createdAt
}
}
# Variables: { "input": { "conversationId": "1", "senderId": "1", "content": "Hello!" } }
# Update message (requires message:update permission)
mutation UpdateMessage($id: ID!, $input: UpdateMessageInput!) {
updateMessage(id: $id, input: $input) {
id
content
updatedAt
}
}
# Delete message (requires message:delete permission)
mutation DeleteMessage($id: ID!) {
deleteMessage(id: $id)
}
Errors are returned in the standard GraphQL error format:
{
"errors": [
{
"message": "Error description",
"path": ["fieldName"],
"locations": [{ "line": 1, "column": 2 }]
}
],
"data": null
}
| Error Message | Cause |
|---|---|
unauthorized: authentication required |
Missing or invalid JWT token |
unauthorized: missing X:Y permission |
User lacks required permission |
invalid credentials |
Wrong email/password on login |
invalid X ID |
Malformed ID provided |
X not found |
Resource with given ID doesn't exist |
failed to create/update/delete X |
Database operation failed |
The API supports real-time updates via GraphQL subscriptions. Subscriptions use WebSocket connections and provide filtered events based on user context.
Subscriptions are filtered by user context. Users only receive events that are relevant to them:
| Subscription | Filtering Rule |
|---|---|
taskCreated |
Only if user is the assignee |
taskUpdated |
Only if user is the assignee |
taskDeleted |
Only if user is the assignee |
messageAdded |
Only if user is a participant in the channel |
This means:
# Task created - received only by assignee
subscription TaskCreated {
taskCreated {
id
title
content
assigneeId
status { code label }
priority
createdAt
}
}
# Task updated - received only by assignee
subscription TaskUpdated {
taskUpdated {
id
title
content
assigneeId
updatedBy { id email }
status { code label }
priority
updatedAt
}
}
# Task deleted - received only by assignee
subscription TaskDeleted {
taskDeleted {
id
title
assigneeId
}
}
# Message added - received by all channel participants
subscription MessageAdded {
messageAdded {
id
conversationId
senderId
sender { id email }
content
sentAt
}
}
To use subscriptions, establish a WebSocket connection:
/query (same as GraphQL endpoint)The server uses the standard GraphQL over WebSocket protocol (graphql-ws):
{
"type": "connection_init",
"payload": {
"Authorization": "Bearer <token>"
}
}
{
"id": "1",
"type": "start",
"payload": {
"query": "subscription { taskCreated { id title assigneeId } }"
}
}
{
"id": "1",
"type": "data",
"payload": {
"data": {
"taskCreated": {
"id": "5",
"title": "New Task",
"assigneeId": "2"
}
}
}
}
{
"id": "1",
"type": "stop"
}
Using the graphql-ws library:
import { createClient } from 'graphql-ws';
const client = createClient({
url: 'wss://api.example.com/query',
connectionParams: {
Authorization: `Bearer ${token}`,
},
});
// Subscribe to taskCreated events
const unsubscribe = client.subscribe(
{
query: `subscription { taskCreated { id title assigneeId } }`,
},
{
next: (data) => {
console.log('Task created:', data.data.taskCreated);
},
error: (error) => {
console.error('Subscription error:', error);
},
complete: () => {
console.log('Subscription closed');
},
}
);
// Later: unsubscribe
unsubscribe();
Using github.com/99designs/gqlgen/client:
import (
"github.com/99designs/gqlgen/client"
)
// Create WebSocket client with authentication
wsClient := client.New(server, client.AddHeader("Authorization", "Bearer "+token))
// Subscribe to taskCreated
subscription := wsClient.Websocket(`subscription { taskCreated { id title } }`)
defer subscription.Close()
for {
var response struct {
TaskCreated *model.Task `json:"taskCreated"`
}
err := subscription.Next(&response)
if err != nil {
break // Connection closed or error
}
if response.TaskCreated != nil {
fmt.Printf("Task created: %s\n", response.TaskCreated.Title)
}
}
| Use Case | Subscription | Notes |
|---|---|---|
| Task assignment notifications | taskCreated |
User receives event when assigned a new task |
| Task status updates | taskUpdated |
User receives event when their assigned task is modified |
| Task removal | taskDeleted |
User receives event when their assigned task is deleted |
| Chat messages | messageAdded |
All channel participants receive new message events |
| Type | Description |
|---|---|
ID |
Unique identifier (string representation) |
String |
UTF-8 string |
Boolean |
true or false |
Task Priority: low, medium, high (string values)
Task Status Codes: Customizable (e.g., open, in_progress, done)
All timestamps use ISO 8601 / RFC 3339 format:
2024-01-15T10:30:00Z
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Before performing privileged operations, verify the user has the required permission by checking the permissions array in the JWT claims or the user's roles.
POST /query HTTP/1.1
Host: api.example.com
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
{
"query": "query { users { id email } }"
}
curl -X POST https://api.example.com/query \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{"query": "query { users { id email } }"}'