Skip to content

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.

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

Terminal window
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.

Returns a simplified list of users in the key owner’s primary organisation. Any authenticated user can call it.

Example request

Terminal window
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

StatusWhen
404The key’s owner is not an active member of any organisation.

Returns the organisation record. Any active member of the organisation can call it.

Path parameters

NameTypeRequiredDescription
idUUIDYesThe organisation id. Must be the organisation the key is bound to.

Example request

Terminal window
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

StatusWhen
404No organisation exists with that id.

Updates the organisation’s details. Only organisation admins and owners can call it.

Path parameters

NameTypeRequiredDescription
idUUIDYesThe organisation id.

Request body (all fields optional; send only what you want to change)

NameTypeRequiredDescription
namestringNoOrganisation display name.
descriptionstringNoFree-text description.
settingsobjectNoOrganisation settings. settings.dailyCapacityHours must be a number between 1 and 24 when supplied.

Example request

Terminal window
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

StatusWhen
400settings.dailyCapacityHours is outside 1 to 24.
404No organisation exists with that id.

Returns simple headcount statistics. Any active member can call it.

Example request

Terminal window
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.

Lists the organisation’s members as membership records with user details. Any active member can call it.

Query parameters

NameTypeRequiredDescription
isActivestringNoPass true to return only active members. Omit it to include deactivated members.
rolestringNoFilter to one role: owner, admin, manager, member, or freelancer.
isFreelancerstringNoPass true to return only freelancers.

Example request

Terminal window
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
}
}
]

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

NameTypeRequiredDescription
emailstringYesA valid email address.
rolestringYesRole to grant on acceptance: owner, admin, manager, member, or freelancer.
isFreelancerbooleanNoMark the new member as a freelancer. Defaults to false.
hourlyRatenumberNoBillable hourly rate to record for the new member.
currencystringNoCurrency for hourlyRate. Defaults to USD.

Example request

Terminal window
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

StatusWhen
400email is not a valid address, or role is not one of the five roles.
409The 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

NameTypeRequiredDescription
idUUIDYesThe organisation id.
userIdUUIDYesThe member’s user id.

Request body

NameTypeRequiredDescription
rolestringYesNew role: owner, admin, manager, member, or freelancer.

Example request

Terminal window
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

StatusWhen
400role is not one of the five roles.
404The 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)

NameTypeRequiredDescription
isFreelancerbooleanNoWhether the member is a freelancer.
hourlyRatenumber or nullNoBillable hourly rate, 0 or higher. Pass null to clear it.
currencystringNoThree-letter currency code, for example AUD. Stored in upper case.

Example request

Terminal window
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

StatusWhen
400No settings supplied, hourlyRate is negative, or currency is not exactly three characters.
404The 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

Terminal window
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

StatusWhen
404The user has no membership record in the organisation.

Lists members with their full user profile details, including rate fields. Any active member can call it.

Query parameters

NameTypeRequiredDescription
rolestringNoFilter to one role: owner, admin, manager, member, or freelancer.
isActivestringNoDefaults to true. Pass any other value to include deactivated members.
pageintegerNoEchoed back in the response. Defaults to 1.
limitintegerNoEchoed 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

Terminal window
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

NameTypeRequiredDescription
actualCostRatenumber or nullNoInternal cost rate used for margin calculations, 0 or higher. Pass null to clear it.
defaultRateCardRoleIdUUID or nullNoAn active rate-card role in the organisation (see the Rate Cards API). Pass null to clear it.

Example request

Terminal window
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

StatusWhen
400defaultRateCardRoleId does not refer to an active rate-card role in the organisation.
404The user is not an active member of the organisation.

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

NameTypeRequiredDescription
filefileYes, for multipartThe spreadsheet, as a multipart form field.
fileNamestringYes, for JSONThe file name, including its extension. Not needed with multipart upload.
fileDataBase64stringYes, for JSONThe file content, base64 encoded. Not needed with multipart upload.
mimeTypestringNoThe file’s MIME type, when sending JSON.
mappingOverridesobjectNoMap of spreadsheet column name to a recognised field name, or null to ignore that column. Overrides the automatic mapping.
longBioSourceColumnsarray of stringsNoColumn names whose text is combined into each person’s long bio.

With multipart upload, pass mappingOverrides and longBioSourceColumns as JSON-encoded strings.

Example request

Terminal window
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

StatusWhen
400No 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.

Terminal window
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.

Terminal window
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

StatusWhen
400The batch is not in the preview-ready state (already committed, or rolled back).
404The 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.

Terminal window
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

StatusWhen
404No committed import batch is available to roll back.

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.

Manage billing rates with the Rate Cards API, or client relationships with the Clients API.