HTTP Semantics
Rules for HTTP method correctness, status codes, headers, retries, and safe semantics.
These rules standardize HTTP behavior so APIs are interoperable, predictable, and safe to integrate with.
Related guides:
Related reference:
HTTP Methods & Operations
#401 - Use HTTP Methods Correctly
Intent: preserve interoperability and make APIs intuitive for clients.
Use HTTP methods according to their standard semantics (RFC 9110 / RFC 7231 lineage). Misusing methods is one of the fastest ways to break caching, retries, SDKs, and client expectations.
GET
GET MUST be safe and side-effect free.
Allowed:
- Read a resource or collection.
- Compute a response based on server state (as long as it does not persist changes).
Not allowed:
- Mutations (even “small” ones like marking as viewed).
- Triggering background jobs that change server state.
Examples:
- ✅
GET /accounts/{accountId} - ✅
GET /accounts?limit=50&offset=0 - ❌
GET /accounts/{accountId}/disable - ❌
GET /accounts/{accountId}that updateslastAccessedAt
POST
POST is used for creating subordinate resources, non-idempotent operations, or request/job resources.
Create resource:
- ✅
POST /accounts→201 Created+Location: /accounts/{id}
Search with body (GET-like semantics):
- ✅
POST /accounts/searchwith body describing filters/sorters, MUST NOT create a resource, MUST be safe to retry.
Command/request resource:
- ✅
POST /accounts/{accountId}/disable-requests(creates a disable request resource)
Anti-examples:
- ❌
POST /accounts/{id}to update (use PATCH/PUT) - ❌
POST /accounts/{id}/get(use GET)
PUT
PUT replaces the entire resource representation.
Rules:
- Missing fields are not “leave unchanged”.
- If you need partial updates, use PATCH.
Examples:
- ✅
PUT /accounts/{accountId}with full representation - ❌
PUT /accounts/{accountId}with only changed fields
PATCH
PATCH applies a partial update.
Rules:
- Explicitly document patch format:
- resource-specific patch schema OR JSON Merge Patch OR JSON Patch.
- Define null semantics (does
nullclear, or is it invalid?).
Examples:
- ✅
PATCH /accounts/{accountId}with{ "status": "DISABLED" } - ✅
PATCH /accounts/{accountId}with JSON Patch ops (if documented)
DELETE
DELETE removes a resource (or performs a documented soft-delete).
Examples:
- ✅
DELETE /accounts/{accountId}→204 No Content - ❌
GET /accounts/{accountId}/delete
Review checklist:
- GET endpoints are demonstrably safe (no writes, no job triggers).
- PUT is full replacement; PATCH is partial update.
- Search-with-body uses POST and is documented as GET-like semantics.
- Paths are noun-based and align with method semantics.
- Consider optimistic locking with ETag/If-Match for updates (see Optimistic locking).
#402 - Honor Method Properties
Intent: enable safe retries, correct caching, and predictable client behavior.
HTTP methods MUST follow their defined properties:
- Safe: repeated calls do not cause state changes (GET/HEAD/OPTIONS).
- Idempotent: repeating the call has the same effect as a single call (GET/HEAD/PUT/PATCH (often), DELETE).
- Cacheable: responses can be cached when appropriate (GET/HEAD).
Examples:
-
Safe:
- ✅
GET /accounts/{id}returning current state - ❌
GET /accounts/{id}that updateslastViewedAt
- ✅
-
Idempotent:
- ✅
DELETE /accounts/{id}deleting (repeat returns 404/410 or 204; effect is “deleted”) - ✅
PUT /accounts/{id}full replacement - ❌
POST /accounts/{id}/disable(repeat creates multiple side effects) unless explicitly made idempotent
- ✅
-
Cacheable:
- ✅
GET /accounts/{id}can be cache-validated viaETag/If-None-Match(see Optimistic locking)
- ✅
Guidance:
- If clients might retry, design operations to be idempotent where feasible (see [#409], [#410]).
- If an operation is not idempotent, document retry behavior and recommended client handling.
#409 - Prefer Idempotent POST/PATCH Where Possible
Intent: make retries safe and reduce duplicate side effects.
For operations that clients are likely to retry (network timeouts, 5xx, gateway errors), APIs SHOULD provide an idempotency mechanism for POST/PATCH where practical.
Recommended mechanisms:
- Optimistic locking for updates:
ETag+If-Match(preferred for PATCH/PUT). - Idempotency-Key header for side-effecting requests (see [#410]).
- Secondary/external keys for create operations (see [#411]).
When to prioritize idempotency:
- Create endpoints (
POST /resources) where duplicates are harmful. - “Command” endpoints that trigger work (e.g.,
POST /imports,POST /exports). - PATCH endpoints used by UIs that may retry on transient failures.
Expected behavior (at a high level):
- If the same request is replayed within the idempotency window, return the same outcome (same created resource, same response body/status) rather than performing the action twice.
- If the key is reused with a different payload, return a deterministic error (commonly
409 Conflict) and explain it.
Notes:
- Idempotency is not required for every endpoint, but the API MUST document retry safety semantics either way.
#411 - Use Secondary Keys for Idempotent POST
Intent: make create operations idempotent without temporary idempotency-key storage.
When creating resources with POST /collection, APIs MAY accept a client-provided secondary key (also called externalId, alternateId, or clientToken) that is unique within the relevant scope.
Requirements if used:
- Document the field name, scope, and uniqueness constraints.
- Enforce uniqueness server-side.
- Define replay behavior:
- If the same secondary key is sent again with the same effective payload, return the existing resource (commonly
200 OKor201 Createdwith the sameLocation). - If the same secondary key is sent with a conflicting payload, return
409 Conflictwith details.
- If the same secondary key is sent again with the same effective payload, return the existing resource (commonly
Example:
POST /accounts
Content-Type: application/json
{ "externalId": "crm:account:12345", "name": "Example" }Notes:
- Secondary keys are best for resources that naturally have a stable external identifier (imports, integrations, id mappings).
- Prefer opaque server IDs as the primary identifier in URLs; secondary keys are for idempotency and lookup.
HTTP Status Codes & Error Handling
#403 - Use Standard HTTP Status Codes
Intent: preserve interoperability and predictable client behavior.
Use only standard HTTP status codes defined in RFCs and/or the IANA HTTP Status Code Registry. Do not invent custom status codes and do not repurpose codes with different semantics.
Required baseline mappings (common cases):
- 400: validation errors, malformed requests
- 401: missing/invalid authentication
- 403: authenticated but not authorized (when revealing existence is acceptable)
- 404: resource not found (or “not authorized to know it exists”)
- 409: conflicts (state transitions, uniqueness constraints)
- 412: conditional request / optimistic locking failures (ETag/If-Match)
- 429: rate limiting
- 5xx: unexpected server failures
Guidance:
- Choose the most semantically appropriate status code for each condition (precision matters).
- When ambiguous, prefer secure defaults that avoid leaking sensitive information (e.g., use
404rather than403when resource existence is sensitive). - Prefer widely-supported, semantically clear codes (e.g., 400/401/403/404/409/412/429).
- Avoid using
5xxfor client mistakes (validation, authorization, conflicts). - If you choose a less common but standard code (e.g.,
422 Unprocessable Content), document when it occurs and why. - Be consistent: do not return different codes for the same condition across endpoints.
#404 - Define a Standard Error Contract in OpenAPI (Problem Details)
Intent: ensure clients can parse errors, retry safely, and generate SDKs from specs.
Every operation MUST define:
- Success responses: status code + schema + examples
- Error responses: status code + standardized schema + examples
This rule consolidates prior guidance on response documentation, error envelopes, and Problem Details.
1) Document responses explicitly
- For each operation, define:
- common success responses (2xx)
- common error responses (400/401/403/404/409/412/429/5xx) where applicable
- Use OpenAPI
defaultonly for truly unexpected server errors; do not hide expected errors behinddefault.
2) Use Problem Details for errors
Error responses MUST use Problem Details (RFC 9457; obsoletes RFC 7807) with media type:
Content-Type: application/problem+json
Required fields:
type,title,status,detail,instance
2.1) Stable application-specific error codes (optional but recommended)
Problem Details extension members are allowed but MUST be documented and stable. Recommended extensions:
errors[]for field-level validationcode(ordetailCode) for stable application-specific error identifierscorrelationIdfor support/debugging
If you define error codes:
- Codes MUST be documented (meaning, when emitted, and whether retry is safe).
- Codes MUST be stable across versions and MUST NOT encode internal implementation details.
- Codes MUST NOT replace HTTP status codes; they are additional detail.
3) Standardize error shape across APIs
- All SailPoint APIs MUST use a single standardized error schema so clients can share error handling.
- Define and reuse a shared OpenAPI component (e.g.,
ProblemDetails) across services.
4) Provide accurate examples
- Include at least one error example per operation.
- Never include secrets or real customer data.
Examples
Validation error (400)
{
"type": "https://developer.sailpoint.com/problems/validation-error",
"title": "Validation error",
"status": 400,
"detail": "One or more fields are invalid.",
"instance": "/accounts",
"errors": [
{ "field": "name", "message": "Must not be blank." }
],
"correlationId": "3f7c2b7f-1b4c-4f0e-9b8e-0b6a0a0b0a0b"
}Optimistic locking failure (412)
{
"type": "https://developer.sailpoint.com/problems/precondition-failed",
"title": "Precondition failed",
"status": 412,
"detail": "ETag does not match current resource version.",
"instance": "/accounts/123",
"code": "ETAG_MISMATCH"
}Testability
- OpenAPI includes
application/problem+jsonschemas and examples for documented errors. - Error schemas reuse a shared component.
- Reviewers can verify every operation has explicit
responsesentries for expected errors.
#405 - Use 207 (or 200) for Per-Item Batch Results
Intent: represent partial success for multi-item operations.
For batch or bulk operations that process multiple items:
- Return per-item results in the response body (status + error details for each item).
- Use
207 Multi-Statuswhen you need to explicitly represent a mixed outcome at the HTTP layer. - If your platform/tooling does not support
207well,200 OKis acceptable as long as the body clearly communicates per-item outcomes.
Example shape:
{
"items": [
{ "id": "A", "status": 204 },
{ "id": "B", "status": 409, "error": { "title": "Conflict", "detail": "Already exists." } }
]
}#406 - Use 429 with Rate-Limit Headers
Intent: support safe retry behavior.
When rate limiting requests, return 429 Too Many Requests and include guidance for clients:
- Retry timing: include
Retry-Afterwhen you can provide a meaningful retry delay. - Rate limit metadata (recommended): use the standard
RateLimit-*header fields (RFC 9235) to expose limit/remaining/reset policy.
Example:
HTTP/1.1 429 Too Many Requests
Retry-After: 10
Content-Type: application/problem+json#408 - Never Expose Stack Traces or Internal Details
Never include stack traces, internal error details, or system information in API error responses.
Examples of forbidden data:
-
stack traces / exception class names
-
internal hostnames, file paths, database keys
-
raw upstream service messages
-
SQL queries, secrets, tokens, cryptographic material
What to do instead:
- Log detailed diagnostics server-side.
- Return sanitized Problem Details to clients (see [#404]).
- Include a stable
correlationId(or equivalent) for support/debugging.
Example (sanitized):
{
"type": "https://developer.sailpoint.com/problems/internal-error",
"title": "Internal error",
"status": 500,
"detail": "An unexpected error occurred.",
"instance": "/accounts/123",
"correlationId": "3f7c2b7f-1b4c-4f0e-9b8e-0b6a0a0b0a0b"
}#412 - Provide Accurate Response Examples
Intent: examples are what people copy/paste—make them correct, safe, and specific.
Every operation MUST include realistic, accurate examples for both success and error responses.
Requirements:
- Include at least one 2xx example and at least one error example per operation.
- Examples MUST match:
- the operation’s documented schema
- the documented status code
- the documented content type
- Examples MUST be specific to the endpoint’s behavior and edge cases; avoid generic examples that don’t demonstrate what the endpoint actually returns.
- Never include secrets, real customer data, or environment-specific identifiers.
OpenAPI guidance:
- Use
content.application/json.examplefor a single canonical example. - Use
content.application/json.exampleswhen you need multiple named examples (recommended for distinct error cases).
Examples
1) Success example (list endpoint)
GET /accounts?limit=2&offset=0{
"items": [
{ "id": "2c9180...", "name": "Example A", "status": "ACTIVE" },
{ "id": "2c9180...", "name": "Example B", "status": "DISABLED" }
],
"count": 123,
"limit": 2,
"offset": 0
}2) Error example (validation error)
{
"type": "https://developer.sailpoint.com/problems/validation-error",
"title": "Validation error",
"status": 400,
"detail": "One or more fields are invalid.",
"instance": "/accounts",
"errors": [
{ "field": "name", "message": "Must not be blank." }
],
"correlationId": "3f7c2b7f-1b4c-4f0e-9b8e-0b6a0a0b0a0b"
}3) Error example (conflict)
{
"type": "https://developer.sailpoint.com/problems/conflict",
"title": "Conflict",
"status": 409,
"detail": "An account with the same externalId already exists.",
"instance": "/accounts",
"code": "DUPLICATE_EXTERNAL_ID"
}Testability:
- A reviewer can match each example to the schema and confirm it would validate.
- Each endpoint’s examples demonstrate its actual edge cases (not generic placeholders).
HTTP Headers
#400 - Use Standard HTTP Headers
Intent: use standard HTTP semantics and improve interoperability.
When your API implements behaviors with standard HTTP representations, you MUST use the corresponding standardized HTTP headers (and document them).
MUST (when applicable):
- Media types / content negotiation:
Accept,Content-Type - Rate limiting/backoff:
Retry-Afterwith429when you can provide meaningful retry guidance - Optimistic locking / conditional updates:
ETag+If-Match(and412on precondition failure)
SHOULD/MAY (when useful):
- Cache validation:
ETag+If-None-Match(with304 Not Modified) - Preferences:
Prefer+Preference-Applied
Examples:
Accept/Content-Typefor content negotiation and media typesRetry-Afterfor rate limiting/backoff guidanceETag/If-Matchfor optimistic locking (see Optimistic locking)
Testability:
- OpenAPI documents supported headers per operation (parameters/headers and response headers).
- Examples include the headers in realistic requests/responses.
#410 - Support Idempotency-Key
Intent: enable safe retries for non-idempotent operations.
APIs MAY support the Idempotency-Key request header for side-effecting operations (typically POST, sometimes PATCH) so clients can retry safely.
If you support it, you MUST define a clear contract:
- Where accepted: which endpoints/methods honor the header.
- Key format: recommend UUID v4 (string).
- Scope: keys are scoped to (client, endpoint, method) at minimum.
- Uniqueness semantics: reusing a key with a different request payload is an error.
- Retention window: how long keys are remembered (e.g., 24 hours) and what happens after expiry.
- Replay behavior: responses are replayed consistently (status code + body + headers) within the retention window.
Recommended server behavior:
- Store a digest of the request (and the resulting response) for the retention window.
- If the same key is received again:
- If the request matches: return the recorded response.
- If the request differs: return
409 Conflict(or400) with a clear error message.
Example:
POST /accounts
Idempotency-Key: 4d2b8c2d-6dbe-4c5e-9b8f-2d3b5a6c7d8e
Content-Type: application/json
{ "name": "Example" }Notes:
Idempotency-Keydoes not replace optimistic locking; useIf-Matchfor updates to existing resources.- Document whether the key is honored on
429/5xxretries.