Organisations & Staff API
The organisations endpoints live under /api/v1/organizations and cover the
organisation record itself plus the staff lifecycle: listing members, inviting
people, changing roles and employment settings, deactivating members, and bulk
user imports from a spreadsheet.
Authentication: every request needs a per-user API key. See API Keys & Authentication.
Role requirements below describe Runnit’s default role permissions (owner, admin, manager, member, freelancer). Organisations that use custom roles may grant access differently.
GET /api/v1/organizations/my
Section titled “GET /api/v1/organizations/my”Lists every organisation the key’s owner is an active member of, with the membership details for each. Any authenticated user can call it.
Example request
curl https://api.runnit.io/api/v1/organizations/my \ -H 'Authorization: Bearer rnk_your_key'Response is a JSON array of membership records:
[ { "id": "<membership-uuid>", "userId": "<user-uuid>", "organizationId": "<org-uuid>", "role": "admin", "isPrimary": true, "isFreelancer": false, "hourlyRate": null, "currency": "AUD", "joinedAt": "2025-03-02T04:11:09.000Z", "isActive": true, "organization": { "id": "<org-uuid>", "name": "Southbank Studio", "slug": "southbank-studio", "logoUrl": null } }]This endpoint is informational. Even though it lists all your organisations, the key still only works against the one it was created in.
GET /api/v1/organizations/users
Section titled “GET /api/v1/organizations/users”Returns a simplified list of users in the key owner’s primary organisation. Any authenticated user can call it.
Example request
curl https://api.runnit.io/api/v1/organizations/users \ -H 'Authorization: Bearer rnk_your_key'Response is a JSON array; each entry contains id, email, firstName,
lastName, displayName, avatarUrl, currentTitle, phone, skills,
preferredWorkTypes, and status.
Errors
| Status | When |
|---|---|
404 | The key’s owner is not an active member of any organisation. |
GET /api/v1/organizations/:id
Section titled “GET /api/v1/organizations/:id”Returns the organisation record. Any active member of the organisation can call it.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | UUID | Yes | The organisation id. Must be the organisation the key is bound to. |
Example request
curl https://api.runnit.io/api/v1/organizations/<org-id> \ -H 'Authorization: Bearer rnk_your_key'Response
{ "id": "<org-uuid>", "name": "Southbank Studio", "slug": "southbank-studio", "description": "Creative production studio.", "url": "https://southbank.example.com", "email": "hello@southbank.example.com", "phone": "+61 3 9000 0000", "addressLine1": "12 Example Street", "city": "Melbourne", "country": "Australia", "currency": "AUD", "tier": "standard", "settings": { "dailyCapacityHours": 8 }, "status": "active", "createdAt": "2024-11-20T22:14:00.000Z", "updatedAt": "2026-06-30T01:05:44.000Z"}Errors
| Status | When |
|---|---|
404 | No organisation exists with that id. |
PATCH /api/v1/organizations/:id
Section titled “PATCH /api/v1/organizations/:id”Updates the organisation’s details. Only organisation admins and owners can call it.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | UUID | Yes | The organisation id. |
Request body (all fields optional; send only what you want to change)
| Name | Type | Required | Description |
|---|---|---|---|
name | string | No | Organisation display name. |
description | string | No | Free-text description. |
settings | object | No | Organisation settings. settings.dailyCapacityHours must be a number between 1 and 24 when supplied. |
Example request
curl -X PATCH https://api.runnit.io/api/v1/organizations/<org-id> \ -H 'Authorization: Bearer rnk_your_key' \ -H 'Content-Type: application/json' \ -d '{ "description": "Creative production studio in Melbourne.", "settings": { "dailyCapacityHours": 7.5 } }'Response is the full updated organisation record, in the same shape as
GET /api/v1/organizations/:id.
Errors
| Status | When |
|---|---|
400 | settings.dailyCapacityHours is outside 1 to 24. |
404 | No organisation exists with that id. |
GET /api/v1/organizations/:id/stats
Section titled “GET /api/v1/organizations/:id/stats”Returns simple headcount statistics. Any active member can call it.
Example request
curl https://api.runnit.io/api/v1/organizations/<org-id>/stats \ -H 'Authorization: Bearer rnk_your_key'Response
{ "memberCount": 24, "projectCount": 0, "activeProjects": 0 }memberCount is the number of active members. projectCount and
activeProjects are reserved fields and currently always return 0; use the
projects endpoints for project counts.
GET /api/v1/organizations/:id/members
Section titled “GET /api/v1/organizations/:id/members”Lists the organisation’s members as membership records with user details. Any active member can call it.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
isActive | string | No | Pass true to return only active members. Omit it to include deactivated members. |
role | string | No | Filter to one role: owner, admin, manager, member, or freelancer. |
isFreelancer | string | No | Pass true to return only freelancers. |
Example request
curl 'https://api.runnit.io/api/v1/organizations/<org-id>/members?isActive=true&role=freelancer' \ -H 'Authorization: Bearer rnk_your_key'Response is a JSON array of membership records:
[ { "id": "<membership-uuid>", "userId": "<user-uuid>", "organizationId": "<org-uuid>", "role": "freelancer", "isPrimary": false, "isFreelancer": true, "hourlyRate": 120, "currency": "AUD", "joinedAt": "2025-09-14T00:30:00.000Z", "isActive": true, "user": { "id": "<user-uuid>", "email": "sam@example.com", "firstName": "Sam", "lastName": "Nguyen", "displayName": "Sam Nguyen", "avatarUrl": null } }]POST /api/v1/organizations/:id/invite
Section titled “POST /api/v1/organizations/:id/invite”Invites a person to the organisation by email. Managers, admins, and owners can call it. Runnit emails the person an invitation link; the invitation expires after 2 days. If the email address has no Runnit account yet, a placeholder account is created and activated when they accept.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
email | string | Yes | A valid email address. |
role | string | Yes | Role to grant on acceptance: owner, admin, manager, member, or freelancer. |
isFreelancer | boolean | No | Mark the new member as a freelancer. Defaults to false. |
hourlyRate | number | No | Billable hourly rate to record for the new member. |
currency | string | No | Currency for hourlyRate. Defaults to USD. |
Example request
curl -X POST https://api.runnit.io/api/v1/organizations/<org-id>/invite \ -H 'Authorization: Bearer rnk_your_key' \ -H 'Content-Type: application/json' \ -d '{ "email": "sam@example.com", "role": "member" }'Response
{ "message": "Invitation sent successfully", "invitationToken": "<token>" }The person accepts the invitation through the Runnit app; there is no public API endpoint for accepting it on their behalf.
Errors
| Status | When |
|---|---|
400 | email is not a valid address, or role is not one of the five roles. |
409 | The person is already an active member of the organisation. |
PATCH /api/v1/organizations/:id/members/:userId
Section titled “PATCH /api/v1/organizations/:id/members/:userId”Changes a member’s role. Only organisation admins and owners can call it.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | UUID | Yes | The organisation id. |
userId | UUID | Yes | The member’s user id. |
Request body
| Name | Type | Required | Description |
|---|---|---|---|
role | string | Yes | New role: owner, admin, manager, member, or freelancer. |
Example request
curl -X PATCH https://api.runnit.io/api/v1/organizations/<org-id>/members/<user-id> \ -H 'Authorization: Bearer rnk_your_key' \ -H 'Content-Type: application/json' \ -d '{ "role": "manager" }'Response
{ "message": "Role updated successfully" }Errors
| Status | When |
|---|---|
400 | role is not one of the five roles. |
404 | The user has no membership record in the organisation. |
PATCH /api/v1/organizations/:id/members/:userId/settings
Section titled “PATCH /api/v1/organizations/:id/members/:userId/settings”Updates a member’s employment settings: the freelancer flag and their billable hourly rate. Managers, admins, and owners can call it.
Request body (at least one field is required)
| Name | Type | Required | Description |
|---|---|---|---|
isFreelancer | boolean | No | Whether the member is a freelancer. |
hourlyRate | number or null | No | Billable hourly rate, 0 or higher. Pass null to clear it. |
currency | string | No | Three-letter currency code, for example AUD. Stored in upper case. |
Example request
curl -X PATCH https://api.runnit.io/api/v1/organizations/<org-id>/members/<user-id>/settings \ -H 'Authorization: Bearer rnk_your_key' \ -H 'Content-Type: application/json' \ -d '{ "isFreelancer": true, "hourlyRate": 135, "currency": "AUD" }'Response
{ "id": "<membership-uuid>", "userId": "<user-uuid>", "organizationId": "<org-uuid>", "isFreelancer": true, "hourlyRate": 135, "currency": "AUD"}Errors
| Status | When |
|---|---|
400 | No settings supplied, hourlyRate is negative, or currency is not exactly three characters. |
404 | The user is not an active member of the organisation. |
DELETE /api/v1/organizations/:id/members/:userId
Section titled “DELETE /api/v1/organizations/:id/members/:userId”Deactivates a member, which is how you archive staff. Only organisation admins and owners can call it. The membership record is kept for history, but the person immediately loses access to the organisation and any API keys they created in it stop working.
Example request
curl -X DELETE https://api.runnit.io/api/v1/organizations/<org-id>/members/<user-id> \ -H 'Authorization: Bearer rnk_your_key'Response is 204 No Content.
Errors
| Status | When |
|---|---|
404 | The user has no membership record in the organisation. |
GET /api/v1/organizations/:id/users
Section titled “GET /api/v1/organizations/:id/users”Lists members with their full user profile details, including rate fields. Any active member can call it.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
role | string | No | Filter to one role: owner, admin, manager, member, or freelancer. |
isActive | string | No | Defaults to true. Pass any other value to include deactivated members. |
page | integer | No | Echoed back in the response. Defaults to 1. |
limit | integer | No | Echoed back in the response. Defaults to 100. |
All matching members are returned in a single response; page and limit are
included in the response body but do not currently slice the result.
Example request
curl 'https://api.runnit.io/api/v1/organizations/<org-id>/users?role=manager' \ -H 'Authorization: Bearer rnk_your_key'Response
{ "members": [ { "id": "<membership-uuid>", "userId": "<user-uuid>", "organizationId": "<org-uuid>", "role": "manager", "isFreelancer": false, "hourlyRate": null, "currency": "AUD", "isActive": true, "user": { "id": "<user-uuid>", "email": "priya@example.com", "firstName": "Priya", "lastName": "Shah", "displayName": "Priya Shah", "avatarUrl": null, "currentTitle": "Senior Producer", "shortBio": "Producer with a broadcast background.", "skills": ["production", "editing"], "yearsInIndustry": 9, "hourlyRate": 160, "actualCostRate": 95, "defaultRateCardRoleId": "<rate-card-role-uuid>", "status": "active", "lastLoginAt": "2026-07-06T23:41:12.000Z" } } ], "total": 1, "page": 1, "limit": 100}PATCH /api/v1/organizations/:id/users/:userId/rate-settings
Section titled “PATCH /api/v1/organizations/:id/users/:userId/rate-settings”Updates a member’s internal cost rate and their default rate-card role. Managers, admins, and owners can call it.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
actualCostRate | number or null | No | Internal cost rate used for margin calculations, 0 or higher. Pass null to clear it. |
defaultRateCardRoleId | UUID or null | No | An active rate-card role in the organisation (see the Rate Cards API). Pass null to clear it. |
Example request
curl -X PATCH https://api.runnit.io/api/v1/organizations/<org-id>/users/<user-id>/rate-settings \ -H 'Authorization: Bearer rnk_your_key' \ -H 'Content-Type: application/json' \ -d '{ "actualCostRate": 95, "defaultRateCardRoleId": "<rate-card-role-uuid>" }'Response
{ "id": "<user-uuid>", "actualCostRate": 95, "defaultRateCardRoleId": "<rate-card-role-uuid>", "hourlyRate": 160}Errors
| Status | When |
|---|---|
400 | defaultRateCardRoleId does not refer to an active rate-card role in the organisation. |
404 | The user is not an active member of the organisation. |
Bulk user imports
Section titled “Bulk user imports”Import staff from a spreadsheet in three steps: upload a file to build a preview, check the preview, then commit it. You can also roll back the most recent committed import. All four endpoints require an organisation admin or owner.
Supported file types are .xlsx, .xls, .xlsm, and .csv, up to 50 MB.
The import recognises these columns and maps common header names to them
automatically: email, firstName, lastName, displayName, phone,
currentTitle, shortBio, skills, yearsInIndustry, role,
isFreelancer, hourlyRate, and currency.
POST /api/v1/organizations/:id/user-imports/preview
Section titled “POST /api/v1/organizations/:id/user-imports/preview”Uploads a spreadsheet and returns a preview of what the import would do,
without changing anything. Send the file either as multipart/form-data with
a file field, or as JSON with a base64-encoded payload.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
file | file | Yes, for multipart | The spreadsheet, as a multipart form field. |
fileName | string | Yes, for JSON | The file name, including its extension. Not needed with multipart upload. |
fileDataBase64 | string | Yes, for JSON | The file content, base64 encoded. Not needed with multipart upload. |
mimeType | string | No | The file’s MIME type, when sending JSON. |
mappingOverrides | object | No | Map of spreadsheet column name to a recognised field name, or null to ignore that column. Overrides the automatic mapping. |
longBioSourceColumns | array of strings | No | Column names whose text is combined into each person’s long bio. |
With multipart upload, pass mappingOverrides and longBioSourceColumns as
JSON-encoded strings.
Example request
curl -X POST https://api.runnit.io/api/v1/organizations/<org-id>/user-imports/preview \ -H 'Authorization: Bearer rnk_your_key' \ -F 'file=@staff.xlsx' \ -F 'mappingOverrides={"Rate (AUD)":"hourlyRate"}'Response is 201 with the preview batch:
{ "batchId": "<batch-uuid>", "status": "preview_ready", "fileName": "staff.xlsx", "mapping": { "Email": { "field": "email", "source": "synonym", "confidence": null } }, "longBioSourceColumns": [], "detectedColumns": [{ "...": "..." }], "llmAnalysis": { "...": "..." }, "summary": { "totalRows": 18, "create": 15, "update": 2, "error": 1 }, "rows": [{ "...": "one entry per spreadsheet row with the planned operation" }]}Errors
| Status | When |
|---|---|
400 | No file supplied, unsupported file type, the file exceeds 50 MB, or mappingOverrides / longBioSourceColumns is not valid JSON. |
GET /api/v1/organizations/:id/user-imports/:batchId
Section titled “GET /api/v1/organizations/:id/user-imports/:batchId”Returns the batch’s current status, mapping, summaries, and per-row detail.
curl https://api.runnit.io/api/v1/organizations/<org-id>/user-imports/<batch-id> \ -H 'Authorization: Bearer rnk_your_key'Response contains batchId, status, fileName, mapping,
longBioSourceColumns, llmAnalysis, previewSummary, commitSummary,
committedAt, rolledBackAt, and rows. Returns 404 if the batch does not
belong to the organisation.
POST /api/v1/organizations/:id/user-imports/:batchId/commit
Section titled “POST /api/v1/organizations/:id/user-imports/:batchId/commit”Applies a previewed batch: creates and updates the users it described. No request body.
curl -X POST https://api.runnit.io/api/v1/organizations/<org-id>/user-imports/<batch-id>/commit \ -H 'Authorization: Bearer rnk_your_key'Response
{ "batchId": "<batch-uuid>", "status": "committed", "summary": { "inserted": 15, "updated": 2, "skipped": 0, "errored": 1 }}Errors
| Status | When |
|---|---|
400 | The batch is not in the preview-ready state (already committed, or rolled back). |
404 | The batch does not belong to the organisation. |
POST /api/v1/organizations/:id/user-imports/rollback-latest
Section titled “POST /api/v1/organizations/:id/user-imports/rollback-latest”Rolls back the most recently committed import batch. No request body.
curl -X POST https://api.runnit.io/api/v1/organizations/<org-id>/user-imports/rollback-latest \ -H 'Authorization: Bearer rnk_your_key'Response contains batchId, status (rolled_back), and a summary of
what was reversed.
Errors
| Status | When |
|---|---|
404 | No committed import batch is available to roll back. |
Other endpoints
Section titled “Other endpoints”The organisations router also exposes endpoints the Runnit app uses for browser sessions: logo upload and retrieval, kanban column configuration, slug availability checks, choosing a primary organisation, leaving an organisation, and accepting invitations. They are not documented here because they are app concerns rather than integration surface.
Next step
Section titled “Next step”Manage billing rates with the Rate Cards API, or client relationships with the Clients API.