Skip to content

Errors and status codes

TruePPM returns errors in two different shapes, and the difference decides whether you get a machine-readable code to branch on. Knowing which is which is the whole point of this page — it is the thing you would otherwise have to reverse-engineer.

For what a version bump may and may not change, see API stability. For webhook delivery failures — a separate surface with its own retry semantics — see Webhooks.

Shape 1 — field validation errors (no code)

Section titled “Shape 1 — field validation errors (no code)”

Serializer validation failures return a map of field name → list of messages, with the HTTP status 400:

{
"planned_start": ["This field is required."],
"duration_days": ["Ensure this value is greater than or equal to 0."]
}

There is no code key in this shape, and the individual messages are not stable. Internally these errors do carry a code, but Django REST Framework serializes each one to its message string alone, so it never reaches the wire.

Branch on the field key. Never match on the message text. The strings come from DRF and Django validators and can change with a framework upgrade.

Non-field validation errors arrive under the non_field_errors key, and authentication or permission failures arrive as a bare detail:

{"detail": "You do not have permission to perform this action."}

Shape 2 — structured errors (with a stable code)

Section titled “Shape 2 — structured errors (with a stable code)”

Failures that a client is expected to handle differently — not merely report — return a flat object carrying a stable code:

{
"detail": "Cannot set progress on a task with no start date.",
"code": "progress_requires_anchor"
}

detail is for humans and may be reworded at any time. code is the contract. Some codes carry extra keys; those are noted in the tables below.

The codes on this page are the complete set of values that appear in this shape. If an endpoint returns a bare detail with no code, treat it as a generic failure of its HTTP status class.

CodeMeaningExtra keys
progress_requires_anchorProgress was set on a task with no date to anchor it to
milestone_rollup_lockedThe value is rolled up to the milestone and cannot be set directly
child_of_milestoneA milestone is a zero-duration gate and cannot be given children
guardrail_blockedA configured guardrail refused the writerule, suggested_action
base_url_not_allowedThe supplied integration base URL is not permitted
invalid_tokenThe password-reset uid+token pair is bad, unknown, or expired
weak_passwordThe new password failed validationmessages
CodeMeaning
scope_accept_forbiddenThe caller may not accept this scope-injection request

404 / 409 — conflicts and protected references

Section titled “404 / 409 — conflicts and protected references”

Refused deletes carry a count of the rows still pointing at the target, and — only where the endpoint’s permission gate authorizes the caller to see them — a capped sample naming them:

{
"detail": "This calendar is still referenced and cannot be deleted.",
"code": "calendar_in_use",
"reference_count": 3,
"references": [{"type": "project", "name": "Harbor Fit-out"}]
}
CodeStatusMeaningExtra keys
protected_reference409Generic refused delete — other rows still reference this onereference_count, references?
calendar_in_use409The working calendar is still referencedreference_count, references?
skill_in_use409The skill is still referencedreference_count, references?
sprint_already_bound409The milestone is already bound to a sprint
sync_conflict409A stale write overlapped a concurrent writersee below
proposal_closed409The ceiling proposal is no longer open
not_open409The planning-poker round is not open for votes
not_live409The planning-poker session is not live
not_revealed409The planning-poker round has not been revealed yet
sprint_not_planned409The sprint is not in the PLANNED state
not_found404The targeted poker round or ceiling proposal does not exist

A sync_conflict carries the field-level divergence so the client can render a merge rather than guess:

{
"code": "sync_conflict",
"conflict_fields": ["name"],
"server_value": {"name": "..."},
"client_value": {"name": "..."},
"server_version": 41
}
CodeMeaningExtra keys
program_schedule_invalid_inputA task in the program has data the schedule engine cannot computereason?, project?, task?
program_schedule_too_largeThe program exceeds the schedule-computation size limit
credential_requiredThe connection needs a credential that was not supplied
provider_verification_failedThe external provider rejected the supplied credential
source_verification_failedThe configured external source could not be verified
CodeMeaningExtra keys
sync_cooldownThe connection was refreshed too recently to sync againretry_after (seconds)

The general rate limiter is different: it returns a bare detail with no code, plus a Retry-After header. See rate limiting.

SSO failures are a third shape again. The login and callback endpoints are browser redirects, so the code arrives as an ?error= query parameter on the SPA completion URL — not in a response body:

https://ppm.example.com/auth/complete?error=invalid_state
CodeEquivalent statusMeaning
oidc_error400Generic SSO failure (the base code)
sso_not_configured400No enabled provider matched the request
invalid_state400The state parameter was missing, unknown, or did not match the cookie
token_exchange_failed400The provider rejected the authorization-code exchange
invalid_id_token400The ID token failed signature or claim validation
email_unverified403The provider reports the account’s email as unverified
sso_no_member403The authenticated identity maps to no workspace member
provider_unreachable502The provider’s discovery or JWKS endpoint could not be reached

These codes never carry token material or PII.

A few code values appear on successful 2xx responses, inside a warnings array or a warning field. The write succeeded — these are advisories. Do not treat them as failures:

CodeAppears onMeaning
resource_overallocatedassignment writesThe resource is now allocated beyond its capacity
skill_mismatchassignment writesThe resource lacks a skill the task requires
has_assignmentstask restructureA task became a summary task while still carrying assignments
scope_pending_on_closesprint closeScope-injection requests were still pending at close

The API stability contract applies as follows.

Within a minor release, TruePPM will not:

  • change the spelling of any code on this page;
  • change the HTTP status a listed code accompanies;
  • remove a listed code, or move one between the error and warning categories;
  • remove a documented extra key (retry_after, conflict_fields, rule, …) from a body that carries it.

Within a minor release, TruePPM may:

  • add new code values, including on endpoints that previously returned a bare detail;
  • promote an endpoint from Shape 1 to Shape 2 by introducing a code;
  • reword any detail string — detail is never part of the contract;
  • add new keys alongside code.

Not covered at all: the message strings in Shape 1 field errors, and the detail text everywhere. Match on the field key or on code, never on prose.

Because new codes may appear in a minor release, treat an unrecognized code as a generic failure of its HTTP status class rather than raising on it. That single rule is what keeps a client forward-compatible.