Developer Docs

GraphQL API

Query exactly the data you need in a single request. Build complex UIs efficiently with the GraphQL API.

GraphQL API

The TRCR GraphQL API gives you the flexibility to request exactly the data your application needs, reducing over-fetching and enabling efficient data loading for complex UIs.

Endpoint: POST https://api.trcr.pro/graphql

Authentication

Include a Bearer token in the Authorization header, the same as with the REST API:

POST /graphql HTTP/1.1
Host: api.trcr.pro
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json

{
  "query": "{ me { id name email } }"
}

Making Requests

curl -X POST https://api.trcr.pro/graphql \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"query":"{ me { id name email } }"}'

Schema Overview

The schema exposes queries for reading data and mutations for writing data. All types use the same IDs as the REST API.

Queries

User

# Current authenticated user
query {
  me {
    id
    name
    email
    avatar_url
    created_at
  }
}

Organizations

# List organizations
query {
  organizations {
    id
    name
    slug
    settings {
      timezone
      currency
    }
    members {
      id
      user { id name email }
      role
    }
  }
}

# Single organization
query {
  organization(id: "org_abc123") {
    id
    name
    members { id role }
  }
}

Projects

# List projects with filters
query {
  projects(status: "active", page: 1, perPage: 10) {
    data {
      id
      name
      description
      color
      billable
      hourly_rate
      client { id name }
      members { id user { name } role }
      task_count
      total_hours
    }
    pagination {
      total
      page
      per_page
      total_pages
    }
  }
}

# Single project with related data
query {
  project(id: "prj_abc123") {
    id
    name
    tasks(status: "open") {
      id
      title
      status
      assignee { name }
    }
    time_entries(from: "2026-03-01", to: "2026-03-31") {
      id
      description
      duration
      user { name }
    }
  }
}

Tasks

# List tasks with nested data
query {
  tasks(
    projectId: "prj_abc"
    status: "open"
    sort: "priority"
    order: "desc"
    page: 1
    perPage: 25
  ) {
    data {
      id
      title
      description
      status
      priority
      start_date
      due_date
      estimated_hours
      assignee { id name avatar_url }
      labels { id name color }
      comments_count
      progress
      complexity
      group_id
      checklists {
        id
        title
        items { text checked }
      }
      dependencies { id title status }
      time_entries {
        id
        duration
        user { name }
      }
      created_at
      updated_at
    }
    pagination { total page per_page total_pages }
  }
}

# Single task
query {
  task(id: "tsk_abc123") {
    id
    title
    description
    comments {
      id
      body
      user { name avatar_url }
      created_at
    }
  }
}

# All task dependencies in an organization (one call for the whole Gantt).
# Returns only edges where both endpoint tasks are visible to the caller
# (admins see all; members see edges within their accessible projects).
query {
  allTaskDependencies(orgId: "org_abc123") {
    id
    taskId
    dependsOnId
    dependencyType   # blocks | blocked_by
    createdAt
  }
}

Time Entries

# List time entries
query {
  timeEntries(
    from: "2026-03-01"
    to: "2026-03-31"
    projectId: "prj_abc"
  ) {
    data {
      id
      description
      start_time
      end_time
      duration
      billable
      user { id name }
      project { id name color }
      task { id title }
    }
    pagination { total page }
  }
}

# Currently running timer
query {
  runningTimer {
    id
    description
    start_time
    project { id name }
    task { id title }
  }
}

Invoices

# List invoices
query {
  invoices(status: "sent", clientId: "cli_abc") {
    data {
      id
      number
      status
      issue_date
      due_date
      subtotal
      tax_amount
      total
      currency
      client { id name email }
      line_items {
        id
        description
        quantity
        unit_price
        total
      }
      payments {
        id
        amount
        method
        paid_at
      }
    }
    pagination { total page }
  }
}

Clients

# List clients with summaries
query {
  clients(search: "acme") {
    data {
      id
      name
      email
      phone
      currency
      hourly_rate
      contacts { id name email role }
      projects_count
      total_invoiced
      total_paid
      outstanding_balance
    }
    pagination { total page }
  }
}

Reports

# Timesheet report
query {
  timesheetReport(from: "2026-03-01", to: "2026-03-31", groupBy: "week") {
    rows {
      period
      user { id name }
      hours
      billable_hours
    }
    totals {
      hours
      billable_hours
    }
  }
}

# Revenue report
query {
  revenueReport(from: "2026-01-01", to: "2026-12-31", groupBy: "month") {
    rows {
      period
      invoiced
      paid
      outstanding
    }
    totals { invoiced paid outstanding }
  }
}

# Utilization report
query {
  utilizationReport(from: "2026-03-01", to: "2026-03-31") {
    rows {
      user { id name }
      tracked_hours
      available_hours
      utilization_percent
    }
  }
}

# Profitability report
query {
  profitabilityReport(from: "2026-01-01", to: "2026-03-31", groupBy: "project") {
    rows {
      entity { id name }
      revenue
      cost
      profit
      margin_percent
    }
  }
}

Task Checklists

# List checklist items for a task
query {
  taskChecklists(orgId: "org_abc123", taskId: "tsk_abc123") {
    id
    task_id
    title
    completed
    position
    created_at
  }
}

Dashboard Summary

# Dashboard summary for an organization (scoped to current user)
query {
  dashboardSummary(orgId: "org_abc123") {
    total_tasks
    done_tasks
    overdue_count
    overdue_urgent_tasks {
      id
      title
      status
      priority
      due_date
      project_id
      assignee_id
    }
  }
}

Project Stats

# Task counts per project for an organization
query {
  projectStats(orgId: "org_abc123") {
    project_id
    total
    done
  }
}

Sidebar Collapsed Nodes

# List sidebar node IDs that are collapsed for the current user
query {
  sidebarCollapsedNodes(orgId: "org_abc123")
}

Task Groups

# List task groups for a project
query {
  taskGroups(orgId: "org_abc123", projectId: "prj_abc123") {
    id
    name
    is_default
    position
    created_at
  }
}

# Get collapsed task group IDs for the current user
query {
  collapsedTaskGroups(orgId: "org_abc123", projectId: "prj_abc123")
}

Task Statuses

# List custom task statuses for an organization
query {
  taskStatuses(orgId: "org_abc123") {
    id
    code
    label
    color
    position
    is_terminal
    supports_progress
    progress_editable
    on_complete
    next_status_code
    is_default
  }
}

Task Recurrences

# Get the recurrence rule for a task
query {
  taskRecurrence(orgId: "org_abc123", taskId: "tsk_abc123") {
    id
    task_id
    recurrence_pattern
    next_occurrence_date
    last_generated_date
    stopped_at
  }
}

Milestones

# List a Space's milestones (ordered by target date)
query {
  milestones(orgId: "org_abc123", projectId: "prj_abc123") {
    id
    organizationId
    projectId
    title
    targetDate
    color
    description
    createdAt
    updatedAt
  }
}

Search

# Unified search
query {
  search(q: "website redesign", type: "task", limit: 10) {
    results {
      type
      id
      title
      description
      score
    }
  }
}

Notifications

query {
  notifications(unread: true) {
    data {
      id
      type
      title
      entity_type
      entity_id
      read
      created_at
    }
    unread_count
  }
}

Mutations

Tasks

CreateTaskInput.startDate and UpdateTaskInput.startDate are optional Date fields (formatted YYYY-MM-DD), mirroringdueDate. TaskType.startDate exposes the value on reads. On update, startDate is MaybeUndefined: omit it to leave the value unchanged, or send null to clear it. startDate must be on or before dueDate, otherwise the mutation returns a validation error (start_date must be on or before due_date).

# Create a task
mutation {
  createTask(input: {
    title: "Implement search"
    projectId: "prj_abc"
    priority: "high"
    assigneeId: "usr_xyz"
    startDate: "2026-04-01"
    dueDate: "2026-04-15"
    labelIds: ["lbl_feature"]
  }) {
    id
    title
    status
    priority
    startDate
    dueDate
  }
}

# Update a task
mutation {
  updateTask(id: "tsk_abc123", input: {
    status: "done"
    priority: "low"
    startDate: "2026-04-02"
  }) {
    id
    status
    priority
    startDate
    dueDate
    updated_at
  }
}

# Delete a task
mutation {
  deleteTask(id: "tsk_abc123") {
    success
  }
}

# Add a comment
mutation {
  addTaskComment(taskId: "tsk_abc123", body: "LGTM! Merging.") {
    id
    body
    user { name }
    created_at
  }
}

Time Entries

# Start a timer
mutation {
  startTimer(input: {
    projectId: "prj_abc"
    description: "Working on search feature"
  }) {
    id
    description
    start_time
    project { name }
  }
}

# Stop the running timer
mutation {
  stopTimer {
    id
    description
    start_time
    end_time
    duration
  }
}

# Create a manual time entry
mutation {
  createTimeEntry(input: {
    projectId: "prj_abc"
    description: "Client meeting"
    startTime: "2026-03-28T09:00:00Z"
    endTime: "2026-03-28T10:30:00Z"
    billable: true
  }) {
    id
    duration
    billable
  }
}

# Update a time entry
mutation {
  updateTimeEntry(id: "te_abc123", input: {
    description: "Updated description"
    billable: false
  }) {
    id
    description
    billable
  }
}

# Delete a time entry
mutation {
  deleteTimeEntry(id: "te_abc123") {
    success
  }
}

Projects

# Create a project
mutation {
  createProject(input: {
    name: "Website Redesign"
    clientId: "cli_abc"
    billable: true
    hourlyRate: 15000
    color: "#6366f1"
  }) {
    id
    name
    color
  }
}

# Update a project
mutation {
  updateProject(id: "prj_abc123", input: {
    status: "archived"
  }) {
    id
    status
  }
}

# Add a member
mutation {
  addProjectMember(projectId: "prj_abc", userId: "usr_xyz", role: "member") {
    id
    user { name }
    role
  }
}

# Remove a member
mutation {
  removeProjectMember(projectId: "prj_abc", userId: "usr_xyz") {
    success
  }
}

Invoices

# Create an invoice
mutation {
  createInvoice(input: {
    clientId: "cli_abc"
    dueDate: "2026-04-30"
    taxRate: 21
  }) {
    id
    number
    status
    total
  }
}

# Add a line item
mutation {
  addInvoiceLineItem(invoiceId: "inv_abc", input: {
    description: "Frontend development"
    quantity: 10.5
    unitPrice: 15000
  }) {
    id
    description
    total
  }
}

# Generate from time entries
mutation {
  generateInvoiceLines(invoiceId: "inv_abc", from: "2026-03-01", to: "2026-03-31") {
    line_items {
      id
      description
      quantity
      unit_price
    }
    total
  }
}

# Send invoice
mutation {
  sendInvoice(id: "inv_abc", message: "Please find attached.") {
    id
    status
    sent_at
  }
}

# Mark as paid
mutation {
  markInvoicePaid(id: "inv_abc", paymentMethod: "bank_transfer") {
    id
    status
    paid_at
  }
}

Clients

# Create a client
mutation {
  createClient(input: {
    name: "Acme Corp"
    email: "billing@acme.com"
    currency: "USD"
    hourlyRate: 15000
  }) {
    id
    name
  }
}

# Update a client
mutation {
  updateClient(id: "cli_abc", input: {
    hourlyRate: 17500
  }) {
    id
    hourly_rate
  }
}

# Add a contact
mutation {
  addClientContact(clientId: "cli_abc", input: {
    name: "John Smith"
    email: "john@acme.com"
    role: "CTO"
  }) {
    id
    name
    email
  }
}

Organizations and Members

# Create organization
mutation {
  createOrganization(input: { name: "Acme Corp" }) {
    id
    name
    slug
  }
}

# Invite a member
mutation {
  inviteMember(email: "newuser@example.com", role: "member") {
    id
    email
    role
    status
  }
}

# Update member role
mutation {
  updateMemberRole(memberId: "mem_abc", role: "admin") {
    id
    role
  }
}

# Remove member
mutation {
  removeMember(memberId: "mem_abc") {
    success
  }
}

Checklists

# Delete a checklist item (requires manage_tasks permission)
mutation {
  deleteChecklist(orgId: "org_abc123", itemId: "item_abc123")
}

Task Groups

# Create a task group
mutation {
  createTaskGroup(orgId: "org_abc123", projectId: "prj_abc123", input: {
    name: "Backlog"
  }) {
    id
    name
    position
  }
}

# Update a task group
mutation {
  updateTaskGroup(orgId: "org_abc123", projectId: "prj_abc123", groupId: "grp_001", input: {
    name: "Sprint Backlog"
  }) {
    id
    name
  }
}

# Delete a task group
mutation {
  deleteTaskGroup(orgId: "org_abc123", projectId: "prj_abc123", groupId: "grp_001") {
    success
  }
}

# Reorder task groups
mutation {
  reorderTaskGroups(orgId: "org_abc123", projectId: "prj_abc123", order: ["grp_002", "grp_001"]) {
    success
  }
}

# Toggle collapsed state for a task group
mutation {
  toggleCollapsedTaskGroup(orgId: "org_abc123", projectId: "prj_abc123", groupId: "grp_001")
}

Task Statuses

# Create a custom task status
mutation {
  createTaskStatus(orgId: "org_abc123", input: {
    code: "qa_review"
    label: "QA Review"
    color: "#8b5cf6"
    supportsProgress: true
    onComplete: "auto_advance"
    nextStatusCode: "done"
  }) {
    id
    code
    label
    color
    position
  }
}

# Update a task status
mutation {
  updateTaskStatus(orgId: "org_abc123", id: "ts_abc123", input: {
    label: "Code Review"
    color: "#6366f1"
  }) {
    id
    label
    color
  }
}

# Delete a task status
mutation {
  deleteTaskStatus(orgId: "org_abc123", id: "ts_abc123") {
    success
  }
}

# Reorder task statuses
mutation {
  reorderTaskStatuses(orgId: "org_abc123", order: ["ts_001", "ts_002", "ts_003"]) {
    success
  }
}

Task Recurrences

# Create a recurrence rule for a task
mutation {
  createTaskRecurrence(orgId: "org_abc123", taskId: "tsk_abc123", input: {
    recurrencePattern: "weekly"
  }) {
    id
    recurrence_pattern
    next_occurrence_date
  }
}

# Update a recurrence rule
mutation {
  updateTaskRecurrence(orgId: "org_abc123", recurrenceId: "rec_abc123", input: {
    recurrencePattern: "monthly"
  }) {
    id
    recurrence_pattern
  }
}

# Stop a recurrence
mutation {
  deleteTaskRecurrence(orgId: "org_abc123", recurrenceId: "rec_abc123") {
    success
  }
}

Sidebar

# Toggle a sidebar node's collapsed state
# Returns true if the node is now collapsed, false if expanded
mutation {
  toggleSidebarCollapsedNode(orgId: "org_abc123", nodeId: "prj_abc123")
}

Labels

mutation {
  createLabel(input: { name: "Bug", color: "#ef4444" }) {
    id
    name
    color
  }
}

mutation {
  updateLabel(id: "lbl_abc", input: { color: "#f97316" }) {
    id
    color
  }
}

mutation {
  deleteLabel(id: "lbl_abc") {
    success
  }
}

Milestones

CreateMilestoneInput takes projectId, title, and a targetDate (a Date formatted YYYY-MM-DD), plus an optional color (hex, defaults to #6366f1) and description. On UpdateMilestoneInput every field is optional;description is MaybeUndefined: omit it to leave the value unchanged, or send null to clear it. Reads require access to the Space; create, update, and delete require manage_tasks permission on the Space.

# Create a milestone
mutation {
  createMilestone(orgId: "org_abc123", input: {
    projectId: "prj_abc123"
    title: "Public beta"
    targetDate: "2026-07-01"
    color: "#22c55e"
    description: "optional"
  }) {
    id
    projectId
    title
    targetDate
    color
  }
}

# Update a milestone
mutation {
  updateMilestone(orgId: "org_abc123", id: "mil_abc123", input: {
    title: "Public beta launch"
    targetDate: "2026-07-08"
  }) {
    id
    title
    targetDate
    updatedAt
  }
}

# Delete a milestone
mutation {
  deleteMilestone(orgId: "org_abc123", id: "mil_abc123") {
    success
  }
}

Chat

# Send a message
mutation {
  sendChatMessage(channelId: "ch_abc", content: "Deployment complete!") {
    id
    content
    user { name }
    created_at
  }
}

# Create a channel
mutation {
  createChatChannel(input: {
    name: "engineering"
    isPrivate: false
    memberIds: ["usr_abc", "usr_xyz"]
  }) {
    id
    name
  }
}

Notifications

mutation {
  markNotificationsRead(ids: ["ntf_001", "ntf_002"]) {
    success
    unread_count
  }
}

mutation {
  updateNotificationPreferences(input: {
    emailEnabled: true
    taskAssigned: true
    chatMentioned: true
  }) {
    email_enabled
    task_assigned
    chat_mentioned
  }
}

Variables

Use variables to keep queries clean and avoid string interpolation:

curl -X POST https://api.trcr.pro/graphql \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "mutation CreateTask($input: CreateTaskInput!) { createTask(input: $input) { id title } }",
    "variables": {
      "input": {
        "title": "New feature",
        "projectId": "prj_abc",
        "priority": "high"
      }
    }
  }'

Error Handling

GraphQL errors are returned in the errors array:

{
  "data": null,
  "errors": [
    {
      "message": "Task not found",
      "path": ["task"],
      "extensions": {
        "code": "NOT_FOUND"
      }
    }
  ]
}

Partial errors are possible -- some fields may resolve while others fail. Always check the errors array even when data is present.

Rate Limits

The GraphQL endpoint is rate-limited to 100 requests per minute per user, the same as REST.

Depth and Complexity Limits

To prevent abuse, queries are limited to:

  • Max depth: 10 levels of nesting
  • Max complexity: 500 points (each field costs 1 point, list fields cost 10)
  • Max aliases: 20 per query

Exceeding these limits returns an error before execution:

{
  "errors": [{
    "message": "Query complexity 750 exceeds maximum allowed 500",
    "extensions": { "code": "QUERY_TOO_COMPLEX" }
  }]
}

Introspection

Schema introspection is enabled. You can explore the schema using tools like GraphQL Playground, Apollo Studio, or Insomnia by pointing them at https://api.trcr.pro/graphql.

# Introspect the schema
query {
  __schema {
    types {
      name
      description
      fields { name type { name } }
    }
  }
}