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,
"timezone": "Australia/Melbourne",
"requireBriefBuilderAiReviewBeforePlanning": true
},
"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. settings.timezone must be a valid IANA timezone such as Australia/Sydney; it controls the organisation calendar used by scheduling. settings.requireBriefBuilderAiReviewBeforePlanning is a boolean; absent or true makes Brief Builder wait for its current AI review before Planning, while false permits handoff during evaluation. settings.taskTable.defaultColumns sets the default visible columns of the project task table, as an ordered array of column ids (built-in ids such as status, assignee, hours, financial ids such as plannedCost, or custom:<key> for custom columns); null restores the standard layout. Kanban boards can override this per board.

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, or settings.timezone is not a valid IANA timezone.
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.

In a demo, sandbox, or training dataset, Runnit creates the invitation but doesn’t send the email. Check emailSent in the response before telling the person to look for it.

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.
customMessagestringNoPersonal note from the inviter, up to 500 characters. Included in the invitation email above the acceptance link.

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>",
"emailSent": true
}

If email isn’t delivered, including in a non-production dataset, the request still succeeds with emailSent: false and the message Invitation created without email delivery.

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, role is not one of the five roles, or customMessage is longer than 500 characters.
409The person is already an active member of the organisation and has signed in before.

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 and their organisation membership status. Any active member can call it. The rate fields (hourlyRate on the membership, hourlyRate and actualCostRate on the user) are financial data and only appear when the caller has financial-report access (managers and above by default); other callers receive the same records without those fields.

Query parameters

NameTypeRequiredDescription
rolestringNoFilter to one role: owner, admin, manager, member, or freelancer.
isActivestringNoDefaults to true. Pass false to return only inactive memberships.
includePendingInvitationsbooleanNoPass true to return active members and outstanding invitations together. Archived members are excluded and this option takes precedence over isActive.
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&includePendingInvitations=true' \
-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,
"membershipStatus": "active",
"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,
"industryStartDate": "2017-07-14",
"hourlyRate": 160,
"actualCostRate": 95,
"defaultRateCardRoleId": "<rate-card-role-uuid>",
"status": "active",
"lastLoginAt": "2026-07-06T23:41:12.000Z"
}
}
],
"total": 1,
"page": 1,
"limit": 100
}

membershipStatus is active, pending, or inactive. A pending invitation may have blank profile names when the email address doesn’t have a Runnit account yet. Use the nested user’s email to identify that row.

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. Both settings are scoped to this organisation: each organisation a person belongs to keeps its own cost rate and default role, and cost rate changes apply from today onwards (past time entries keep the rate they were priced with).

Request body

NameTypeRequiredDescription
actualCostRatenumber or nullNoInternal cost rate used for margin calculations, 0 or higher. Pass null to clear it from today onwards; historical entries keep their captured rate.
actualCostRateCurrencystringNoISO 4217 code for the cost rate (a London employee can cost in GBP inside an AUD organisation). Defaults to the organisation’s reporting currency.
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. A yearsInIndustry value is stored as the industry start date it implies (today minus the value), so the person’s years of experience keep increasing automatically after the import.

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.

Kanban boards are views over workflow stages. Every project sits in one stage; a board column shows one or more stages, and dropping a card moves the project to the column’s primary stage. Reading needs organisation membership; writing needs the kanban management permission.

GET /api/v1/organizations/:id/workflow-stages

Section titled “GET /api/v1/organizations/:id/workflow-stages”

Lists the organisation’s workflow stages. Each stage has id, name, category (draft, active, on_hold, done, or cancelled), statusValue (the project status it syncs to), displayOrder, and isSystem. System stages exist for every project status and cannot be deleted.

PUT /api/v1/organizations/:id/workflow-stages

Section titled “PUT /api/v1/organizations/:id/workflow-stages”

Bulk stage management. Send stages (rename or reorder existing stages by id; create new ones with name, category, and displayOrder) and deletions (each with stageId and reassignToStageId; projects on the deleted stage move to the reassignment stage first). A stage’s category is fixed after creation. Upserts apply atomically; creating or renaming to a name another stage already has returns 409. Deletions run after the upserts are saved, and any that fail are reported in a deletionErrors array instead of failing the request.

Lists the boards you can see. Users restricted to specific clients see the default board plus boards scoped to their accessible clients only.

Creates a board. Body: name, optional description, optional clientOrganizationId (must be an active client of the organisation), and columns (each with name, displayOrder, stageIds, and primaryStageId; a stage can appear in only one column per board).

You can create the stages a board needs in the same request. Send stages with new entries carrying a tempKey, name, and category, then reference those tempKeys inside columns[].stageIds and primaryStageId. The stages and the board are created together: if anything fails, nothing is saved. The response includes tempKeyMap (tempKey to created stage id) and the fresh stages list. Creating a stage whose name already exists returns 409; use the existing stage’s id instead.

Optional memberUserIds limits the board to those people (they must be active organisation members, 400 otherwise). Only listed people and holders of the kanban management permission see a limited board; everyone else gets 404 for it. This organises navigation only: the projects on the board remain visible wherever the viewer can already see them. The default board cannot be limited.

Optional taskTableColumns sets the default visible columns of the project task table for projects on this board, as an ordered array of column ids (the same ids as the organisation setting above). Omit it to inherit the organisation default.

Returns the board with its columns (stage and brief-status mappings) plus the organisation’s stages in one call. Boards you cannot see return 404.

Updates board fields and, when columns is supplied, replaces the full column set. The default board cannot be given a client scope.

Like board creation, this accepts stages (renames by id, new stages with a tempKey and category) and stageDeletions (each with stageId and reassignToStageId). Stage changes and the board update commit together, and column references may use the tempKeys. Deletions run last because they move projects; any that fail are reported in deletionErrors without undoing the saved board.

memberUserIds replaces the board’s audience: a non-empty array limits the board to those people, [] opens it to everyone again, and omitting the field leaves the audience unchanged. Board payloads report audienceRestricted and memberUserIds.

taskTableColumns sets the board’s default task-table columns: an array sets them, null clears them back to inheriting the organisation setting, and omitting the field leaves them unchanged. The default board follows the organisation setting and rejects an array with 400. Board payloads report the value under settings.taskTableColumns.

Deletes a board. The default board cannot be deleted (400).

To move a project between stages, use PATCH /api/v1/projects/:id/stage.

The organisations router also exposes endpoints the Runnit app uses for browser sessions: logo upload and retrieval, legacy kanban column configuration, the shared custom-column registry used by the table-column settings editors, 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.