Errors
Every v3 error is an RFC 7807 problem-details body. At
https://openapi.niftypm.com/api/v3 every one of them is served as
application/json, the 429 a rate limiter raises included — see
Rate limits:
Code
Branch on code, never on detail (wording can change). Quote requestId when you contact support. Validation errors add errors: [{ field, code, message }]; expand_unknown_relation adds available: [...]. Extra fields like these sit at the top level of the body, not nested under a meta key. The legacy v1 API uses a different shape, { "message": "...", "code": 401 }, and OAuth endpoints use RFC 6749 { "error", "error_description" }.
A v1 request that fails validation still returns code: 400, and also names the rejected fields in the same errors: [{ field, code, message }] list v3 uses:
Code
code on each entry is required (the field was missing) or invalid (it was sent with the wrong type or value). Nested fields use a dotted path, e.g. items.1.name.
validation_failed
HTTP 400. The request body failed validation — a missing required field, wrong type, or out-of-range value.
Fix: Check errors[] for the specific field(s) and resend with corrected values.
cursor_invalid
HTTP 400. The ?cursor= value on a paginated LIST isn't a cursor this endpoint issued — malformed, expired, or copied from a different resource or sort.
Fix: Drop the cursor and start pagination over, or use the nextCursor from the endpoint's own most recent response.
cursor_sort_mismatch
HTTP 400. A paginated request supplied both ?cursor= and ?sort=, and the sort doesn't match the one the cursor was minted under.
Fix: Keep the same sort value across every page of a paginated walk, or drop the cursor and restart.
pagination_param_unknown
HTTP 400. A LIST request included a pagination query parameter the endpoint doesn't recognize.
Fix: Remove the unrecognized parameter; valid pagination params are cursor, limit, sort.
sort_field_invalid
HTTP 400. ?sort= named a field the resource doesn't allow sorting by.
Fix: Use one of the resource's allowed sort fields — field names the rejected field and resource the resource it was requested on.
resource_unknown
HTTP 400. The resource name in the URL path (e.g. /v3/<name>) does not match any v3 resource.
Fix: Check the resource name against the v3 API reference for a typo.
resource_misconfigured
HTTP 400. The server could not process the request.
Fix: Retrying is unlikely to help — report it with the requestId.
search_query_missing
HTTP 400. A search endpoint was called without a ?q= parameter.
Fix: Pass a non-empty q.
search_query_too_short
HTTP 400. ?q= is under 3 characters.
Fix: Use 3 or more characters.
search_query_too_long
HTTP 400. ?q= exceeds 200 characters.
Fix: Shorten the query.
search_query_invalid
HTTP 400. ?q= contains a URL, script tag, HTML, or email address — a spam-shield rejection.
Fix: Strip the flagged content; reason names which check failed (url | script | html | email).
search_timed_out
HTTP 400. The search took too long to complete.
Fix: Narrow the search (e.g. scope to a project) instead of searching the whole workspace; retrying the identical query will likely time out again.
identity_rejected
HTTP 400. The authenticated identity's type (human, agent, system, OAuth, etc.) isn't allowed to perform this specific operation.
Fix: Use a credential of the identity type the operation requires — detail names it.
empty_update
HTTP 400. A PATCH body has zero fields to update.
Fix: Include at least one field to change.
project_not_specified
HTTP 400. A project-scoped resource was created or listed without identifying a project.
Fix: Pass projectId in the request body (create) or ?projectId= (list).
accessible_project_ids_exceeded
HTTP 400. A workspace-scope LIST (no ?projectId= filter) was requested by a caller with access to more than 1000 projects, exceeding the fan-out cap.
Fix: Narrow the request with ?projectId=<id>.
milestone_dependency_wrong_project
HTTP 400. A task's dependencyId references a task in a different project than the task being created or updated.
Fix: Use a dependency task from the same project.
milestone_hiddenfor_roles_not_in_team
HTTP 400. A milestone's hiddenFor array names role IDs that don't belong to the team.
Fix: Use role IDs from GET /v3/roles for this team.
favorite_folder_invalid
HTTP 400. favoriteFolderId doesn't reference a folder the caller owns.
Fix: Use a folder ID returned by the caller's own GET /v3/favorite-folders, or omit the field.
expand_too_deep
HTTP 400. ?expand= requested a relation chain deeper than the endpoint's depth limit.
Fix: Shorten the expand path — max names the limit, depth what you asked for, and path the offending entry.
expand_cycle_detected
HTTP 400. An ?expand= path revisits a resource already visited earlier in the same chain.
Fix: Remove the relation that creates the cycle.
expand_unknown_relation
HTTP 400. An ?expand= segment names a relation the resource doesn't have — usually a typo.
Fix: Check available for the valid relation names on this resource.
expand_budget_exceeded
HTTP 400. The total rows materialized by ?expand= across the whole request exceeded the per-request budget — protects against fan-out shapes like ?expand=subtasks.subtasks.subtasks.
Fix: Drop the relation named in relation and re-issue with a narrower expand; budget names the per-request row budget.
invalid_field_selector
HTTP 400. ?fields[resource]= names a field the resource doesn't have.
Fix: Check the field name against the resource's schema.
invalid_query_param
HTTP 400. A known query parameter was given a value outside its allowed set (e.g. ?include_trashed=maybe).
Fix: Use one of the values in allowed for the parameter named in name.
move_target_milestone_mismatch
HTTP 400. A cross-project task move (PATCH task.projectId) also set milestoneId to a milestone that lives in a different project than the new target.
Fix: Pass a milestoneId from the target project, or omit it and let the move default one.
move_target_taskgroup_mismatch
HTTP 400. Same as move_target_milestone_mismatch but for taskGroupId (v3's name for the status column).
Fix: Pass a taskGroupId from the target project, or omit it.
move_target_no_default_milestone
HTTP 400. A cross-project task move omitted milestoneId and the target project has no non-archived milestone to default to.
Fix: Create a milestone in the target project first, or pass an explicit milestoneId.
move_target_no_default_taskgroup
HTTP 400. Same as move_target_no_default_milestone but for the target project's status column.
Fix: Create a status in the target project first, or pass an explicit taskGroupId.
task_no_default_status
HTTP 400. A task is being created in a project that has no active status to default the task into.
Fix: Create a status in the project first, or pass an explicit taskGroupId.
move_subtask_must_move_with_parent
HTTP 400. task.projectId was PATCHed directly on a subtask.
Fix: Move the parent task instead (the whole subtree moves with it), or clear parentTaskId first to promote the subtask to a root task, then move it.
task_subtask_chain_too_deep
HTTP 400. A cross-project task move's subtree (root + descendants) exceeded 200 nodes; the transaction rolled back atomically rather than landing a half-moved tree.
Fix: Split the move into smaller per-subtree PATCH calls.
task_dependency_incomplete
HTTP 400. completed: true was PATCHed on a task whose dependency task isn't complete yet.
Fix: Complete the dependency task first, or have a workspace admin enable the team's "Complete Dependant Tasks" setting.
request_revision_stale
Not live yet. This code ships with the requests API (/task-requests), which is not in the released API yet.
HTTP 409. A write on a request carried a revision (or round) older than the request's current one: the requester edited it since you read it. Nothing was written.
Fix: Re-read the request and retry with its current revision and round.
request_not_open
Not live yet. This code ships with the requests API (/task-requests), which is not in the released API yet.
HTTP 409. The request is not in a state that accepts this action: it was withdrawn, or it is no longer open for an answer, a decline or a question. An edit also returns this when every person asked has declined, because nobody would be left to answer.
Fix: Re-read the request. To ask someone new, create a new request.
request_already_answered
Not live yet. This code ships with the requests API (/task-requests), which is not in the released API yet.
HTTP 409. Someone else answered this round first; the first answer wins. respondedByMemberId names who answered. Nothing was written.
Fix: Re-read the request to see the answer. Don't retry the same answer.
request_subject_changed
Not live yet. This code ships with the requests API (/task-requests), which is not in the released API yet.
HTTP 409. An approval named a subjectVersion that is no longer the version the request points at: the thing to approve changed since it was reviewed.
Fix: Re-read the request, review the current version, and approve again with its subjectVersion.
request_askee_ineligible
Not live yet. This code ships with the requests API (/task-requests), which is not in the released API yet.
HTTP 422. A member in askeeMemberIds (or reassignToMemberId) can't be asked this kind of request on this task, for example because they can't read it or, for an approval, can't edit it. errors names each refused member's field.
Fix: Ask only members that GET /task-requests/eligible-askees returns with eligible: true for the same task and kind.
request_confirm_in_app
Not live yet. This code ships with the requests API (/task-requests), which is not in the released API yet.
HTTP 422. An approval or an acknowledgement was sent with an API token or by an agent. These are confirmed by the person in the Nifty app.
Fix: Ask the person to confirm the request in the Nifty app. Other kinds (answer, provide) can be answered over the API.
request_dependency_unsupported
Not live yet. This code ships with the requests API (/task-requests), which is not in the released API yet.
HTTP 422. A request task can't have a dependency, neither blocking another task nor blocked by one.
Fix: Set the dependency on the request's parent task instead.
bulk_too_large
HTTP 400. A bulk write named more than 150 items.
Fix: Split the request into batches of 150 or fewer.
batch_empty
HTTP 400. An M2M sub-resource request (e.g. POST /v3/files/:id/labels) sent a missing or empty ids array.
Fix: Pass a non-empty ids: string[].
batch_too_large
HTTP 400. An M2M sub-resource request's ids array exceeded the endpoint's max.
Fix: Split into smaller batches — max names the limit and received what you sent.
m2m_relation_unknown
HTTP 400. An M2M sub-resource request named a relation the parent resource doesn't declare — usually a typo'd relation segment.
Fix: Use one of the relation names the error body lists for this resource.
m2m_target_not_found
HTTP 404. One or more ids in an M2M sub-resource request don't exist, are soft-deleted, or belong to a different workspace. The whole operation is rejected rather than partially applied.
Fix: Drop the IDs listed in missing and retry.
m2m_not_supported
HTTP 501. This relation is not available on this resource.
Fix: Retrying won't help; report it.
unauthorized
HTTP 401. The request has no valid authentication credential — no bearer token, or the header is missing or malformed.
Fix: Send Authorization: Bearer <token> with a valid Personal Access Token or OAuth access token.
invalid_token
HTTP 401. The bearer token isn't found, was revoked, or is otherwise invalid.
Fix: Mint a new Personal Access Token or re-run the OAuth flow.
token_expired
HTTP 401. The bearer token's expiresAt has passed.
Fix: Mint a new token.
token_owner_inactive
HTTP 401. The token's owning team member has been deactivated, or the owning user account has been deleted.
Fix: The token can't be reactivated — have an active member mint a new one.
pat_cap_reached
HTTP 400. Creating a new Personal Access Token would exceed the 20-active-PAT-per-user cap.
Fix: Revoke an existing token first (DELETE /personal-access-tokens/{id}), then create the new one.
cannot_broaden_scope
HTTP 400. Updating a PAT tried to add a scope it didn't already have.
Fix: Revoke the token and issue a new one with the broader scope set instead of updating in place.
cannot_shorten_expiry
HTTP 400. Updating a PAT tried to set expiresAt earlier than its current value.
Fix: Use revoke instead of shortening expiry.
cannot_create_empty_token
HTTP 400. The scopes requested for a new PAT don't intersect with the creating user's actual role permissions — the token would be able to do nothing.
Fix: Request only scopes the user's role actually grants.
permission_denied
HTTP 403. The authenticated identity is missing one or more permissions the operation requires.
Fix: Check missingPermissions in the response and have a workspace admin grant them, or use a credential with the right role/scopes.
project_access_denied
HTTP 403. The authenticated identity doesn't have access to the specific project the request targets.
Fix: Use a credential belonging to a team member with access to that project.
default_label_locked
HTTP 403. The request tried to rename or delete a system default label (e.g. the archived-status label).
Fix: Default labels can't be mutated — create a new label instead.
default_role_locked
HTTP 403. The request tried to rename, re-permission, or delete a system role (owner, manager, regular, client).
Fix: System roles are fixed — create a custom role instead.
permission_exceeds_caller_grant
HTTP 403. A caller tried to set a role's permissions to include a permission they don't themselves currently hold.
Fix: Only grant permissions the acting identity already has.
resource_not_found
HTTP 404. The requested resource ID doesn't exist, is soft-deleted, or isn't accessible to the caller.
Fix: Check the ID and the caller's access to that resource.
conflict
HTTP 409. The generic catch-all for a state collision that doesn't have its own dedicated code — typically a business-rule invariant the current row state violates.
Fix: Re-read the resource's current state and adjust the request accordingly; detail names the specific conflict.
duplicate_label
HTTP 409. A create or rename set a label name that already exists (case aside) among the team's active labels.
Fix: Use a different name.
duplicate_role
HTTP 409. A create or rename set a role name that already exists in the team.
Fix: Use a different name.
role_in_use
HTTP 409. A role delete was requested while the role is still assigned to one or more active team members.
Fix: Reassign those members to a different role first.
regular_role_missing
HTTP 409. A role delete needs to reassign its former (removed) holders to the team's "regular" role, but the team has none.
Fix: Create a regular role in the team, or leave this role in place.
stale_write
HTTP 409. An update included expectedUpdatedAt and the row's actual updated_at no longer matches — another writer committed after the read this update was based on.
Fix: Re-read the resource, re-derive your change from the current values, and retry with the fresh updatedAt. Do not reuse the value in detail.
no_running_timer
HTTP 409. POST /time-entries/stop was called, but the caller has no running timer.
Fix: Start a timer with POST /time-entries/start first, or read GET /time-entries to find the entry you meant to stop.
idempotency_key_reuse
HTTP 422. An Idempotency-Key was reused with a request body whose content fingerprint differs from the first use of that key.
Fix: Use a fresh Idempotency-Key for the new body.
idempotency_in_progress
HTTP 409. A retry with the same Idempotency-Key arrived while the first request with that key was still being processed.
Fix: Back off briefly and retry — the first request's result will apply.
name_contains_url
HTTP 400. A free-text name field (workspace, project, portfolio, label, etc.) contains a URL.
Fix: Remove the URL from the name.
name_contains_email
HTTP 400. A free-text name field contains an email address.
Fix: Remove the email address from the name.
name_contains_html
HTTP 400. A free-text name field contains HTML markup.
Fix: Remove the markup — use plain text.
name_contains_script
HTTP 400. A free-text name field contains script-like markup.
Fix: Remove the script-like content.
team_storage_quota_exceeded
HTTP 413. Uploading a file would put the team over its storage quota (storage_per_user × billable members, in GiB).
Fix: Free up space or upgrade the plan — usage/limit/requested in the body show the numbers.
rate_limited
HTTP 429. The caller exceeded the API rate limit.
Fix: Back off and retry after retryAfter seconds (also in the Retry-After header); scope names which limit you hit.
free_plan_write_cap_exceeded
HTTP 429. A free-plan team's programmatic caller (agent, or a PAT/OAuth-bearer human) hit the per-team, per-resource, per-day create cap.
Fix: Wait for the daily UTC reset, or upgrade the plan — the cap doesn't apply to paid teams. limit, used and resource describe the cap you hit.
quota_exceeded
HTTP 429. The team has spent its monthly allowance for a metered dimension (automation runs, API/MCP calls, AI generations, form responses).
Fix: Wait until resetsAt, or have the billing owner raise the plan's limit for dimension. The same numbers arrive as the X-Quota-Limit, X-Quota-Remaining and X-Quota-Reset headers.
not_implemented
HTTP 501. This operation is not available on this resource.
Fix: This operation isn't available for this resource — check the API reference for what it does support.
create_not_durable
HTTP 503. The create could not be confirmed.
Fix: Retry with the same Idempotency-Key; it's safe either way — a true miss gets a fresh create, a false alarm replays the already-committed result.
internal_error
HTTP 500. The fallback for an unexpected server-side error that doesn't map to any other code.
Fix: Retry once; if it persists, report it with the requestId.
actor_resolution_failed
HTTP 500. The server could not resolve the acting identity.
Fix: Not caller-fixable — report it with the requestId.
response_schema_violation
HTTP 500. The write was not applied.
Fix: Retry once with the same Idempotency-Key; if it persists, report it with the requestId.
route_not_found
HTTP 404. No v3 route matches the method and path.
Fix: Check the method and the path against the v3 API reference.