Skip to content

Rate Cards API

Rate cards define the hourly rates your organisation bills for each role. There are two layers:

  • Organisation rate-card roles are the base rates, one per role (for example “Senior Designer” at 180 AUD an hour).
  • Client rate cards overlay the base rates for a specific client. Creating a card imports every base role, and you can then override individual rates.

A project bills using the first of: the rate card assigned to the project, the client’s default rate card, then the base roles. The effective-rate endpoints resolve that chain for you.

All endpoints live under /api/v1/organizations/:id, where id is your organisation’s id.

Authentication: every request needs a per-user API key bound to the organisation in the path. See API Keys & Authentication.

Rates are financial data, so note who can see and change them: any active member of the organisation can read the endpoints on this page, but the hourly rate values only appear in responses when the caller has financial-report access (managers and above by default). Other callers receive the same structure with role names and no rate fields, so role pickers keep working without exposing pricing. Creating, updating, or archiving rate-card roles and client rate cards needs an organisation admin or owner. Assigning a rate card to a project needs project-update access, which managers, admins, and owners have by default. Organisations that use custom roles may grant these permissions differently.

GET /api/v1/organizations/:id/rate-card-roles

Section titled “GET /api/v1/organizations/:id/rate-card-roles”

Lists the organisation’s base rate-card roles. Any active member can call it.

Query parameters

NameTypeRequiredDescription
includeInactivestringNoPass true to include archived roles. Defaults to active roles only.

Example request

Terminal window
curl https://api.runnit.io/api/v1/organizations/<org-id>/rate-card-roles \
-H 'Authorization: Bearer rnk_your_key'

Response

{
"roles": [
{
"id": "<role-uuid>",
"organizationId": "<org-uuid>",
"roleKey": "senior-designer",
"roleName": "Senior Designer",
"hourlyRate": 180,
"currency": "AUD",
"isActive": true,
"createdAt": "2026-01-10T00:00:00.000Z",
"updatedAt": "2026-06-01T00:00:00.000Z"
}
]
}

POST /api/v1/organizations/:id/rate-card-roles

Section titled “POST /api/v1/organizations/:id/rate-card-roles”

Creates a base rate-card role. Only organisation admins and owners can call it.

Request body

NameTypeRequiredDescription
roleNamestringYesDisplay name, 1 to 120 characters.
roleKeystringNoStable key, 1 to 80 characters, unique within the organisation. Generated from the name if omitted.
hourlyRatenumberYesHourly rate, 0 or higher (up to 99,999,999.99).
currencystringNoISO 4217 currency code, for example AUD. Lowercase input is stored uppercase; invalid codes are rejected with 400.
isActivebooleanNoDefaults to true.

Example request

Terminal window
curl -X POST https://api.runnit.io/api/v1/organizations/<org-id>/rate-card-roles \
-H 'Authorization: Bearer rnk_your_key' \
-H 'Content-Type: application/json' \
-d '{ "roleName": "Senior Designer", "hourlyRate": 180, "currency": "AUD" }'

Response is 201 with the created role, in the same shape as the list endpoint.

Errors

StatusWhen
400roleName or hourlyRate is missing or out of range.
409A role with the same roleKey already exists in the organisation.

PATCH /api/v1/organizations/:id/rate-card-roles/:roleId

Section titled “PATCH /api/v1/organizations/:id/rate-card-roles/:roleId”

Updates a base rate-card role. Only organisation admins and owners can call it. All body fields are optional and follow the same constraints as creation: roleName, roleKey, hourlyRate, currency, and isActive.

Example request

Terminal window
curl -X PATCH https://api.runnit.io/api/v1/organizations/<org-id>/rate-card-roles/<role-id> \
-H 'Authorization: Bearer rnk_your_key' \
-H 'Content-Type: application/json' \
-d '{ "hourlyRate": 190 }'

Response is the updated role.

Errors

StatusWhen
400A field fails validation, or roleId is not a UUID.
404The role does not exist in the organisation.

DELETE /api/v1/organizations/:id/rate-card-roles/:roleId

Section titled “DELETE /api/v1/organizations/:id/rate-card-roles/:roleId”

Archives a base rate-card role. Only organisation admins and owners can call it. The role is deactivated rather than deleted, so historical rates are preserved; archived roles reappear in listings with includeInactive=true.

Terminal window
curl -X DELETE https://api.runnit.io/api/v1/organizations/<org-id>/rate-card-roles/<role-id> \
-H 'Authorization: Bearer rnk_your_key'

Response is 204 No Content. Returns 404 if the role does not exist in the organisation.

GET /api/v1/organizations/:id/client-rate-cards

Section titled “GET /api/v1/organizations/:id/client-rate-cards”

Lists client rate cards, optionally for one client. Any active member can call it.

Query parameters

NameTypeRequiredDescription
clientOrganizationIdUUIDNoOnly return cards for this client organisation.
includeInactivebooleanNoPass true to include archived cards.

Example request

Terminal window
curl 'https://api.runnit.io/api/v1/organizations/<org-id>/client-rate-cards?clientOrganizationId=<client-org-id>' \
-H 'Authorization: Bearer rnk_your_key'

Response

{
"cards": [
{
"id": "<card-uuid>",
"organizationId": "<org-uuid>",
"clientOrganizationId": "<client-org-uuid>",
"name": "Retainer 2026",
"isDefault": true,
"isActive": true,
"createdAt": "2026-02-01T00:00:00.000Z",
"updatedAt": "2026-02-01T00:00:00.000Z"
}
]
}

POST /api/v1/organizations/:id/client-rate-cards

Section titled “POST /api/v1/organizations/:id/client-rate-cards”

Creates a client rate card and imports every base role onto it as inherited rows. Only organisation admins and owners can call it. The client must already be linked to your organisation (see the Clients API).

Request body

NameTypeRequiredDescription
clientOrganizationIdUUIDYesThe client organisation the card belongs to. Must be a linked client.
namestringYesCard name, 1 to 150 characters, unique per client.
isDefaultbooleanNoMake this the client’s default card.
isActivebooleanNoDefaults to true.

Example request

Terminal window
curl -X POST https://api.runnit.io/api/v1/organizations/<org-id>/client-rate-cards \
-H 'Authorization: Bearer rnk_your_key' \
-H 'Content-Type: application/json' \
-d '{ "clientOrganizationId": "<client-org-id>", "name": "Retainer 2026", "isDefault": true }'

Response is 201 with the card plus importedRoles, the count of base roles copied onto the new card:

{
"id": "<card-uuid>",
"organizationId": "<org-uuid>",
"clientOrganizationId": "<client-org-uuid>",
"name": "Retainer 2026",
"isDefault": true,
"isActive": true,
"importedRoles": 6
}

To read the imported role rows, call GET /api/v1/organizations/:id/client-rate-cards/:cardId/roles.

Errors

StatusWhen
400The client organisation is not linked to your organisation, or a field fails validation.
409A card with this name already exists for the client.

PATCH /api/v1/organizations/:id/client-rate-cards/:cardId

Section titled “PATCH /api/v1/organizations/:id/client-rate-cards/:cardId”

Updates a client rate card’s metadata. Only organisation admins and owners can call it. Optional body fields: name (1 to 150 characters), isDefault, and isActive.

Terminal window
curl -X PATCH https://api.runnit.io/api/v1/organizations/<org-id>/client-rate-cards/<card-id> \
-H 'Authorization: Bearer rnk_your_key' \
-H 'Content-Type: application/json' \
-d '{ "isDefault": true }'

Response is the updated card. Returns 404 if the card does not exist in the organisation.

DELETE /api/v1/organizations/:id/client-rate-cards/:cardId

Section titled “DELETE /api/v1/organizations/:id/client-rate-cards/:cardId”

Archives a client rate card. Only organisation admins and owners can call it. Returns 204 No Content, or 404 if the card does not exist in the organisation.

GET /api/v1/organizations/:id/client-rate-cards/:cardId/roles

Section titled “GET /api/v1/organizations/:id/client-rate-cards/:cardId/roles”

Lists the card’s own role rows: the overlay entries, not the merged view. Any active member can call it. Use the effective-rate endpoints below when you want the resolved rates. Pass includeInactive=true to include archived rows.

Terminal window
curl https://api.runnit.io/api/v1/organizations/<org-id>/client-rate-cards/<card-id>/roles \
-H 'Authorization: Bearer rnk_your_key'

Response

{
"roles": [
{
"id": "<card-role-uuid>",
"clientRateCardId": "<card-uuid>",
"baseRoleId": "<role-uuid>",
"roleKey": "senior-designer",
"roleName": "Senior Designer",
"overrideHourlyRate": 165,
"currency": "AUD",
"isActive": true
}
]
}

A row with overrideHourlyRate: null inherits the base role’s rate.

POST /api/v1/organizations/:id/client-rate-cards/:cardId/roles

Section titled “POST /api/v1/organizations/:id/client-rate-cards/:cardId/roles”

Adds a role override to a client rate card. Only organisation admins and owners can call it. Every card role must link to a base role.

Request body

NameTypeRequiredDescription
baseRoleIdUUIDYesThe base rate-card role this row overrides.
overrideHourlyRatenumber or nullNoThe client-specific rate, 0 or higher. null or omitted inherits the base rate.
currencystringNoISO 4217 currency code. Lowercase input is stored uppercase; invalid codes are rejected with 400.

Example request

Terminal window
curl -X POST https://api.runnit.io/api/v1/organizations/<org-id>/client-rate-cards/<card-id>/roles \
-H 'Authorization: Bearer rnk_your_key' \
-H 'Content-Type: application/json' \
-d '{ "baseRoleId": "<role-id>", "overrideHourlyRate": 165 }'

Response is 201 with the created card role.

Errors

StatusWhen
400baseRoleId is missing or not a UUID, or overrideHourlyRate is negative.
409The card already has a row for that role.

PATCH /api/v1/organizations/:id/client-rate-cards/:cardId/roles/:roleId

Section titled “PATCH /api/v1/organizations/:id/client-rate-cards/:cardId/roles/:roleId”

Updates a card role’s override values. Only organisation admins and owners can call it. Optional body fields: overrideHourlyRate (number, 0 or higher, or null to revert to the base rate) and currency.

Terminal window
curl -X PATCH https://api.runnit.io/api/v1/organizations/<org-id>/client-rate-cards/<card-id>/roles/<card-role-id> \
-H 'Authorization: Bearer rnk_your_key' \
-H 'Content-Type: application/json' \
-d '{ "overrideHourlyRate": null }'

Response is the updated card role. Returns 404 if the role, card, or organisation do not match.

DELETE /api/v1/organizations/:id/client-rate-cards/:cardId/roles/:roleId

Section titled “DELETE /api/v1/organizations/:id/client-rate-cards/:cardId/roles/:roleId”

Archives a card role. Only organisation admins and owners can call it. Returns 204 No Content, or 404 if the role does not exist on that card.

PATCH /api/v1/organizations/:id/projects/:projectId/rate-card

Section titled “PATCH /api/v1/organizations/:id/projects/:projectId/rate-card”

Assigns a client rate card to a project, or clears the assignment. Requires project-update access, which managers, admins, and owners have by default. The card must belong to the project’s client.

Request body

NameTypeRequiredDescription
clientRateCardIdUUID or nullNoThe card to assign. Pass null (or omit) to clear the assignment, so the project falls back to the client’s default card.

Example request

Terminal window
curl -X PATCH https://api.runnit.io/api/v1/organizations/<org-id>/projects/<project-id>/rate-card \
-H 'Authorization: Bearer rnk_your_key' \
-H 'Content-Type: application/json' \
-d '{ "clientRateCardId": "<card-id>" }'

Response

{ "project": { "id": "<project-uuid>", "name": "Winter Launch", "clientRateCardId": "<card-uuid>", "...": "..." } }

Errors

StatusWhen
400The project has no client, the card is not in your organisation, the card is archived, or the card belongs to a different client.
404The project does not exist in the organisation.

GET /api/v1/organizations/:id/clients/:clientOrgId/effective-rate-roles

Section titled “GET /api/v1/organizations/:id/clients/:clientOrgId/effective-rate-roles”

Resolves the effective rates for a client: the client’s default rate card if one exists, otherwise the base roles. Any active member can call it.

Terminal window
curl https://api.runnit.io/api/v1/organizations/<org-id>/clients/<client-org-id>/effective-rate-roles \
-H 'Authorization: Bearer rnk_your_key'

Response

{
"roles": [
{
"id": "<role-uuid>",
"roleName": "Senior Designer",
"hourlyRate": 165,
"currency": "AUD",
"sourceType": "client_overlay",
"baseRoleId": "<role-uuid>",
"clientRateCardRoleId": "<card-role-uuid>",
"clientRateCardId": "<card-uuid>"
}
],
"clientRateCardId": "<card-uuid>"
}

sourceType is base (rate comes straight from the base role) or client_overlay (a card row overrides a base role). clientRateCardId is omitted when the rates come from the base roles alone.

GET /api/v1/organizations/:id/projects/:projectId/effective-rate-roles

Section titled “GET /api/v1/organizations/:id/projects/:projectId/effective-rate-roles”

Resolves the effective rates for a project, following the full chain: the project’s assigned card, then the client’s default card, then the base roles. Any active member can call it. The response has the same shape as the client effective-rate endpoint, with one difference: every role in a project resolution links to a base role, because project members and tasks can only be priced against base-role ids. Pass a projectId that is a valid UUID; an unknown project resolves against the base roles.

Terminal window
curl https://api.runnit.io/api/v1/organizations/<org-id>/projects/<project-id>/effective-rate-roles \
-H 'Authorization: Bearer rnk_your_key'

Link the clients these cards bill against with the Clients API.

Lists the organisation’s currency conversion rates and its reporting currency. Any active member can call it.

Response

{
"reportingCurrency": "AUD",
"rates": [
{
"id": "<rate-uuid>",
"organizationId": "<org-uuid>",
"fromCurrency": "GBP",
"toCurrency": "AUD",
"rate": 1.95,
"effectiveDate": "2026-08-01",
"source": "manual",
"createdAt": "2026-08-18T00:00:00.000Z"
}
]
}

Adds or updates a dated conversion rate. Only organisation admins and owners can call it. A rate applies from its effective date until a newer row supersedes it; amounts already recorded keep the rate they were valued at.

NameTypeRequiredDescription
fromCurrencystringYesISO 4217 code.
toCurrencystringYesISO 4217 code, different from fromCurrency.
ratenumberYesPositive conversion rate (1 fromCurrency = rate toCurrency).
effectiveDatestringNoISO date; defaults to today.

Response is 201 with the stored rate. Posting the same pair and effective date again updates the rate in place.

DELETE /api/v1/organizations/:id/fx-rates/:rateId

Section titled “DELETE /api/v1/organizations/:id/fx-rates/:rateId”

Deletes a conversion rate row. Only organisation admins and owners can call it. Returns 204, or 404 when the rate does not exist. Deleting a rate does not change amounts that were already valued with it.

PATCH /api/v1/organizations/:id/reporting-currency

Section titled “PATCH /api/v1/organizations/:id/reporting-currency”

Sets the organisation’s reporting currency (the single roll-up currency for all financial reporting). Only organisation admins and owners can call it. Body: { "currency": "AUD" } (ISO 4217).

Returns 409 once any time entry has been valued against the current reporting currency; re-basing historical reporting is a support operation.