Staff Profiles & Work History API
The users endpoints live under /api/v1/users and cover individual staff
profiles: personal details, portfolio, preferences, work history, organisation
role, and client assignments.
Authentication: every request needs a per-user API key. See API Keys & Authentication.
Most endpoints here follow one permission pattern: you can always act on your own profile, and organisation admins and owners can act on the profiles of people in their organisation. Where an endpoint differs, its section says so.
GET /api/v1/users/:id
Section titled “GET /api/v1/users/:id”Returns a user’s full profile. You can fetch your own profile; owners, admins, and managers can also fetch the profile of anyone who shares an organisation with them.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | UUID | Yes | The user’s id. |
Example request
curl https://api.runnit.io/api/v1/users/<user-id> \ -H 'Authorization: Bearer rnk_your_key'Response is the user object (with credentials removed). The main fields:
{ "id": "<user-uuid>", "email": "priya@example.com", "firstName": "Priya", "lastName": "Shah", "displayName": "Priya Shah", "avatarUrl": null, "phone": "+61 400 000 000", "shortBio": "Producer with a broadcast background.", "longBio": "...", "yearsInIndustry": 9, "yearsInCurrentRole": 3, "currentTitle": "Senior Producer", "portfolioUrl": "https://priya.example.com", "skills": ["production", "editing"], "preferredWorkTypes": ["campaigns"], "acceptingProjects": true, "preferences": { "...": "..." }, "hourlyRate": 160, "actualCostRate": 95, "defaultRateCardRoleId": "<rate-card-role-uuid>", "status": "active", "lastLoginAt": "2026-07-06T23:41:12.000Z", "createdAt": "2024-11-20T22:14:00.000Z"}Errors
| Status | When |
|---|---|
403 | You are not the user and you do not hold an owner, admin, or manager role in an organisation the user belongs to. |
404 | No user exists with that id. |
PATCH /api/v1/users/:id
Section titled “PATCH /api/v1/users/:id”Updates a user’s core profile fields. You can update your own profile; organisation admins and owners can update anyone who shares an organisation with them. Managers cannot edit other people through this endpoint.
Request body (all optional)
| Name | Type | Required | Description |
|---|---|---|---|
email | string | No | A valid email address. Must not be in use by another account. |
firstName | string | No | Cannot be empty when supplied. |
lastName | string | No | Cannot be empty when supplied. |
displayName | string | No | Preferred display name. |
phone | string | No | Phone number. |
currentTitle | string | No | Job title. |
acceptingProjects | boolean | No | Whether the person is open to new project assignments. |
status | string | No | One of active, archived, or disabled. |
preferences | object | No | Replaces the user’s preferences object. Use the preferences endpoint below if you want merge behaviour. |
Example request
curl -X PATCH https://api.runnit.io/api/v1/users/<user-id> \ -H 'Authorization: Bearer rnk_your_key' \ -H 'Content-Type: application/json' \ -d '{ "currentTitle": "Executive Producer", "acceptingProjects": false }'Response is the full updated user object, in the same shape as
GET /api/v1/users/:id.
Errors
| Status | When |
|---|---|
400 | A supplied field fails validation. |
403 | You are not the user and not an admin or owner of a shared organisation. |
404 | No user exists with that id. |
409 | The email address is already in use. |
PATCH /api/v1/users/:id/portfolio
Section titled “PATCH /api/v1/users/:id/portfolio”Updates the portfolio section of a profile. Same permissions as
PATCH /api/v1/users/:id: yourself, or admins and owners of a shared
organisation.
Request body (all optional)
| Name | Type | Required | Description |
|---|---|---|---|
shortBio | string | No | Short biography. |
longBio | string | No | Long-form biography. |
yearsInIndustry | number | No | Between 0 and 50. |
yearsInCurrentRole | number | No | Between 0 and 50. |
currentTitle | string | No | Job title. |
portfolioUrl | string | No | Must start with http:// or https://. An empty string clears it. |
skills | array of strings | No | Replaces the skills list. An empty array clears it. |
preferredWorkTypes | array of strings | No | Replaces the preferred work types. An empty array clears it. |
Example request
curl -X PATCH https://api.runnit.io/api/v1/users/<user-id>/portfolio \ -H 'Authorization: Bearer rnk_your_key' \ -H 'Content-Type: application/json' \ -d '{ "shortBio": "Producer with a broadcast background.", "skills": ["production", "editing"] }'Response returns the updated portfolio fields:
{ "id": "<user-uuid>", "shortBio": "Producer with a broadcast background.", "longBio": null, "yearsInIndustry": 9, "yearsInCurrentRole": 3, "currentTitle": "Senior Producer", "portfolioUrl": "https://priya.example.com", "skills": ["production", "editing"], "preferredWorkTypes": ["campaigns"]}If the body contains no recognised fields, the response is
{ "message": "No updates provided" } and nothing changes.
Errors
| Status | When |
|---|---|
400 | A field fails validation, for example yearsInIndustry outside 0 to 50. |
403 | You are not the user and not an admin or owner of a shared organisation. |
404 | No user exists with that id. |
PATCH /api/v1/users/:id/preferences
Section titled “PATCH /api/v1/users/:id/preferences”Updates your preferences. This endpoint is self-only: even admins cannot change another person’s preferences. The supplied object is merged into the existing preferences at the top level, so keys you omit are kept.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
preferences | object | Yes | Preference keys to set or overwrite. |
Example request
curl -X PATCH https://api.runnit.io/api/v1/users/<your-user-id>/preferences \ -H 'Authorization: Bearer rnk_your_key' \ -H 'Content-Type: application/json' \ -d '{ "preferences": { "timezone": "Australia/Melbourne" } }'Response is the full merged preferences object.
Errors
| Status | When |
|---|---|
400 | preferences is missing or not an object. |
403 | The id is not your own user id. |
PATCH /api/v1/users/:id/organization-role
Section titled “PATCH /api/v1/users/:id/organization-role”Changes a user’s role in an organisation. The caller must be an admin or owner
of that organisation, with two extra rules: only owners can assign the owner
role, and only owners can change another owner’s role.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
organizationId | UUID | Yes | The organisation to change the role in. With an API key this must be the key’s organisation. |
role | string | Yes | One of member, manager, admin, or owner. The freelancer role cannot be assigned here. |
Example request
curl -X PATCH https://api.runnit.io/api/v1/users/<user-id>/organization-role \ -H 'Authorization: Bearer rnk_your_key' \ -H 'Content-Type: application/json' \ -d '{ "organizationId": "<org-id>", "role": "manager" }'Response
{ "message": "User role updated successfully", "userId": "<user-uuid>", "organizationId": "<org-uuid>", "newRole": "manager"}Errors
| Status | When |
|---|---|
400 | organizationId is not a UUID, or role is not one of the four allowed values. |
403 | You are not an active member of the organisation, you are not an admin or owner, you tried to assign owner without being an owner, or you tried to change an owner’s role without being an owner. |
404 | The target user is not an active member of the organisation. |
Work history
Section titled “Work history”Work history entries (also called portfolio projects) describe past projects a person has worked on. They come from two sources: entries added through this API are always manual, and Runnit also creates entries automatically when delivery projects complete. Editing an automatic entry through the API converts it to manual, which stops the automatic sync from overwriting your change.
All five work history endpoints use the same permissions: yourself, or admins and owners of a shared organisation.
GET /api/v1/users/:id/projects
Section titled “GET /api/v1/users/:id/projects”Lists a user’s work history entries.
curl https://api.runnit.io/api/v1/users/<user-id>/projects \ -H 'Authorization: Bearer rnk_your_key'Response is a JSON array:
[ { "id": "<entry-uuid>", "userId": "<user-uuid>", "clientName": "Harbour Retail Group", "projectName": "Winter Launch", "briefSummary": "Campaign delivery across social and retail.", "startDate": "2025-06-01", "endDate": "2025-08-31", "durationMonths": 3, "projectRole": "Producer", "technologies": ["premiere"], "deliverables": ["30s hero film"], "teamSize": 6, "projectUrl": null, "status": "completed", "isFeatured": false, "displayOrder": 0, "source": "auto", "sourceProjectId": "<project-uuid>" }]source is manual or auto; sourceProjectId links an automatic entry to
the delivery project it came from.
POST /api/v1/users/:id/projects
Section titled “POST /api/v1/users/:id/projects”Adds a work history entry. Entries created here are always stored as manual.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
clientName | string | Yes | Who the work was for. |
projectName | string | Yes | The project’s name. |
briefSummary | string | Yes | Short description of the work. |
startDate | string | Yes | ISO 8601 date, for example 2025-06-01. |
endDate | string | No | ISO 8601 date. |
projectRole | string | Yes | The person’s role on the project. |
durationMonths | integer | No | 0 or higher. |
durationDays | integer | No | 0 or higher. |
teamSize | integer | No | 1 or higher. |
projectUrl | string | No | Must be a valid URL. |
technologies | array of strings | No | Tools and technologies used. |
deliverables | array of strings | No | What was delivered. |
Example request
curl -X POST https://api.runnit.io/api/v1/users/<user-id>/projects \ -H 'Authorization: Bearer rnk_your_key' \ -H 'Content-Type: application/json' \ -d '{ "clientName": "Harbour Retail Group", "projectName": "Winter Launch", "briefSummary": "Campaign delivery across social and retail.", "startDate": "2025-06-01", "endDate": "2025-08-31", "projectRole": "Producer" }'Response is 201 with the created entry, in the same shape as the list
endpoint.
Errors
| Status | When |
|---|---|
400 | A required field is missing, a date is not ISO 8601, or a numeric field is out of range. |
403 | You are not the user and not an admin or owner of a shared organisation. |
PATCH /api/v1/users/:id/projects/:projectId
Section titled “PATCH /api/v1/users/:id/projects/:projectId”Updates a work history entry. Accepts the same fields as creation, all optional, with the same constraints. Editing marks the entry as manual, so the automatic sync stops updating it.
curl -X PATCH https://api.runnit.io/api/v1/users/<user-id>/projects/<entry-id> \ -H 'Authorization: Bearer rnk_your_key' \ -H 'Content-Type: application/json' \ -d '{ "teamSize": 8 }'Response is the updated entry. Returns 404 if the entry does not belong
to the user.
DELETE /api/v1/users/:id/projects/:projectId
Section titled “DELETE /api/v1/users/:id/projects/:projectId”Deletes a work history entry. Returns 204 No Content, or 404 if the entry
does not belong to the user.
curl -X DELETE https://api.runnit.io/api/v1/users/<user-id>/projects/<entry-id> \ -H 'Authorization: Bearer rnk_your_key'POST /api/v1/users/:id/projects/reorder
Section titled “POST /api/v1/users/:id/projects/reorder”Sets the display order of a user’s work history entries.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
projectOrders | array | Yes | One object per entry: id (UUID of the entry) and order (integer, 0 or higher). |
Example request
curl -X POST https://api.runnit.io/api/v1/users/<user-id>/projects/reorder \ -H 'Authorization: Bearer rnk_your_key' \ -H 'Content-Type: application/json' \ -d '{ "projectOrders": [ { "id": "<entry-id-1>", "order": 0 }, { "id": "<entry-id-2>", "order": 1 } ] }'Response
{ "message": "Projects reordered successfully" }Client assignments
Section titled “Client assignments”These endpoints manage which clients a person is restricted to, within the organisation the API key is bound to. They pair with the client-side view in the Clients API.
GET /api/v1/users/:id/clients
Section titled “GET /api/v1/users/:id/clients”Returns the clients this user is explicitly assigned to. You can view your own assignments; owners, admins, and managers can view anyone’s in the organisation.
curl https://api.runnit.io/api/v1/users/<user-id>/clients \ -H 'Authorization: Bearer rnk_your_key'Response
{ "hasSpecificAssignments": true, "assignedClients": [ { "id": "<client-org-uuid>", "name": "Harbour Retail Group", "slug": "harbour-retail" } ]}When hasSpecificAssignments is false, the person can work on every client
that has no team restrictions.
Errors
| Status | When |
|---|---|
403 | You are not an active member of the organisation, or you are viewing someone else without an owner, admin, or manager role. |
PUT /api/v1/users/:id/clients
Section titled “PUT /api/v1/users/:id/clients”Replaces the user’s client assignments. Only organisation admins and owners can call it.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
clientIds | array of UUIDs | Yes | Client organisation ids the user is restricted to. An empty array gives the user all-client access. |
Example request
curl -X PUT https://api.runnit.io/api/v1/users/<user-id>/clients \ -H 'Authorization: Bearer rnk_your_key' \ -H 'Content-Type: application/json' \ -d '{ "clientIds": ["<client-org-id>"] }'Response
{ "success": true, "hasAllClientAccess": false, "clients": [ { "id": "<client-org-uuid>", "name": "Harbour Retail Group", "slug": "harbour-retail" } ]}Errors
| Status | When |
|---|---|
400 | clientIds is missing, not an array, or contains a value that is not a UUID. |
403 | You are not an admin or owner of the organisation. |
Endpoints intended for the app
Section titled “Endpoints intended for the app”DELETE /api/v1/users/:id exists but is designed for the Runnit app: it soft
deletes your own account and requires your password in the request body.
Avatar upload endpoints are also reserved for the app. None of these are
recommended for integrations.
Next step
Section titled “Next step”Manage memberships, roles, and rates at the organisation level with the Organisations & Staff API.