REST API · v1

DeskChime Developer API

Pull feedback results, goal progress, meeting records, and team responsiveness into your own tools — authenticated with a single API key.

12 endpoints60 req/minJSON responsesx-api-key auth

Your API Key

Paste your workspace API key to enable the Try-it panels below. Stored only in this browser tab — never sent to our servers from this page.

Don't have one? Get an API key →

Overview

The DeskChime REST API gives you read access to your organization's data — feedback, goals, meetings, one-on-ones, users, and activity reports — so you can push it into your own dashboards, BI tools, or HR platforms.

Base URL

https://deskchime.com

Format

JSON (UTF-8)

Timestamps

UTC ISO 8601

Authentication

Every request must include your organization's API key in the x-api-key header. Generate your key in Settings → Workspace → API Key (Admin role required). Regenerating a key immediately invalidates the previous one.

curl
curl "https://deskchime.com/api/v1/feedback" \
  -H "x-api-key: YOUR_API_KEY"
401

Missing header

x-api-key header not present

401

Invalid format

Key format is not recognized

401

Key not found

Key doesn't match any organization

403

Cross-org access

User/resource belongs to a different org

400

Invalid filter

Unknown enum value or unparseable date in a query param

404

Not found

No record with that id in your organization

Rate Limits

All /api/v1/* endpoints allow 60 requests per 60 seconds per organization (burst-friendly — not strictly 1/sec). When the limit is exceeded you receive a 429. Check Retry-After and back off.

Response headerMeaning
X-RateLimit-LimitMaximum requests allowed in the window (60)
X-RateLimit-RemainingRequests remaining in the current window
X-RateLimit-ResetISO timestamp when the window resets
Retry-AfterSeconds to wait — only present on 429 responses

Organization

2 endpoints
GET/api/workspace-stats

High-level counts for your organization — goals, users, teams, meetings, feedback, and more. Cancelled meetings and archived goals are excluded; pendingReviews counts feedback cycles that have not closed yet.

curl

curl "https://deskchime.com/api/workspace-stats" \
  -H "x-api-key: YOUR_API_KEY"

Example response

{
  "success": true,
  "organization": {
    "id": 1,
    "name": "Acme Inc.",
    "slug": "acme"
  },
  "stats": {
    "totalGoals": 12,
    "totalUsers": 8,
    "totalTeams": 3,
    "totalReviews": 24,
    "totalMeetings": 47
  }
}
POST/api/workspace-stats/user

Stats for a specific user — feedback given/received, goals, meetings, and applaudes. One-on-ones count meetings the member organized or took part in.

Parameters

NameInTypeRequiredDescription
emailbodystringrequiredUser email address

curl

curl -X POST "https://deskchime.com/api/workspace-stats/user" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]"}'

Example response

{
  "success": true,
  "user": {
    "id": "u1",
    "email": "[email protected]",
    "name": "Jane"
  },
  "stats": {
    "feedbacksGiven": 3,
    "feedbacksReceived": 5,
    "totalGoals": 4,
    "completedGoals": 2,
    "oneOnOnes": {
      "total": 8,
      "completed": 7
    }
  }
}

Users

1 endpoint
GET/api/v1/users

All users in your organization with their roles and status. Filter by role. state is "active", "invited" (pending invitation) or "deactivated"; deactivated members stay in the list with deactivatedAt set so integrations can see who left and when. The existing integer status field is retained for backwards compatibility, but state is preferred. managers[] is always an array, [] when the user has no managers — DeskChime has no single manager column, a manager is a TeamManager of any team the user is a TeamMember of, and both relations are many-to-many, so a user can have zero, one, or several managers. Only direct team membership counts; team hierarchy is not walked. Scoped to your organization's teams, and a user is never listed as their own manager.

Parameters

NameInTypeRequiredDescription
pagequerynumberoptionalPage number (default: 1)
limitquerynumberoptionalResults per page, max 50 (default: 20)
rolequerystringoptionalFilter by role: SuperAdmin | Admin | Manager | Member. Any other value returns 400.

curl

curl "https://deskchime.com/api/v1/users?page=1&limit=20&role=Manager" \
  -H "x-api-key: YOUR_API_KEY"

Example response

{
  "data": [
    {
      "id": "u1",
      "name": "Jane Smith",
      "email": "[email protected]",
      "role": "Admin",
      "status": 1,
      "state": "active",
      "deactivatedAt": null,
      "isOnboarded": true,
      "joinedAt": "2024-09-01T10:00:00Z",
      "managers": []
    },
    {
      "id": "u2",
      "name": "Bob Lee",
      "email": "[email protected]",
      "role": "Member",
      "status": 0,
      "state": "invited",
      "deactivatedAt": null,
      "isOnboarded": true,
      "joinedAt": "2024-10-15T09:30:00Z",
      "managers": [
        {
          "id": "u5",
          "name": "Priya Nair",
          "email": "[email protected]"
        }
      ]
    }
  ],
  "meta": {
    "page": 1,
    "limit": 20,
    "total": 8,
    "totalPages": 1,
    "hasMore": false
  }
}

Feedback

3 endpoints
GET/api/v1/feedback

Paginated list of feedback forms in your organization. responseCount counts live responses only — withdrawn ones are not included. email, userId, and assignee filter to cycles where the named user is an assignee — assignee is an accepted alias for userId, kept for integrations already sending it. Supply only one of the three; supplying more than one returns 400. An unknown user returns 404, and a user outside your organization returns 403. This is unrelated to status, which keeps filtering the raw Review.status column exactly as before. Omitting email, userId, and assignee lists feedback across the whole organization.

Parameters

NameInTypeRequiredDescription
pagequerynumberoptionalPage number (default: 1)
limitquerynumberoptionalResults per page, max 50 (default: 20)
statusquerystringoptionalFilter by the raw lifecycle flag. Feedback is created as "pending" and stays there — use closeDate to tell whether a cycle has closed.
emailquerystringoptionalFilter to cycles where this member is an assignee. Provide only one of email, userId, or assignee — supplying more than one returns 400. An unknown user returns 404, and a user outside your organization returns 403.
userIdquerystringoptionalFilter to cycles where this member is an assignee, by DeskChime user id. Stable across email changes. Same either/or rule as email.
assigneequerystringoptionalAccepted alias for userId, kept for existing integrations already sending assignee. Same either/or rule as email and userId — combining it with either returns 400.

curl

curl "https://deskchime.com/api/v1/feedback?page=1&limit=20&status=pending&email=jane%40acme.com" \
  -H "x-api-key: YOUR_API_KEY"

Example response

{
  "data": [
    {
      "id": "abc",
      "title": "Q1 Review",
      "status": "pending",
      "frequency": "Once",
      "assigneeCount": 5,
      "responseCount": 3,
      "createdAt": "2025-01-15T10:00:00Z"
    }
  ],
  "meta": {
    "page": 1,
    "limit": 20,
    "total": 42,
    "totalPages": 3,
    "hasMore": true
  }
}
GET/api/v1/feedback/:id

Single feedback form with an anonymised per-question response summary (no individual names).

Parameters

NameInTypeRequiredDescription
idpathstringrequiredFeedback ID

curl

curl "https://deskchime.com/api/v1/feedback/abc123" \
  -H "x-api-key: YOUR_API_KEY"

Example response

{
  "data": {
    "id": "abc",
    "title": "Q1 Review",
    "status": "completed",
    "assigneeCount": 8,
    "responseCount": 6,
    "questions": [
      {
        "id": 1,
        "question": "Rate communication",
        "type": "scale",
        "responseCount": 6,
        "averageScore": 4.2,
        "scale": {
          "min": "1",
          "max": "5"
        }
      },
      {
        "id": 2,
        "question": "What's working well?",
        "type": "textarea",
        "responseCount": 5
      }
    ]
  }
}
GET/api/v1/feedback/requests

Lists individual outstanding feedback requests, one row per assignee per cycle, with a derived status — status is never stored, it's computed as pending, answered, or expired, and the same predicate drives the query filter, so meta.total and paging stay correct for a filtered request rather than counting the unfiltered set. Omitting status returns all three. Anonymous feedback cycles are excluded from data[] entirely — that's a privacy boundary, not an oversight — and show up only as the aggregate meta.excludedAnonymous count, so meta.total + meta.excludedAnonymous reconciles against stats.feedback.received from the activity endpoint for the same user and month. Returns identity, titles, and timestamps only — never feedback content, answers, or ratings. cycleTitle maps to Review.review_name, an optional column, so it can be null. cycleDescription comes from the cycle's form record and is null when unset. subjectUserId, subjectName, and subjectEmail identify the respondent — the person the request is on, i.e. who is being asked — while requestedByUserId/requestedByName are the creator of the cycle. On a single-user query (email or userId) subjectUserId necessarily equals the user you queried for, so it only carries new information on org-wide calls. respondedAt is set only once the request has actually been answered, and stays null otherwise. Omitting both email and userId lists outstanding requests across the whole organization.

Parameters

NameInTypeRequiredDescription
emailquerystringoptionalFilter to one member's requests. Either email or userId, not both — supplying both returns 400. An unknown user returns 404, and a user outside your organization returns 403.
userIdquerystringoptionalFilter to one member's requests by DeskChime user id. Stable across email changes. Same either/or rule as email.
statusquerystringoptionalpending | answered | expired. Any other value returns 400. Omit to return all three.
monthquerystringoptionalYYYY-MM. Filters on when the request was raised, not when it's due. Resolved in the member's own timezone when you filter by user, otherwise the platform default. Malformed values return 400.
pagequerynumberoptionalPage number (default: 1)
limitquerynumberoptionalResults per page, max 50 (default: 20)

curl

curl "https://deskchime.com/api/v1/feedback/requests?email=jane%40acme.com&status=pending&month=2025-04&page=1&limit=20" \
  -H "x-api-key: YOUR_API_KEY"

Example response

{
  "data": [
    {
      "id": "ra1",
      "cycleTitle": "Q1 Peer Review",
      "cycleDescription": "Quarterly peer feedback for engineering",
      "subjectUserId": "u2",
      "subjectName": "Bob Lee",
      "subjectEmail": "[email protected]",
      "requestedByUserId": "u1",
      "requestedByName": "Jane Smith",
      "requestedAt": "2025-01-10T09:00:00Z",
      "dueAt": "2025-01-24T23:59:59Z",
      "status": "pending",
      "respondedAt": null,
      "url": "https://app.deskchime.com/acme/feedback/id/ra1"
    }
  ],
  "meta": {
    "page": 1,
    "limit": 20,
    "total": 14,
    "totalPages": 1,
    "hasMore": false,
    "excludedAnonymous": 3
  }
}

Goals

2 endpoints
GET/api/v1/goals

Paginated list of goals with progress and assignees. Filter by status or type. Archived goals are excluded. email and userId filter to goals where the named user is an assignee — either one, not both; supplying both returns 400, an unknown user returns 404, and a user outside your organization returns 403. month filters on the goal's end date (Goals.end_date), not on when it was assigned, and end_date is nullable, so a goal with no end date never matches a month filter. Omitting email, userId, and month lists goals across the whole organization.

Parameters

NameInTypeRequiredDescription
pagequerynumberoptionalPage number
limitquerynumberoptionalMax 50
statusquerystringoptionalOnTrack | Completed | Delayed | AtRisk | Abandoned. Any other value returns 400.
typequerystringoptionalOrganization | Individual | Team | Self. Any other value returns 400.
emailquerystringoptionalFilter to goals where this member is an assignee. Either email or userId, not both — supplying both returns 400. An unknown user returns 404, and a user outside your organization returns 403.
userIdquerystringoptionalFilter to goals where this member is an assignee, by DeskChime user id. Stable across email changes. Same either/or rule as email.
monthquerystringoptionalYYYY-MM. Filters on the goal's end date, not on when it was assigned — end_date is nullable, so a goal with no end date never matches. Resolved in the filtered member's own timezone when you filter by email or userId, otherwise the platform default. Malformed values return 400.

curl

curl "https://deskchime.com/api/v1/goals?page=1&limit=20&status=OnTrack&type=Individual&email=jane%40acme.com&month=2025-04" \
  -H "x-api-key: YOUR_API_KEY"

Example response

{
  "data": [
    {
      "id": "g1",
      "title": "Grow ARR by 30%",
      "type": "Organization",
      "status": "OnTrack",
      "progress": 45,
      "assignees": [
        {
          "name": "Jane",
          "email": "[email protected]"
        }
      ]
    }
  ],
  "meta": {
    "page": 1,
    "limit": 20,
    "total": 8,
    "totalPages": 1,
    "hasMore": false
  }
}
GET/api/v1/goals/:id

Single goal with full timeline of progress updates and actionable items.

Parameters

NameInTypeRequiredDescription
idpathstringrequiredGoal ID

curl

curl "https://deskchime.com/api/v1/goals/g1" \
  -H "x-api-key: YOUR_API_KEY"

Example response

{
  "data": {
    "id": "g1",
    "title": "Grow ARR by 30%",
    "status": "OnTrack",
    "progress": 45,
    "timeline": [
      {
        "status": "OnTrack",
        "comment": "Closed 3 new accounts",
        "date": "2025-03-10T09:00:00Z"
      }
    ],
    "actionItems": [
      {
        "id": "a1",
        "title": "Close enterprise deal",
        "status": "OnTrack"
      }
    ]
  }
}

Meetings

1 endpoint
GET/api/v1/meetings

Paginated list of meetings. Filter by type or date range. Cancelled meetings are excluded.

Parameters

NameInTypeRequiredDescription
pagequerynumberoptionalPage number
limitquerynumberoptionalMax 50
typequerystringoptionalGoal | Review | Casual | ONE_ON_ONE | Booking. Any other value returns 400.
fromquerystringoptionalAccepts YYYY-MM-DD (resolved in the platform default timezone — this endpoint has no per-user filter), YYYY-MM-DDTHH:mm[:ss[.sss]] (same, read as wall-clock), or either with a trailing Z or ±HH:MM/±HHMM offset (honoured exactly as given). Loosely-formatted values like 2026-8-1 or 2026/08/01 return 400 rather than being guessed at.
toquerystringoptionalSame accepted forms as from. A bare date is the upper bound — it covers the whole day through 23:59:59.999 rather than stopping at midnight. Loosely-formatted values like 2026-8-1 or 2026/08/01 return 400.

curl

curl "https://deskchime.com/api/v1/meetings?page=1&limit=20&type=Review&from=2025-01-01&to=2025-03-31" \
  -H "x-api-key: YOUR_API_KEY"

Example response

{
  "data": [
    {
      "id": "m1",
      "title": "Sprint Retro",
      "type": "Casual",
      "scheduledAt": "2025-03-15T14:00:00Z",
      "isCompleted": true,
      "attendeeCount": 6
    }
  ],
  "meta": {
    "page": 1,
    "limit": 20,
    "total": 47
  }
}

One-on-Ones

1 endpoint
GET/api/v1/one-on-ones

Paginated list of one-on-one meetings with organizer, participant, and full participants[] details. Cancelled sessions are excluded by default — set includeCancelled=true to include them, returned with status: "cancelled". participants[] always contains exactly two entries, since a DeskChime 1-on-1 is always two people; role is "owner" (whoever booked the session) or "attendee". isCurrentMember is false when that person has since left or been removed from the workspace — their past sessions are still returned deliberately, because the meeting genuinely happened, so you can tell "this person left" apart from "I could not match this person". status is exactly "completed" | "scheduled" | "cancelled" — there is no no-show state. completedAt was only introduced in May 2026 and is null for most sessions completed before then; use status to tell held from booked, and scheduledAt to attribute a session to a month — do not rely on completedAt for historical data. title has the participant's name substituted into the stored template ($name), relative to whichever member you filtered by via email or userId — with no user filter it falls back to the organizer's own perspective. titleTemplate carries the raw stored string with the $name placeholder intact.

Parameters

NameInTypeRequiredDescription
pagequerynumberoptionalPage number
limitquerynumberoptionalMax 50
fromquerystringoptionalAccepts YYYY-MM-DD (resolved in the member's own timezone, or the platform default when no user filter is supplied), YYYY-MM-DDTHH:mm[:ss[.sss]] (same, read as wall-clock), or either with a trailing Z or ±HH:MM/±HHMM offset (honoured exactly as given). Loosely-formatted values like 2026-8-1 or 2026/08/01 return 400 rather than being guessed at.
toquerystringoptionalSame accepted forms as from. A bare date is the upper bound — it covers the whole day through 23:59:59.999 rather than stopping at midnight, not just its first instant. Loosely-formatted values like 2026-8-1 or 2026/08/01 return 400.
heldFromquerystringoptionalFilters on when the session was completed, not booked — combinable with month/from/to, and a session that was never completed can never match. Accepts YYYY-MM-DD (resolved in the member's own timezone, or the platform default with no user filter), YYYY-MM-DDTHH:mm[:ss[.sss]], or either with a trailing Z or ±HH:MM/±HHMM offset. Loosely-formatted values like 2026-8-1 or 2026/08/01 return 400.
heldToquerystringoptionalSame accepted forms as heldFrom, as the upper bound — a bare date covers the whole day through 23:59:59.999 rather than stopping at midnight. Combinable with month/from/to; a session that was never completed can never match.
statusquerystringoptionalscheduled | completed | cancelled. Any other value returns 400. status=cancelled returns cancelled sessions directly — you don't additionally need includeCancelled=true.
emailquerystringoptionalFilter to one member's sessions. Either email or userId, not both — supplying both returns 400. An unknown user returns 404.
userIdquerystringoptionalFilter to one member's sessions by DeskChime user id. Stable across email changes.
monthquerystringoptionalYYYY-MM. Resolved in the member's own timezone. Takes precedence over from/to when both are supplied. Malformed values return 400.
includeCancelledquerystringoptionalSet to true to include cancelled sessions, which are otherwise omitted.

curl

curl "https://deskchime.com/api/v1/one-on-ones?page=1&limit=20&from=2025-01-01&to=2025-03-31&heldFrom=2025-01-01&heldTo=2025-03-31&status=completed&month=2025-04&includeCancelled=true" \
  -H "x-api-key: YOUR_API_KEY"

Example response

{
  "data": [
    {
      "id": "613d3206-a5c4-4bcd-bb1f-a05e94968212",
      "title": "1-on-1 with Aaditya verma",
      "titleTemplate": "1-on-1 with $name",
      "frequency": "Monthly",
      "scheduledAt": "2024-11-05T10:30:00Z",
      "endsAt": "2024-11-05T11:00:00Z",
      "completedAt": null,
      "status": "completed",
      "isCompleted": true,
      "createdAt": "2024-07-03T04:03:07Z",
      "organizer": {
        "id": "u1",
        "name": "Anil sharma",
        "email": "[email protected]"
      },
      "participant": {
        "id": "u2",
        "name": "Aaditya verma",
        "email": "[email protected]"
      },
      "participants": [
        {
          "id": "u1",
          "name": "Anil sharma",
          "email": "[email protected]",
          "role": "owner",
          "isCurrentMember": true
        },
        {
          "id": "u2",
          "name": "Aaditya verma",
          "email": "[email protected]",
          "role": "attendee",
          "isCurrentMember": false
        }
      ]
    }
  ],
  "meta": {
    "page": 1,
    "limit": 20,
    "total": 18
  }
}

User Activity

2 endpoints
POST/api/v1/users/activity

Activity counts and responsiveness rates for a team member. Rates are whole percentages, or null when there is nothing to measure. feedback.received counts individual feedback requests, so each cycle of a recurring form counts once. applaudes.systemGenerated counts applauds received that were generated automatically by the system rather than sent by a colleague — it is a subset of applaudes.received, not a separate bucket; applaudes.received minus applaudes.systemGenerated is the colleague-sent count. meetings.assigned counts meetings that actually took place; a member counts as attending unless they explicitly declined. oneOnOnes.total/completed and meetings.assigned/attended are evaluated as of asOf (a top-level ISO instant in the response), not over the whole month — they grow if you call again later in the same month, because a 1-on-1 scheduled for later this month is not yet a missed one. scheduledInMonth, on both stats.oneOnOnes and stats.meetings, is the same count without that as-of clamp, and it's the figure that reconciles with GET /api/v1/one-on-ones for a whole month. The relationship isn't the same on both: for oneOnOnes, scheduledInMonth differs from total only by the asOf clamp; for meetings, assigned/attended additionally require the meeting to have concluded, so scheduledInMonth differs from them by the clamp and that completion requirement together — don't assume one relationship explains the other. An unrecognised user returns 404, and a user outside your organization returns 403 — so a zero-filled 200 always means a genuine month of no activity. Month boundaries resolve in the member's own timezone.

Parameters

NameInTypeRequiredDescription
emailbodystringoptionalUser email address. Either email or userId, not both — supplying both, or neither, returns 400.
userIdbodystringoptionalDeskChime user id. Either email or userId, not both — supplying both, or neither, returns 400. Stable across email changes.
monthbodystringoptionalRestrict to one calendar month, YYYY-MM. Month boundaries follow the member's own timezone. Omit for all-time.

curl

curl -X POST "https://deskchime.com/api/v1/users/activity" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]","userId":"","month":"2025-03"}'

Example response

{
  "user": {
    "id": "u1",
    "name": "Jane",
    "email": "[email protected]"
  },
  "organization": {
    "id": 1
  },
  "period": "2025-03",
  "asOf": "2025-03-14T09:12:44Z",
  "stats": {
    "feedback": {
      "given": 3,
      "received": 10,
      "answered": 8
    },
    "goals": {
      "total": 5,
      "completed": 3
    },
    "meetings": {
      "created": 4,
      "assigned": 12,
      "attended": 11,
      "scheduledInMonth": 15
    },
    "applaudes": {
      "given": 6,
      "received": 4,
      "systemGenerated": 2
    },
    "oneOnOnes": {
      "total": 8,
      "completed": 7,
      "scheduledInMonth": 9
    }
  },
  "responsiveness": {
    "feedbackResponseRate": 80,
    "goalCompletionRate": 60,
    "meetingAttendanceRate": 92,
    "oneOnOneCompletionRate": 88
  }
}
GET/api/v1/organizations/:id/activity

Returns the same per-user activity payload as POST /api/v1/users/activity, but for every member of the organization in one paginated call — a nightly sync can make one or two requests instead of one per employee. Each element of data[] is identical in shape to the single-user response, so existing parsing code can be reused in a loop, except asOf is carried per element rather than once at the top level: users on a page are computed one at a time, so each one's asOf is captured at a slightly different instant, and a single top-level asOf would overstate that precision. applaudes.systemGenerated counts applauds received that were generated automatically by the system rather than sent by a colleague — it is a subset of applaudes.received, not a separate bucket; applaudes.received minus applaudes.systemGenerated is the colleague-sent count. oneOnOnes.total/completed and meetings.assigned/attended are evaluated as of that element's own asOf, not over the whole month — they grow on a later call in the same month, because a 1-on-1 scheduled for later this month is not yet a missed one. scheduledInMonth, on both stats.oneOnOnes and stats.meetings, is the same count without that as-of clamp, and it's the figure that reconciles with GET /api/v1/one-on-ones for a whole month. The relationship isn't the same on both: for oneOnOnes, scheduledInMonth differs from total only by the asOf clamp; for meetings, assigned/attended additionally require the meeting to have concluded, so scheduledInMonth differs from them by the clamp and that completion requirement together — don't assume one relationship explains the other. :id must match the organization your API key belongs to — any other id returns 403.

Parameters

NameInTypeRequiredDescription
idpathstringrequiredYour organization id. Must match the organization your API key belongs to, or the request returns 403.
monthquerystringoptionalYYYY-MM. Omit for all-time figures. Each member's month is resolved in their own timezone.
pagequerynumberoptionalPage number (default: 1)
limitquerynumberoptionalResults per page, max 25 (default: 25)

curl

curl "https://deskchime.com/api/v1/organizations/3/activity?month=2025-04&page=1&limit=25" \
  -H "x-api-key: YOUR_API_KEY"

Example response

{
  "data": [
    {
      "user": {
        "id": "u1",
        "name": "Jane",
        "email": "[email protected]"
      },
      "asOf": "2025-04-10T06:45:00Z",
      "stats": {
        "feedback": {
          "given": 4,
          "received": 12,
          "answered": 9
        },
        "goals": {
          "total": 6,
          "completed": 4
        },
        "meetings": {
          "created": 5,
          "assigned": 0,
          "attended": 0,
          "scheduledInMonth": 3
        },
        "applaudes": {
          "given": 7,
          "received": 5,
          "systemGenerated": 2
        },
        "oneOnOnes": {
          "total": 9,
          "completed": 8,
          "scheduledInMonth": 11
        }
      },
      "responsiveness": {
        "feedbackResponseRate": 75,
        "goalCompletionRate": 67,
        "meetingAttendanceRate": null,
        "oneOnOneCompletionRate": 89
      }
    }
  ],
  "meta": {
    "total": 30,
    "period": "2025-04",
    "page": 1,
    "limit": 25,
    "totalPages": 2,
    "hasMore": true
  }
}