sailpoint-guidelines
sailpoint-guidelines
Github Repository
Table of Contents
SailPoint RESTful API Guidelines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1. Introduction. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Conventions used in these guidelines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
SailPoint specific information. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2. Principles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
API design principles. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
API as a product . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
API first . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
3. General guidelines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
MUST follow SailPoint API Guidelines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
SHOULD follow API first principle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
MUST provide API specification using OpenAPI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
MUST provide detailed API description . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
SHOULD provide API user manual . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
MUST describe every parameter and property . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
MUST provide an example for every parameter and property . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
SHOULD keep operation summaries at five or less words . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
MUST provide a valid operationId in camelCase for each operation . . . . . . . . . . . . . . . . . . . . . . . . 10
MUST provide a valid tag for each operation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
MUST provide a valid operationId in camelCase under the key x-sailpoint-resource-
operation-id for each path parameter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
API Schema Validator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
developer.sailpoint.com . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
Exceptions to this rule . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
MUST write APIs using U.S. English . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
4. Meta information . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
MUST contain API meta information . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
MUST use semantic versioning . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
MUST provide API audience . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
5. Security . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
1
MUST secure endpoints with OAuth 2.0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
MUST define and assign permissions (scopes) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
MUST define user levels (capabilities) needed by an endpoint . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
MUST Document necessary license add-on to use an API collection . . . . . . . . . . . . . . . . . . . . . . . . . . 19
MUST follow naming convention for permissions (scopes) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
6. Compatibility . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
MUST not break backward compatibility . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
SHOULD prefer compatible extensions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
MUST prepare clients to accept compatible API extensions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
SHOULD design APIs conservatively . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
MUST always return JSON objects as top-level data structures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
MUST treat OpenAPI specification as open for extension by default . . . . . . . . . . . . . . . . . . . . . . . . . . 21
SHOULD avoid versioning . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
MUST use URI versioning . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
MUST follow versioned API requirements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
MUST follow beta API requirements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
7. Deprecation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
SHOULD Confer with clients on accepted deprecation time-span . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
MUST reflect deprecation in API specifications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
MUST monitor usage of deprecated API scheduled for sunset. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
SHOULD add Deprecation and Sunset header to responses . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
SHOULD add monitoring for Deprecation and Sunset header . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
MUST not start using deprecated APIs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
8. JSON guidelines. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
SHOULD pluralize array names. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
MUST property names must be ASCII camelCase . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
MUST declare enum values using UPPER_SNAKE_CASE string . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
SHOULD define maps using additionalProperties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
MUST not use null for boolean properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
MUST define a default value for boolean properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
SHOULD avoid using qualifying verbs. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
SHOULD use positive semantics for boolean fields . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
MUST use a field name that suggests the value type when referencing an object . . . . . . . . . . . . . . 28
SHOULD name references to foreign objects as <objectName>Ref . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
SHOULD avoid using nested objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
MUST define a default for optional values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
MUST define the “required” attribute for request/response objects and parameters . . . . . . . . . 30
MUST use same semantics for null and absent properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
MUST use the “nullable” attribute for properties that can be null . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
MUST not use null for empty arrays. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
SHOULD define dates properties compliant with RFC 3339 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
2
SHOULD define time durations and intervals properties conform to ISO 8601 . . . . . . . . . . . . . . . . 32
9. Data formats . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
MUST use JSON as payload data interchange format . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
MAY pass non-JSON media types using data specific standard formats . . . . . . . . . . . . . . . . . . . . . . . 32
SHOULD use standard media types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
MUST use standardized property formats . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
MUST use standard date and time formats . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
MUST use standards for country, language and currency codes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
MUST define format for number and integer types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
MUST use proper description format for the filters query param . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
MUST use proper description format for the sorters query param . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
10. Common data types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
MUST use the common money object. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
MUST use common field names and semantics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
11. API naming . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
MUST/SHOULD use functional naming schema . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
MUST use lowercase separate words with hyphens for path segments. . . . . . . . . . . . . . . . . . . . . . . . 39
MUST camelCase for query parameters. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
MUST pluralize resource names . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
MUST not use /api as base path . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
MUST use normalized paths without empty path segments and trailing slashes . . . . . . . . . . . . . . . 40
MUST stick to conventional query parameters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
MUST Customer org name must never appear in the path of public APIs . . . . . . . . . . . . . . . . . . . . . 41
12. Resources . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
SHOULD avoid actions — think about resources . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
SHOULD model complete business processes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
SHOULD define useful resources . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
SHOULD keep URLs verb-free . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
MUST use domain-specific resource names . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
MUST use URL-friendly resource identifiers: [a-zA-Z0-9:._\-/]*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
MUST identify resources and sub-resources via path segments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
SHOULD consider using (non-)nested URLs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
MUST not use sequential, numerical IDs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
SHOULD limit number of resource types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
SHOULD limit number of sub-resource levels . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
13. HTTP requests and responses . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
MUST use HTTP methods correctly . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
MUST fulfill common method properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
SHOULD consider to design POST and PATCH idempotent . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
MAY use secondary key for idempotent POST design . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
MUST define collection format of header and query parameters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
3
SHOULD design simple query languages using query parameters. . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
MUST design complex query languages using JSON . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
MUST document implicit filtering . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
14. HTTP status codes and errors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
MUST specify success and error responses . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
MUST use a standard error response object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
MAY define application specific codes for the standard error response object. . . . . . . . . . . . . . . . . 55
MUST use the most accurate response example for each endpoint . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
MUST use standard HTTP status codes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
MUST use most specific HTTP status codes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
MUST use code 207 for batch or bulk requests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
MUST use code 429 with headers for rate limits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
MUST support problem JSON . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
MUST not expose stack traces. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
15. Performance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
SHOULD support partial responses via filtering . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
SHOULD allow optional embedding of sub-resources . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
16. Pagination . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
MUST support pagination . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
MAY use pagination links where applicable. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
17. Hypermedia. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
MUST use REST maturity level 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
SHOULD use full, absolute URI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
MUST use common hypertext controls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
MUST not use link headers with JSON entities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
18. Standard headers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
MAY use standardized headers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
SHOULD use uppercase separate words with hyphens for HTTP headers . . . . . . . . . . . . . . . . . . . . . 65
MUST use Content-* headers correctly. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
SHOULD use Location header instead of Content-Location header. . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
MAY use Content-Location header. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
MAY consider to support Prefer header to handle processing preferences . . . . . . . . . . . . . . . . . . . . 67
MAY consider to support ETag together with If-Match/If-None-Match header. . . . . . . . . . . . . . . . . . . 68
MAY consider to support Idempotency-Key header. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
19. API Operation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
MUST publish OpenAPI specification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
MUST monitor API usage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
Appendix A: References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
OpenAPI specification. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
Publications, specifications and standards . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
Dissertations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
4
Books. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
Blogs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
Appendix B: Tooling. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
API first integrations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
Appendix C: Best practices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
Cursor-based pagination in RESTful APIs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
Optimistic locking in RESTful APIs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
Appendix D: Changelog. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
Rule Changes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
1. Introduction
SailPoint’s SaaS software architecture centers around microservices that provide functionality via
RESTful APIs with a JSON payload. Small engineering teams own, deploy and operate these
microservices. Our APIs most purely express what our systems do, and are therefore highly
valuable business assets. Designing high-quality, long-lasting APIs has become even more critical
for us as we invest more in our SaaS platform and enabling customers and partners to build
functionalty outside of our UI.
With this in mind, we’ve adopted "API First" as one of our key engineering principles. Microservices
development begins with API definition outside the code and ideally involves ample peer-review
feedback to achieve high-quality APIs. API First encompasses a set of quality-related standards and
fosters a peer review culture including a lightweight review procedure. We encourage our teams to
follow them to ensure that our APIs:
• are general and abstracted from specific implementation and use cases
• are consistent with other teams’ APIs and our global architecture
Ideally, all SailPoint APIs will look like the same author created them.
5
Teams are responsible to fulfill these guidelines during API development and are encouraged to
contribute to guideline evolution via pull requests.
These guidelines will, to some extent, remain work in progress as our work evolves, but teams can
confidently follow and trust them.
• clients of existing APIs have to cope with these APIs based on outdated rules
Furthermore you should keep in mind that once an API becomes public externally available, it has
to be re-reviewed and changed according to current guidelines - for sake of overall consistency.
2. Principles
API design principles
Comparing SOA web service interfacing style of SOAP vs. REST, the former tend to be centered
around operations that are usually use-case specific and specialized. In contrast, REST is centered
around business (data) entities exposed as resources that are identified via URIs and can be
manipulated via standardized CRUD-like methods using different representations, and hypermedia.
RESTful APIs tend to be less use-case specific and come with less rigid client / server coupling and
are more suitable for an ecosystem of (core) services providing a platform of APIs to build diverse
new business services. We apply the RESTful web service principles to all kind of application
(micro-) service components, independently from whether they provide functionality via the
internet or intranet.
An important principle for API design and usage is Postel’s Law, aka The Robustness Principle (see
also RFC 1122):
Readings: Some interesting reads on the RESTful API design style and service architecture:
• Book: Irresistable APIs: Designing web APIs that developers will love
6
Architectures
API as a product
At SailPoint, we want to deliver products to our (internal and external) customers which can be
consumed like a service. Platform products provide their functionality via (public) APIs; hence, the
design of our APIs should be based on the API as a Product principle:
• Put yourself into the place of your customers; be an advocate for their needs
• Emphasize simplicity, comprehensibility, and usability of APIs to make them irresistible for
client engineers
• Actively improve and maintain API consistency over the long term
Embracing 'API as a Product' facilitates a service ecosystem, which can be evolved more easily and
used to experiment quickly with new business ideas by recombining core capabilities. It makes the
difference between agile, innovative product service business built on a platform of APIs and
ordinary enterprise integration business where APIs are provided as "appendix" of existing
products to support system integration and optimised for local server-side realization.
Understand the concrete use cases of your customers and carefully check the trade-offs of your API
design variants with a product mindset. Avoid short-term implementation optimizations at the
expense of unnecessary client side obligations, and have a high attention on API quality and client
developer experience.
API as a Product is closely related to our API First principle (see next chapter) which is more
focused on how we engineer high quality APIs.
API first
API First is one of our engineering and architecture principles. In a nutshell API First requires two
aspects:
• define APIs first, before coding its implementation, using a standard specification language
By defining APIs outside the code, we want to facilitate early review feedback and also a
development discipline that focus service interface design on…
• generalized business entities / resources, i.e. avoidance of use case specific APIs
• clear separation of WHAT vs. HOW concerns, i.e. abstraction from implementation aspects —
APIs should be stable even if we replace complete service implementation including its
underlying technology stack
7
Moreover, API definitions with standardized specification format also facilitate…
• single source of truth for the API specification; it is a crucial part of a contract between service
provider and client users
• infrastructure tooling for API discovery, API GUIs, API documents, automated quality checks
Elements of API First are also this API Guidelines and a standardized API review process as to get
early review feedback from peers and client developers. Peer review is important for us to get high
quality APIs, to enable architectural and design alignment and to supported development of client
applications decoupled from service provider engineering life cycle.
It is important to learn, that API First is not in conflict with the agile development principles that
we love. Service applications should evolve incrementally — and so its APIs. Of course, our API
specification will and should evolve iteratively in different cycles; however, each starting with draft
status and early team and peer review feedback. API may change and profit from implementation
concerns and automated testing feedback. API evolution during development life cycle may include
breaking changes for not yet productive features and as long as we have aligned the changes with
the clients. Hence, API First does not mean that you must have 100% domain and requirement
understanding and can never produce code before you have defined the complete API and get it
confirmed by peer review.
On the other hand, API First obviously is in conflict with the bad practice of publishing API
definition and asking for peer review after the service integration or even the service productive
operation has started. It is crucial to request and get early feedback — as early as possible, but not
before the API changes are comprehensive with focus to the next evolution step and have a certain
quality (including API Guideline compliance), already confirmed via team internal reviews.
3. General guidelines
The titles are marked with the corresponding labels: MUST, SHOULD, MAY.
• You should define APIs first, before coding their implementation, using OpenAPI as the
specification language
• You should call for early review feedback from peers and client developers
8
MUST provide API specification using OpenAPI
We use the OpenAPI specification as the standard to define API specification files. OpenAPI 3.0 must
be supported, but you MAY support other versions, like Swagger 2.
The API specification files should be subject to version control using a source code management
system.
You must publish the component API specification with the deployment of the implementing
service, and, hence, make it discoverable for the appropriate group via our API Portal.
• Major dependencies
The user manual must be published online, e.g. via our documentation hosting platform service.
Please do not forget to include a link to the API user manual into the API specification using the
#/externalDocs/url property.
This manual does not have to be created by engineering, but could be created by a
documentation team, Developer Relations or by community effort. It is important
to provide extra documentation for our developers to reduce the number of
support related questions that come in.
9
MUST provide an example for every parameter and
property
Every query/path parameter and request/response property in the API specification must have an
accurate example. An accurate example will show the API consumer what an input/output value
will realistically look like, and could even be used in a real request/response. However, take care to
not use personally identifiable information or secrets in examples.
Every operation (POST, PUT, PATCH, etc) may define one or more operation level examples.
Certain tools, like the OpenAPI Generator use this value to name the corresponding methods in
code.
get:
operationId: listAccessProfiles
tags:
- Access Profiles
summary: List Access Profiles
description: >-
This API returns a list of Access Profiles.
Use this guide when creating your operationIds. Your operationId must start with one of the
approved values for each method.
• compare
• export
• get
• list
10
• search
• get
• search
• test
POST methods
• approve
• cancel
• complete
• create
• delete
• disable
• enable
• export
• hide
• import
• move
• ping
• reject
• reset
• search
• send
• set
• show
• start
• submit
• sync
• unlock
• unregister
• update
PUT methods
• put
• set
11
PATCH methods
• patch
• update
DELETE methods
• delete
• remove
get:
operationId: listAccessProfiles
tags:
- Access Profiles
parameters:
- in: path
name: id
schema:
type: string
required: true
12
description: Account ID.
example: ef38f94347e94562b5bb8424a56397d8
x-sailpoint-resource-operation-id: listAccounts
When performing a lookup, the validator follows a structured process to extract the required path
parameter from the response of the specified operationId. For example, if an API path requires
/accounts/{accountId}, the corresponding x-sailpoint-resource-operation-id: listAccounts is
necessary. The validator will attempt to resolve the accountId by checking the response in the
following order:
1. Exact Key Match – It first looks for a key in the response that matches the path parameter
exactly accountId.
3. Final Attempt with name – If neither accountId nor id exists, it tries to use name as a last resort.
4. Validation Failure – If none of these keys are present in the response, the validator will error
out, as it cannot resolve a valid identifier for the path parameter.
This resolution process ensures flexibility while maintaining consistency in how identifiers are
determined across different API responses.
developer.sailpoint.com
Uses the x-sailpoint-resource-operation-id value to improve API discoverability and
documentation by linking path parameters to their respective operations. When an API requires a
path parameter, such as a unique resource identifier (id), this key helps determine how the value
can be retrieved. If an operation exists to list or search for the resource, its operationId can be
referenced, allowing developers to programmatically obtain the required identifier before making
a request to an endpoint that depends on it.
Enums in Path Parameters – If the path parameter is an enum, the set of possible values is
predefined and does not require lookup from an external resource. Tools can iterate through the
available values without needing an associated operationId. The API Schema Validator and other
tools can directly use the enum values to construct valid API requests, eliminating the need for
additional lookups.
parameters:
- in: path
name: type
schema:
13
type: string
enum:
- ACCESS_PROFILE
- APPLICATION
- CAMPAIGN
- ENTITLEMENT
- IDENTITY
- ROLE
- SOD_POLICY
- SOURCE
required: true
example: ACCESS_PROFILE
User-Defined Path Parameters – If the path parameter represents a value entirely determined by
the user (e.g., specifying a filename when downloading or saving a file), there is no need to retrieve
a resource identifier from the system. Since these values are not tied to any existing API resource,
defining x-sailpoint-resource-operation-id would not be meaningful or applicable.
4. Meta information
MUST contain API meta information
API specifications must contain the following OpenAPI meta information to allow for API
management:
We’ll automatically generate #/info/contact/* when creating the public Open API
spec.
• Increment the MAJOR version when you make incompatible API changes after having aligned
this changes with consumers,
• Increment the MINOR version when you add new functionality in a backwards-compatible
manner, and
14
• Optionally increment the PATCH version when you make backwards-compatible bug fixes or
editorial changes not affecting the functionality.
Additional Notes:
• Pre-release versions (rule 9) and build metadata (rule 10) must not be used in API version
information.
• While patch versions are useful for fixing typos etc, API designers are free to decide whether
they increment it or not.
• API designers should consider to use API version 0.y.z (rule 4) for initial API design.
Example:
openapi: 3.0.1
info:
title: Parcel Service API
description: API for <...>
version: 1.3.7
<...>
We’ll automatically generate the global #/info/version when creating public Open
API spec. Each individual endpoint spec author need not worry about this
attribute.
internal-company external-public
/info/x-audience:
type: string
x-extensible-enum:
- internal-company
- external-public
description: |
Intended target audience of the API. Relevant for standards around
quality of design and documentation, reviews, discoverability,
changeability.
15
Exactly one audience per API specification is allowed. For this reason a smaller
audience group is intentionally included in the wider group and thus does not
need to be declared additionally. If parts of your API have a different target
audience, we recommend to split API specifications along the target audience —
even if this creates redundancies (rationale (internal link)).
Example:
openapi: 3.0.1
info:
x-audience: internal-company
title: Service to Service API
description: API for <...>
version: 1.2.4
<...>
5. Security
MUST secure endpoints with OAuth 2.0
Every public API endpoint must be secured using OAuth 2.0. We have defined two security schemas,
userAuth and applicationAuth. In the rare case that an endpoint does not require authentication,
and is completely open to the public, then an empty object can be used instead.
• userAuth is the authentication mechanism that assigns a user context to a token, such as a
Personal Access Token or OAuth token granted via authorization code.
• applicationAuth is the authentication mechanism that provides a token that does not have a
user context. This is a result of the OAuth client credentials grant flow.
Each endpoint must select one or both of the security schemes depending on which one(s) it
supports. For example, if an endpoint supports only userAuth, then the path would be defined as
follows:
get:
operationId: getAccessProfile
tags:
- Access Profiles
summary: Get an Access Profile
description: >-
This API returns an Access Profile by its ID.
security:
- userAuth: [idn:access-profile:read]
If the endpoint supports both security schemes, then it would be defined as follows:
16
get:
operationId: getAccessProfile
tags:
- Access Profiles
summary: Get an Access Profile
description: >-
This API returns an Access Profile by its ID.
security:
- userAuth: [idn:access-profile:read]
- applicationAuth: [idn:access-profile:read]
If the endpoint does not require authentication, then it would be defined as follows:
get:
operationId: getAccessProfile
tags:
- Access Profiles
summary: Get an Access Profile
description: >-
This API returns an Access Profile by its ID.
security:
- {}
security:
- userAuth:
- 'idn:task-definition:read'
- 'idn:task-definition:manage'
get:
tags:
- tenants
summary: Get Tenant
description: Get tenant object based on current auth token
operationId: getTenant
responses:
'200':
description: successful operation
17
content:
application/json:
schema:
$ref: '../schemas/Tenant.yaml'
security:
- userAuth:
- 'idn:accounts:read'
x-sailpoint-userLevels:
- ORG_ADMIN
- SOURCE_ADMIN
get:
tags:
- tenants
summary: Get Tenant
description: Get tenant object based on current auth token
operationId: getTenant
responses:
'200':
description: successful operation
content:
application/json:
schema:
$ref: '../schemas/Tenant.yaml'
security:
- userAuth:
- 'idn:accounts:read'
x-sailpoint-userLevels:
- ORG_ADMIN
- SOURCE_ADMIN
18
MUST Document necessary license add-on to use an
API collection
If an API collection requires additional product licenses to enable the feature, then each required
license must be documented in the API collection description.
6. Compatibility
MUST not break backward compatibility
Change APIs, but keep all consumers running. Consumers usually have independent release
lifecycles, focus on stability, and avoid changes that do not provide additional value. APIs are
contracts between service providers and service consumers that cannot be broken via unilateral
decisions.
We strongly encourage using compatible API extensions and discourage versioning (see SHOULD
avoid versioning and MUST use URI versioning below). The following guidelines for service
providers (SHOULD prefer compatible extensions) and consumers (MUST prepare clients to accept
compatible API extensions) enable us (having Postel’s Law in mind) to make compatible changes
without versioning.
Note: There is a difference between incompatible and breaking changes. Incompatible changes are
changes that are not covered by the compatibility rules below. Breaking changes are incompatible
changes deployed into operation, and thereby breaking running API consumers. Usually,
incompatible changes are breaking changes when deployed into operation. However, in specific
controlled situations it is possible to deploy incompatible changes in a non-breaking way, if no API
consumer is using the affected API aspects (see also Deprecation guidelines).
Hint: Please note that the compatibility guarantees are for the "on the wire" format. Binary or
source compatibility of code generated from an API definition is not covered by these rules. If client
implementations update their generation process to a new version of the API definition, it has to be
expected that code changes are necessary.
19
SHOULD prefer compatible extensions
API designers should apply the following rules to evolve RESTful APIs for services in a backward-
compatible way:
• Never change the semantic of fields (e.g. changing the semantic from customer-number to
customer-id, as both are different unique customer keys)
• Input fields may have (complex) constraints being validated via server-side business logic.
Never change the validation logic to be more restrictive and make sure that all constraints are
clearly defined in description.
• Enum ranges can be reduced when used as input parameters, only if the server is ready to
accept and handle old range values too. Enum range can be reduced when used as output
parameters.
• Enum ranges cannot be extended when used for output parameters — clients may not be
prepared to handle it. However, enum ranges can be extended when used for input parameters.
• Be conservative with API requests and data passed as input, e.g. avoid to exploit definition
deficits like passing megabytes of strings with unspecified maximum length.
Service clients must be prepared for compatible API extensions of service providers:
• Be tolerant with unknown fields in the payload (see also Fowler’s "TolerantReader" post), i.e.
ignore new fields but do not eliminate them from payload if needed for subsequent PUT
requests.
• Be prepared that x-extensible-enum return parameter may deliver new values; either be
agnostic or provide default behavior for unknown values.
• Be prepared to handle HTTP status codes not explicitly specified in endpoint definitions. Note
also, that status codes are extensible. Default handling is how you would treat the
corresponding 2xx code (see RFC 7231 Section 6).
• Follow the redirect when the server returns HTTP status code 301 (Moved Permanently).
20
• Unknown input fields in payload or URL should not be ignored; servers should provide error
feedback to clients via an HTTP 400 response code.
• Be accurate in defining input data constraints (like formats, ranges, lengths etc.) — and check
constraints and return dedicated error information in case of violations.
• Prefer being more specific and restrictive (if compliant to functional requirements), e.g. by
defining length range of strings. It may simplify implementation while providing freedom for
further evolution as compatible extensions.
Not ignoring unknown input fields is a specific deviation from Postel’s Law (e.g. see also
The Robustness Principle Reconsidered) and a strong recommendation. Servers might want to take
different approach but should be aware of the following problems and be explicit in what is
supported:
• Ignoring unknown input fields is actually not an option for PUT, since it becomes asymmetric
with subsequent GET response and HTTP is clear about the PUT replace semantics and default
roundtrip expectations (see RFC 7231 Section 4.3.4). Note, accepting (i.e. not ignoring) unknown
input fields and returning it in subsequent GET responses is a different situation and compliant
to PUT semantics.
• Certain client errors cannot be recognized by servers, e.g. attribute name typing errors will be
ignored without server error feedback. The server cannot differentiate between the client
intentionally providing an additional field versus the client sending a mistakenly named field,
when the client’s actual intent was to provide an optional input field.
• Future extensions of the input data structure might be in conflict with already ignored fields
and, hence, will not be compatible, i.e. break clients that already use this field but with different
type.
In specific situations, where a (known) input field is not needed anymore, it either can stay in the
API definition with "not used anymore" description or can be removed from the API definition as
long as the server ignores this specific parameter.
Maps (see SHOULD define maps using additionalProperties), even though technically objects, are
also forbidden as top level data structures, since they don’t support compatible, future extensions.
21
JSON-Schema keywords related to extensibility, like additionalProperties. Following our
compatibility guidelines, OpenAPI object definitions are considered open for extension by default
as per Section 5.18 "additionalProperties" of JSON-Schema.
When it comes to OpenAPI, this means an additionalProperties declaration is not required to make
an object definition extensible:
• API clients consuming data must not assume that objects are closed for extension in the absence
of an additionalProperties declaration and must ignore fields sent by the server they cannot
process. This allows API servers to evolve their data formats.
• For API servers receiving unexpected data, the situation is slightly different. Instead of ignoring
fields, servers may reject requests whose entities contain undefined fields in order to signal to
clients that those fields would not be stored on behalf of the client. API designers must
document clearly how unexpected fields are handled for PUT, POST, and PATCH requests.
API formats must not declare additionalProperties to be false, as this prevents objects being
extended in the future.
Note that this guideline concentrates on default extensibility and does not exclude the use of
additionalProperties with a schema as a value, which might be appropriate in some circumstances,
e.g. see SHOULD define maps using additionalProperties.
If changing an API can’t be done in a compatible way, then proceed in one of these three ways:
• create a new service endpoint — i.e. a new application with a new API (with a new domain
name)
• create a new API version supported in parallel with the old API by the same microservice
/v{version number}
22
MUST follow versioned API requirements
All versioned APIs must adhere to the following requirements
• Supported (sev-1!)
Breaking changes include, but are not limited to, the following:
• Changing an existing field on a post/put/patch from optional to required (or not permitted)
• Removal of resources
• URI changes
• Other semantic changes (including new values in enumerated types if those values are part of
the API contract)
• Example: Consider a list API that returns multiple object types with a common standardized
representation and the objects have a TYPE field. Adding a new TYPE value would represent a
breaking change UNLESS the the contract for the API specifies that new types may be added. If
there is no means of filtering based on TYPE in this example, this is probably an unacceptable
term for the API contract.
If in doubt, ask! Call out any uncertainties during API review or during the design process.
Non-breaking changes may be added to an existing live version. Here are some examples of non-
breaking changes:
• New fields on response models (that do not change the meaning of the model)
• Adding new optional fields to input models (as long as the default value of the field preserves
the previous meaning of the model)
• New query parameters and filterable/sortable fields (as long as they are optional and the
existing behavior is preserved if the new parameters are not passed)
23
• Documented in OpenAPI specification format (and marked as beta)
• This is expected to be a stable, usable API that is available for external and internal integration.
7. Deprecation
Sometimes it is necessary to phase out an API endpoint, an API version, or an API feature, e.g. if a
field or parameter is no longer supported or a whole business functionality behind an endpoint is
supposed to be shut down. As long as the API endpoints and features are still used by consumers
these shut downs are breaking changes and not allowed. To progress the following deprecation
rules have to be applied to make sure that the necessary consumer changes and actions are well
communicated and aligned using deprecation and sunset dates.
If an API endpoint (operation object), an input argument (parameter object), an in/out data object
(schema object), or on a more fine grained level, a schema attribute or property should be
deprecated, the producers must set deprecated: true for the affected element and add further
explanation to the description section of the API specification. If a future shut down is planned, the
producer must provide a sunset date and document in details what consumers should use instead
and how to migrate.
Must notify customers using deprecated APIs on a timely basis to help them move onto newer APIs,
24
especially as the API moves closer to its sunset date.
The Deprecation header can either be set to true - if a feature is retired -, or carry a deprecation time
stamp, at which a replacement will become/became available and consumers must not on-board
any longer (see MUST not start using deprecated APIs). The optional Sunset time stamp carries the
information when consumers latest have to stop using a feature. The sunset date should always
offer an eligible time interval for switching to a replacement feature.
If multiple elements are deprecated the Deprecation and Sunset headers are expected to be set to the
earliest time stamp to reflect the shortest interval consumers are expected to get active.
Note: adding the Deprecation and Sunset header is not sufficient to gain client consent to shut down
an API or feature.
Hint: In earlier guideline versions, we used the Warning header to provide the deprecation info to
clients. However, Warning header has a less specific semantics, will be obsolete with draft: RFC HTTP
Caching, and our syntax was not compliant with RFC 7234 — Warning header.
Hint: In earlier guideline versions, we used the Warning header to provide the deprecation info (see
hint in SHOULD add Deprecation and Sunset header to responses ).
25
8. JSON guidelines
These guidelines provides recommendations for defining JSON data at SailPoint. JSON here refers to
RFC 7159 (which updates RFC 4627), the "application/json" media type and custom JSON media
types defined for APIs. The guidelines clarifies some specific cases to allow SailPoint JSON data to
have an idiomatic form across teams and services.
The first some of the following guidelines are about property names, the later ones about values.
“ID” is common in field names, and must be presented in camel case as follows:
• If “ID” appears as the first word, then it is entirely lowercase (ex. “id”).
• If “ID” appears after the first word, then the “I” is capitalized and the “d” is lowercase (ex.
“userId”).
Exception: This rule does not apply for case sensitive values sourced from outside API definition
scope, e.g. for language codes from ISO 639-1, or when declaring possible values for a rule 137 [sort
parameter].
The map keys don’t count as property names in the sense of rule 118, and can follow whatever
26
format is natural for their domain. Please document this in the description of the map object’s
schema.
components:
schemas:
Message:
description:
A message together with translations in several languages.
type: object
properties:
message_key:
type: string
description: The message key.
translations:
description:
The translations of this message into several languages.
The keys are [IETF BCP-47 language
tags](https://tools.ietf.org/html/bcp47).
type: object
additionalProperties:
type: string
description:
the translation of this message into the language identified by the key.
An actual JSON object described by this might then look like this:
{ "message_key": "color",
"translations": {
"de": "Farbe",
"en-US": "color",
"en-GB": "colour",
"eo": "koloro",
"nl": "kleur"
}
}
27
MUST define a default value for boolean properties
All optional boolean properties must have a default value defined. If the property is required, it is
not required to have a default value.
• Discouraged: isEnabled
• Recommended: enabled
For example, the following request/response for an account object uses the proper naming for
object references.
“id” refers to the account object being requested, and all other object references include the object
reference name (i.e. sourceId, identityId, etc.)
{
"id": "id12345",
"name": "aName",
"created": "2019-08-24T14:15:22Z",
"modified": "2019-08-24T14:15:22Z",
"sourceId": "2c9180835d2e5168015d32f890ca1581",
"identityId": "2c9180835d2e5168015d32f890ca1581",
"attributes": { },
"authoritative": true,
"description": "string",
"disabled": true,
28
"locked": true,
"nativeIdentity": "string",
"systemAccount": true,
"uncorrelated": true,
"uuid": "string",
"manuallyCorrelated": true,
"hasEntitlements": true
}
Example
{
"id": "2c9180857182305e0171993735622948",
"name": "Alison Ferguso",
"alias": "alison.ferguso",
"email": "[email protected]",
"status": "Active",
"managerRef": {
"type": "IDENTITY",
"id": "2c9180a46faadee4016fb4e018c20639",
"name": "Thomas Edison"
},
"attributes":[]
}
It is recognized, of course, that particular use cases may require nesting objects inside each other.
For example, if a UI module needs to display data from a set of 100 IdentityRequests and their child
IdentityRequestItems, it makes no sense to require the UI to make one API call to get the list of
IdentityRequests and then 100 additional calls to get the IdentityRequestItems for each.
It is preferable in these cases to use a summary DTO for the nested objects that contains the
minimum amount of detail required to support the known or plausible use case(s). For example, if
the only reason I need to include the owner of an object is so the caller can display their first and
29
last name, then it is better to do something like the following:
{
...
"owner": {
"type": "IDENTITY",
"id": "2c90b0c06460804b016460f9f59b001",
"firstName": "Frank",
"lastName": "Dogs"
}
}
One particular valid use of nested objects occurs when a DTO abstracts over a set of types that may
have significantly different attributes. In this case the non-general fields of the DTO should be
pushed down to a nested object, with a type field on the main object being used as a discriminator.
For example, if a DTO could represent either an Access Profile or a Role, the former case could be
implemented as follows:
{
...
"type": "ACCESS_PROFILE",
...
"accessProfileInfo": {
"appRefs": ["app1", "app2"]
},
"roleInfo": null
}
Generally, query parameters should be optional, but there are cases where a query parameter is
30
required. In these cases, make sure to set the “required” attribute for the query parameters to true.
• for "date" use strings matching date-fullyear "-" date-month "-" date-mday, for example: 2015-
05-28
• for "date-time" use strings matching full-date "T" full-time, for example 2015-05-28T14:07:17Z
Note that the OpenAPI format "date-time" corresponds to "date-time" in the RFC) and 2015-05-28 for
a date (note that the OpenAPI format "date" corresponds to "full-date" in the RFC). Both are specific
profiles, a subset of the international standard ISO 8601.
A zone offset may be used (both, in request and responses) — this is simply defined by the
standards. However, we encourage restricting dates to UTC and without offsets. For example 2015-
05-28T14:07:17Z rather than 2015-05-28T14:07:17+00:00. From experience we have learned that zone
offsets are not easy to understand and often not correctly handled. Note also that zone offsets are
different from local times that might be including daylight saving time. Localization of dates should
be done by the services that provide user interfaces, if required.
When it comes to storage, all dates should be consistently stored in UTC without a zone offset.
Localization should be done locally by the services that provide user interfaces, if required.
Sometimes it can seem data is naturally represented using numerical timestamps, but this can
introduce interpretation issues with precision, e.g. whether to represent a timestamp as
1460062925, 1460062925000 or 1460062925.000. Date strings, though more verbose and requiring
more effort to parse, avoid this ambiguity.
31
SHOULD define time durations and intervals
properties conform to ISO 8601
Schema based JSON properties that are by design durations and intervals could be strings
formatted as recommended by ISO 8601 (Appendix A of RFC 3339 contains a grammar for
durations).
9. Data formats
MUST use JSON as payload data interchange format
Use JSON (RFC 7159) to represent structured (resource) data passed with HTTP requests and
responses as body payload. The JSON payload must use a JSON object as top-level data structure (if
possible) to allow for future extension. This also applies to collection resources, where you ad-hoc
would use an array — see also MUST always return JSON objects as top-level data structures.
Additionally, the JSON payload must comply to the more restrictive Internet JSON (RFC 7493),
particularly
• consist of valid Unicode strings, i.e. must not contain non-characters or surrogates, and
You should avoid using custom media types like application/x.sailpoint.article+json. Custom
media types beginning with x bring no advantage compared to the standard media type for JSON,
and make automated processing more difficult.
32
MUST encode embedded binary data in base64url
Exposing binary data using an alternative media type is generally preferred. See the rule above.
If an alternative content representation is not desired then binary data should be embedded into
the JSON document as a base64url-encoded string property following RFC 7493 Section 4.4.
33
Remark: Please note that this list of standard data formats is not exhaustive and everyone is
encouraged to propose additions.
Read more about date and time format in SHOULD define dates properties compliant with RFC
3339.
HTTP headers
Http headers including the proprietary headers use the HTTP date format defined in RFC 7231.
◦ Hint: It is "GB", not "UK", even though "UK" has seen some use at sailpoint
The precision must be translated by clients and servers into the most specific language types. E.g.
34
for the following definitions the most specific language types in Java will translate to BigDecimal for
Money.amount and int or Integer for the OrderList.page_size:
components:
schemas:
Money:
type: object
properties:
amount:
type: number
description: Amount expressed as a decimal number of major currency units
format: decimal
example: 99.95
...
OrderList:
type: object
properties:
page_size:
type: integer
description: Number of orders in list
format: int32
example: 42
in: query
name: filters
schema:
type: string
description: >-
Filter results using the standard syntax described in [V3 API Standard Collection
Parameters](https://developer.sailpoint.com/idn/api/standard-collection-
parameters#filtering-results)
35
**name**: *eq, sw*
**requestable**: *eq*
• Always include the first two lines in your description to point users towards the extended
documenation.
• Always use two new lines between lines. The use of one new line will result in the text not
displaying on separate lines in the API documentaiton.
• Always bold the property name using two asterisks before and after the name.
• Always italicize the operators by using a single asterisk before an after the list of operators.
• Only use the operators listed here. If you are introducing a new operator not in the list, please
contact the Developer Relations team to update the list.
• Do not add any additional text in the description, such as examples, clarifications, etc.
in: query
name: sorters
schema:
type: string
format: comma-separated
description: >-
36
Sort results using the standard syntax described in [V3 API Standard Collection
Parameters](https://developer.sailpoint.com/idn/api/standard-collection-
parameters#sorting-results)
• Always include the first line in your description to point users towards the extended
documenation.
• Always use two new lines between lines. The use of one new line will result in the text not
displaying on separate lines in the API documentaiton.
• The second line must start with "Sorting is supported for the following fields:"
• Do not add any additional lines or text in the description. There should only be two lines: the
extended documentation and the list of supporter properties.
• Money object
• Problem object
Money:
type: object
properties:
amount:
type: number
description: >
The amount describes unit and subunit of the currency in a single value,
where the integer part (digits before the decimal point) is for the
major unit and fractional part (digits after the decimal point) is for
the minor unit.
format: decimal
example: 99.95
currency:
type: string
37
description: 3 letter currency code as defined by ISO-4217
format: iso-4217
example: EUR
required:
- amount
- currency
APIs are encouraged to include a reference to the global schema for Money.
SalesOrder:
properties:
grand_total:
$ref: 'https://sailpoint-oss.github.io/sailpoint-api-guidelines/money-
1.0.0.yaml#/Money'
Please note that APIs have to treat Money as a closed data type, i.e. it’s not meant to be used in an
inheritance hierarchy. That means the following usage is not allowed:
{
"amount": 19.99,
"currency": "EUR",
"discounted_amount": 9.99
}
Cons
• Less flexible since both amounts are coupled together, e.g. mixed currencies are impossible
{
"price": {
"amount": 19.99,
"currency": "EUR"
},
"discounted_price": {
"amount": 9.99,
"currency": "EUR"
}
}
38
Pros
Notes
Please be aware that some business cases (e.g. transactions in Bitcoin) call for a higher precision, so
applications must be prepared to accept values with unlimited precision, unless explicitly stated
otherwise in the API specification.
• 0.23 = 23 Cent
• 42.0 or 42 = 42 Euros
Make sure that you don’t convert the "amount" field to float / double types when implementing this
interface in a specific language or when doing calculations. Otherwise, you might lose precision.
Instead, use exact formats like Java’s BigDecimal. See Stack Overflow for more info.
Some JSON parsers (NodeJS’s, for example) convert numbers to floats by default. After discussing
the pros and cons we’ve decided on "decimal" as our amount format. It is not a standard OpenAPI
format, but should help us to avoid parsing numbers as float / doubles.
39
• Use {camelCase} with surrounding brackets to indicate path parameters
Example:
/shipment-orders/{shipmentOrderId}
We need to have a consistent look and feel for our APIs. In the case of query parameters, which can
reference actual properties in the response object, camelCase preserves a consistent look and feel.
Example:
/users, /companies/{companyId}/employees/{employeeId}
If the service should also support non-public, internal APIs — for specific operational support
functions, for example — we encourage you to maintain two different API specifications and
provide API audience. For both APIs, you should not use /api as base path.
We see API’s base path as a part of deployment variant configuration. Therefore, this information
has to be declared in the server object.
Reasoning: Non standard paths have no clear semantics. As a result, behavior for non standard
paths varies between different HTTP infrastructure components and libraries. This may leads to
40
ambiguous and unexpected results during request handling and monitoring.
We recommend to implement services robust against clients not following this rule. All services
should normalize request paths before processing by removing duplicate and trailing slashes.
Hence, the following requests should refer to the same resource:
GET /orders/{orderId}
GET /orders/{orderId}/
GET /orders//{orderId}
Note: path normalization is not supported by all framework out-of-the-box. Services are required to
support at least the normalized path while rejecting all alternatives paths, if failing to deliver the
same resource.
Pagination
• limit: Integer that specifies the maximum number of results to return. If not specified a default
limit will be used.
• offest: Integer that specifies the offset of the first result from the beginning of the collection
• count: Boolean that indicates whether a total count will be returned, factoring in any filter
parameters, in the X-Total-Count response header.
filters: an item will only be included in the returned array if the filters expression evaluates to
true for that item. Each endpoint that implements filters must clearly define in the API spec what
operations and fields are supported.
sorters: a set of comma-separated field names. Each field name may be optionally prefixed with a "-
" character, which indicates the sort is descending based on the value of that field. Otherwise, the
sort is ascending. Each endpoint that implements sorters must clearly define which fields are
supported.
See https://developer.sailpoint.com/docs/standard_collection_parameters.html#standard-collection-
parameters for implementation details for each of the above parameters.
Note: Additional query parameters are allowed, but effort should be made to fit them within the
five listed above.
41
12. Resources
SHOULD avoid actions — think about resources
REST is all about your resources, so consider the domain entities that take part in web service
interaction, and aim to model your API around these using the standard HTTP methods as
operation indicators. For example, rather than creating a specific action for completing a
certification campaign, prefer to use PATCH to update the completed status of the campaign.
Request
PATCH v1/campaigns/{campaignId}
Body
{
“completed”: true
}
Sometimes, standard HTTP methods aren’t specific enough to indicate the action you wish to
perform on a resource, or there is complex business logic on the back end that can’t be satisfied by
PATCHing a single field. In these cases, it is advisable to use the following URI format for specific
resource actions:
Request
POST v1/campaigns/{campaignId}/remediation-scan
42
putting a message in a letter box: e.g., instead of having the verb cancel in the url, think of sending a
message to cancel an order to the cancellations letter box on the server side.
/resources/{resourceId}/sub-resources/{subResourceId}
In order to improve the consumer experience, you should aim for intuitively understandable URLs,
where each sub-path is a valid reference to a resource or a set of resources. For instance, if
/partners/{partnerId}/addresses/{addressId} is valid, then, in principle, also
/partners/{partnerId}/addresses, /partners/{partnerId} and /partners must be valid. Examples of
concrete url paths:
/shopping-carts/de:1681e6b88ec1/items/1
/shopping-carts/de:1681e6b88ec1
/content/images/9cacb4d8
/content/images
Exception: In some situations the resource identifier is not passed as a path segment but via the
authorization information, e.g. an authorization token or session cookie. Here, it is reasonable to
use self as pseudo-identifier path segment. For instance, you may define /employees/self or
/employees/self/personal-details as resource paths — and may additionally define endpoints that
support identifier passing in the resource path, like define /employees/{emplId} or
/employees/{emplId}/personal-details.
43
SHOULD consider using (non-)nested URLs
If a sub-resource is only accessible via its parent resource and may not exist without parent
resource, consider using a nested URL structure, for instance:
/shoping-carts/de/1681e6b88ec1/cart-items/1
However, if the resource can be accessed directly via its unique id, then the API should expose it as
a top-level resource. For example, customer has a collection for sales orders; however, sales orders
have globally unique id and some services may choose to access the orders directly, for instance:
/customers/1637asikzec1
/sales-orders/5273gh3k525a
Creating an Object
POST requests are idiomatically used to create single resources on a collection resource endpoint,
but other semantics on single resources endpoint are equally possible. The semantic for collection
endpoints is best described as "please add the enclosed representation to the collection resource
44
identified by the URL". The semantic for single resource endpoints is best described as "please
execute the given well specified request on the resource identified by the URL".
• Modeled as POST /…/plural-noun, where plural-noun indicates the type of object being created.
• on a successful POST request, the server will create one or multiple new resources and provide
their URI/URLs in the response
• successful POST requests will usually generate 200 (if resources have been updated), 201 (if
resources have been created), 202 (if the request was accepted but has not been finished yet),
and exceptionally 204 with Location header (if the actual resource is not returned).
• If the POST is used to create or update a resource, then the response payload needs to include
every field from the request, and may include additional fields.
• If the POST is used as an action, then the response may be different from the request schema.
• Specifying a value for a system-generated field in the input results in a 400 Bad Request
response.
Note: By using POST to create resources the resource ID must not be passed as request input date by
the client, but created and maintained by the service and returned with the response payload.
Apart from resource creation, POST should be also used for scenarios that cannot be covered by the
other methods sufficiently. However, in such cases make sure to document the fact that POST is used
as a workaround (see e.g. GET with body).
Hint: Posting the same resource twice is not required to be idempotent (check MUST fulfill
common method properties) and may result in multiple resources. However, you SHOULD
consider to design POST and PATCH idempotent to prevent this.
• Query params are allowed, for example, to return the object at different levels of detail.
• Is free of side-effects.
[
{
"id": "123",
"name": "John"
},
...
45
]
• Proposed for API version 4, on success, returns a 200 with a JSON array of objects enveloped
inside an object. By returning an object as the top level for all responses, we allow our APIs to
extend without breaking backwards compatibility. If there was ever a need to add an additional
field to a response that returns an array (ex. pagination links in the body), then we would need
to break the API by wrapping it in an object. By requiring objects at the top level from the start,
we avoid this in the future.
{
"results": [...],
"count": ...
}
• Supports pagination via limit and offset query parameters unless the back end data store
makes this impossible or prohibitively expensive.
• The default value for limit is 250 unless the endpoint documentation states otherwise.
• The standard filters query parameter is preferred over custom filtering query params.
• If filters are used, the supported fields and operations are whitelisted. Unsupported filters
should result in an error response.
• If filters refer to fields in nested objects, then "." notation is used, for example
filters=owner.name eq "leah.pierce"
• If at all possible, supports reading a list of objects by their ids, either in the form of a filter, i.e.
filters=id in (id0, id1, …, idN) or a custom query param.
• Use of single boolean-valued params should be avoided; strings, enumerated values, or comma-
separated values are preferred.
• Results are not implicitly filtered or scoped based on the current logged in user. If such filtering
is required it is via an explicit query param taking an identity id. By convention, me can stand
in for the currently logged in user’s identity id as the value for such a param.
• Is free of side-effects.
APIs sometimes face the problem, that they have to provide extensive structured request
information with GET, that may conflict with the size limits of clients, load-balancers, and servers.
As we require APIs to be standard conform (request body payload in GET must be ignored on
server side), API designers have to check the following two options:
1. GET with URL encoded query parameters: when it is possible to encode the request information
in query parameters, respecting the usual size limits of clients, gateways, and servers, this
46
should be the first choice. The request information can either be provided via multiple query
parameters or by a single structured URL encoded string.
2. POST with body payload content: when a GET with URL encoded query parameters is not
possible, a POST request with body payload must be used, and explicitly documented with a
hint like in the following example:
paths:
/products:
post:
description: >
[GET with body payload](https://sailpoint-oss.github.io/sailpoint-api-
guidelines/#get-with-body) - no resources created:
Returns all products matching the query passed as request input payload.
requestBody:
required: true
content:
...
PUT requests are used to update (and sometimes to create) entire resources – single or collection
resources. The semantic is best described as "please put the enclosed representation at the resource
mentioned by the URL, replacing any existing resource.".
• PUT requests are usually applied to single resources, and not to collection resources, as this
would imply replacing the entire collection
• PUT requests are usually robust against non-existence of resources by implicitly creating the
resource before updating
• on successful PUT requests, the server will replace the entire resource addressed by the URL
with the representation passed in the payload (subsequent reads will deliver the same payload)
• successful PUT requests will usually generate 200 or 204 (if the resource was updated – with or
without actual content returned), and 201 (if the resource was created)
• Returns a 404 if the object does not exist and the endpoint does not support PUT as a means of
creation.
• Does a complete replacement of the referenced object and does not attempt to merge the input
DTO with the existing object.
Important: It is good practice to prefer POST over PUT for creation of (at least top-level) resources.
This leaves the resource ID management under control of the service and not the client, and focus
PUT on its usage for updates. However, in situations where PUT is used for resource creation, the
resource IDs are maintained by the client and passed as a URL path segment. Putting the same
resource twice is required to be idempotent and to result in the same single resource instance (see
MUST fulfill common method properties).
47
Hint: To prevent unnoticed concurrent updates and duplicate creations when using PUT, you MAY
consider to support ETag together with If-Match/If-None-Match header to allow the server to react on
stricter demands that expose conflicts and prevent lost updates. See also Optimistic locking in
RESTful APIs for details and options.
PATCH requests are used to update parts of single resources, i.e. where only a specific subset of
resource fields should be replaced. The semantic is best described as "please change the resource
identified by the URL according to my change request". The semantic of the change request is not
defined in the HTTP standard and must be described in the API specification by using suitable
media types.
• PATCH requests are usually applied to single resources as patching entire collection is challenging
• PATCH requests are usually not robust against non-existence of resource instances
• on successful PATCH requests, the server will update parts of the resource addressed by the URL
as defined by the change request in the payload
• successful PATCH requests will usually generate 200 or 204 (if resources have been updated with
or without updated content returned)
Deleting an Object
DELETE requests are used to delete resources. The semantic is best described as "please delete the
resource identified by the URL".
• DELETE requests are usually applied to single resources, not on collection resources, as this
would imply deleting the entire collection.
• DELETE request can be applied to multiple resources at once using query parameters on the
collection resource (see DELETE with query parameters).
• successful DELETE requests will usually generate 200 (if the deleted resource is returned) or 204
(if no content is returned).
• failed DELETE requests will usually generate 404 (if the resource cannot be found) or 410 (if the
resource was already deleted before).
Important: After deleting a resource with DELETE, a GET request on the resource is expected to either
return 404 (not found) or 410 (gone) depending on how the resource is represented after deletion.
Under no circumstances the resource must be accessible after this operation on its endpoint.
48
DELETE with query parameters
DELETE request can have query parameters. Query parameters should be used as filter parameters
on a resource and not for passing context information to control the operation behavior.
DELETE /resources?param1=value1¶m2=value2...¶mN=valueN
Note: When providing DELETE with query parameters, API designers must carefully document the
behavior in case of (partial) failures to manage client expectations properly.
The response status code of DELETE with query parameters requests should be similar to usual
DELETE requests. In addition, it may return the status code 207 using a payload describing the
operation results (see MUST use code 207 for batch or bulk requests for details).
In rare cases DELETE may require additional information, that cannot be classified as filter
parameters and thus should be transported via request body payload, to perform the operation.
Since RFC-7231 states, that DELETE has an undefined semantic for payloads, we recommend to utilize
POST. In this case the POST endpoint must be documented with the hint DELETE with body analog to
how it is defined for GET with body. The response status code of DELETE with body requests should be
similar to usual DELETE requests.
HEAD (Optional)
HEAD requests are used to retrieve the header information of single resources and resource
collections.
• HEAD has exactly the same semantics as GET, but returns headers only, no body.
Hint: HEAD is particular useful to efficiently lookup whether large resources or collection resources
have been updated in conjunction with the ETag-header.
OPTIONS (Optional)
OPTIONS requests are used to inspect the available operations (HTTP methods) of a given endpoint.
• OPTIONS responses usually either return a comma separated list of methods in the Allow header
or as a structured list of link templates
Note: OPTIONS is rarely implemented, though it could be used to self-describe the full functionality
of a resource.
• safe - the operation semantic is defined to be read-only, meaning it must not have intended side
49
effects, i.e. changes, to the server state.
• idempotent - the operation has the same intended effect on the server state, independently
whether it is executed once or multiple times. Note: this does not require that the operation is
returning the same response or status code.
• cacheable - to indicate that responses are allowed to be stored for future reuse. In general,
requests to safe methods are cachable, if it does not require a current or authoritative response
from the server.
Note: The above definitions, of intended (side) effect allows the server to provide additional state
changing behavior as logging, accounting, pre- fetching, etc. However, these actual effects and state
changes, must not be intended by the operation so that it can be held accountable.
Method implementations must fulfill the following basic properties according to RFC 7231:
• A resource specific conditional key provided via If-Match header in the request. The key is in
general a meta information of the resource, e.g. a hash or version number, often stored with it. It
allows to detect concurrent creations and updates to ensure idempotent behavior (see MAY
consider to support ETag together with If-Match/If-None-Match header).
• A resource specific secondary key provided as resource property in the request body. The
secondary key is stored permanently in the resource. It allows to ensure idempotent behavior by
looking up the unique secondary key in case of multiple independent resource creations from
different clients (see MAY use secondary key for idempotent POST design).
• A client specific idempotency key provided via Idempotency-Key header in the request. The key
50
is not part of the resource but stored temporarily pointing to the original response to ensure
idempotent behavior when retrying a request (see MAY consider to support Idempotency-Key
header).
Note: While conditional key and secondary key are focused on handling concurrent requests, the
idempotency key is focused on providing the exact same responses, which is even a stronger
requirement than the idempotency defined above. It can be combined with the two other patterns.
To decide, which pattern is suitable for your use case, please consult the following table showing
the major properties of each pattern:
Note: The patterns applicable to PATCH can be applied in the same way to PUT and DELETE providing
the same properties.
If you mainly aim to support safe retries, we suggest to apply conditional key and secondary key
pattern before the Idempotency Key pattern.
The secondary key is stored permanently in the resource as alternate key or combined key (if
consisting of multiple properties) guarded by a uniqueness constraint enforced server-side, that is
visible when reading the resource. The best and often naturally existing candidate is a unique
foreign key, that points to another resource having one-on-one relationship with the newly created
resource, e.g. a parent process identifier.
A good example here for a secondary key is the shopping cart ID in an order resource.
Note: When using the secondary key pattern without Idempotency-Key all subsequent retries should
fail with status code 409 (conflict). We suggest to avoid 200 here unless you make sure, that the
delivered resource is the original one implementing a well defined behavior. Using 204 without
content would be a similar well defined option.
51
MUST define collection format of header and query
parameters
TBD
Some APIs will have a need for sophisticated and more complex query languages. Dominant
examples are APIs around search (incl. faceting) and product catalogs.
Aspects that set those APIs apart from the rest include but are not limited to:
APIs that qualify for a specific, complex query language are encouraged to use nested JSON data
structures and define them using OpenAPI directly. The provides the following benefits:
JSON-specific rules and most certainly needs to make use of the GET-with-body pattern.
52
Example
The following JSON document should serve as an idea how a structured query might look like.
{
"and": {
"name": {
"match": "Alice"
},
"age": {
"or": {
"range": {
">": 25,
"<=": 50
},
"=": 65
}
}
}
}
• GraphQL: Queries
In such cases, the fact that implicit filtering is applied must be documented in the API
specification’s endpoint description. Example:
If an employee of the company Foo accesses one of our business-to-business service and performs a
GET /business-partners, it must, for legal reasons, not display any other business partner that is not
owned or contractually managed by her/his company. It should never see that we are doing
business also with company Bar.
{
"items": [
53
{ "name": "Foo Performance" },
{ "name": "Foo Sport" },
{ "name": "Foo Signature" }
]
}
{
"items": [
{ "name": "Bar Classics" },
{ "name": "Bar pour Elle" }
]
}
paths:
/business-partner:
get:
description: >-
Get the list of registered business partner.
Only the business partners to which you have access to are returned.
Therefore, you must define all success and service specific error responses in your API
specification. Both are part of the interface definition and provide important information for
service clients to handle standard as well as exceptional situations.
Hint: In most cases it is not useful to document all technical errors, especially if they are not under
control of the service provider. Thus unless a response code conveys application-specific functional
semantics or is used in a none standard way that requires additional explanation, multiple error
response specifications can be combined using the following pattern:
responses:
...
default:
description: error occurred - see status code and problem object for more
information.
54
content:
"application/problem+json":
schema:
$ref: 'https://sailpoint-oss.github.io/sailpoint-api-guidelines/problem-
1.0.1.yaml#/Problem'
API designers should also think about a troubleshooting board as part of the associated online API
documentation. It provides information and handling guidance on application-specific errors and is
referenced via links from the API specification. This can reduce service support tasks and
contribute to service client and provider performance.
RFC standards define ~60 different HTTP status codes with specific semantics (mainly RFC7231 and
RFC 6585) — and there are upcoming new ones, e.g. draft legally-restricted-status. See overview on
all error codes on Wikipedia or via https://httpstatuses.com/) also inculding 'unofficial codes', e.g.
used by popular web servers like Nginx.
Below we list the most commonly used and best understood HTTP status codes, consistent with
their semantic in the RFCs. APIs should only use these to prevent misconceptions that arise from
55
less commonly used HTTP status codes.
Important: As long as your HTTP status code usage is well covered by the semantic defined here,
you should not describe it to avoid an overload with common sense information and the risk of
inconsistent definitions. Only if the HTTP status code is not in the list below or its usage requires
additional information aside the well defined semantic, the API specification must provide a clear
description of the HTTP status code in the response.
Success codes
201 Created - Returned on successful entity creation. You are free to POST, PUT
return either an empty response or the created resource in
conjunction with the Location header. Always set the Location
header.
202 Accepted - The request was successful and will be processed POST, PUT, PATCH,
asynchronously. DELETE
207 Multi-Status - The response body contains multiple status POST, (DELETE)
informations for different parts of a batch/bulk request (see MUST
use code 207 for batch or bulk requests).
Redirection codes
301 Moved Permanently - This and all future requests should be directed <all>
to the given URI.
303 See Other - The response to the request can be found under another POST, PUT, PATCH,
URI using a GET method. DELETE
304 Not Modified - indicates that a conditional GET or HEAD request GET, HEAD
would have resulted in 200 response if it were not for the fact that
the condition evaluated to false, i.e. resource has not been modified
since the date or version passed via request headers If-Modified-
Since or If-None-Match.
400 Bad request - generic / unknown error. Should also be delivered in <all>
case of input payload fails business logic validation.
401 Unauthorized - the users must log in (this often means <all>
"Unauthenticated").
56
Code Meaning Methods
403 Forbidden - the user is not authorized to use this resource. <all>
405 Method Not Allowed - the method is not supported, see OPTIONS. <all>
406 Not Acceptable - resource can only generate content not acceptable <all>
according to the Accept headers sent in the request.
408 Request timeout - the server times out waiting for the resource. <all>
409 Conflict - request cannot be completed due to conflict, e.g. when two POST, PUT, PATCH,
clients try to create the same resource or if there are concurrent, DELETE
conflicting updates.
410 Gone - resource does not exist any longer, e.g. when accessing a <all>
resource that has intentionally been deleted.
412 Precondition Failed - returned for conditional requests, e.g. If-Match PUT, PATCH, DELETE
if the condition failed. Used for optimistic locking.
415 Unsupported Media Type - e.g. clients sends request body without POST, PUT, PATCH,
content type. DELETE
423 Locked - Pessimistic locking, e.g. processing states. PUT, PATCH, DELETE
429 Too many requests - the client does not consider rate limiting and <all>
sent too many requests (see MUST use code 429 with headers for
rate limits).
500 Internal Server Error - a generic error indication for an unexpected <all>
server execution problem (here, client retry may be sensible)
501 Not Implemented - server cannot fulfill the request (usually implies <all>
future availability, e.g. new feature).
57
processing status or error situations. See the below table for examples of when to use the generic
400 vs a more specific 4xx
See https://github.com/sailpoint/cloud-api-client-common/blob/master/design-docs/v3/
definition.md#response-codes-and-headers for a list of response codes that SailPoint prefers to use.
If you encounter a scenario where two or more response codes are appropriate, prefer to use the
response code that preserves the security of the system and does not hand out too much
information to unauthorized users.
• A batch or bulk request always responds with HTTP status code 207 unless a non-item-specific
failure occurs.
• A batch or bulk request may return 4xx/5xx status codes, if the failure is non-item-specific and
cannot be restricted to individual items of the batch or bulk request, e.g. in case of overload
situations or general service failures.
• A batch or bulk response with status code 207 always returns as payload a multi-status
response containing item specific status and/or monitoring information for each part of the
batch or bulk request.
Note: These rules apply even in the case that processing of all individual parts fail or each part is
executed asynchronously!
The rules are intended to allow clients to act on batch and bulk responses in a consistent way by
inspecting the individual results. We explicitly reject the option to apply 200 for a completely
successful batch as proposed in Nakadi’s POST /event-types/{name}/events as short cut without
inspecting the result, as we want to avoid risks and expect clients to handle partial batch failures
anyway.
BatchOrBulkResponse:
description: batch response object.
type: object
properties:
items:
type: array
items:
type: object
properties:
id:
58
description: Identifier of batch or bulk request item.
type: string
status:
description: >
Response status value. A number or extensible enum describing
the execution status of the batch or bulk request items.
type: string
x-extensible-enum: [...]
description:
description: >
Human readable status description and containing additional
context information about failures etc.
type: string
required: [id, status]
Note: while a batch defines a collection of requests triggering independent processes, a bulk defines
a collection of independent resources created or updated together in one request. With respect to
response processing this distinction normally does not matter.
• Return a Retry-After header indicating how long the client ought to wait before making a
follow-up request. The Retry-After header can contain a HTTP date value to retry after or the
number of seconds to delay. Either is acceptable but APIs should prefer to use a delay in
seconds.
• Return a trio of X-RateLimit headers. These headers (described below) allow a server to express
a service level in the form of a number of allowing requests within a given window of time and
when the window is reset.
• X-RateLimit-Limit: The maximum number of requests that the client is allowed to make in this
window.
• X-RateLimit-Reset: The relative time in seconds when the rate limit window will be reset.
Beware that this is different to Github and Twitter’s usage of a header with the same name
which is using UTC epoch seconds instead.
The reason to allow both approaches is that APIs can have different needs. Retry-After is often
sufficient for general load handling and request throttling scenarios and notably, does not strictly
require the concept of a calling entity such as a tenant or named account. In turn this allows
resource owners to minimise the amount of state they have to carry with respect to client requests.
The 'X-RateLimit' headers are suitable for scenarios where clients are associated with pre-existing
59
account or tenancy structures. 'X-RateLimit' headers are generally returned on every request and
not just on a 429, which implies the service implementing the API is carrying sufficient state to
track the number of requests made within a given window for each named entity.
15. Performance
SHOULD support partial responses via filtering
Depending on your use case and payload size, you can significantly reduce network bandwidth
need by supporting filtering of returned entity fields. Here, the client can explicitly determine the
subset of fields he wants to receive via the fields query parameter. (It is analogue to GraphQL
fields and simple queries, and also applied, for instance, for Google Cloud API’s partial responses.)
Unfiltered
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": "cddd5e44-dae0-11e5-8c01-63ed66ab2da5",
"name": "John Doe",
"address": "1600 Pennsylvania Avenue Northwest, Washington, DC, United States",
"birthday": "1984-09-13",
"friends": [ {
"id": "1fb43648-dae1-11e5-aa01-1fbc3abb1cd0",
"name": "Jane Doe",
"address": "1600 Pennsylvania Avenue Northwest, Washington, DC, United States",
"birthday": "1988-04-07"
} ]
}
Filtered
60
GET http://api.example.org/users/123?fields=(name,friends(name)) HTTP/1.1
HTTP/1.1 200 OK
Content-Type: application/json
{
"name": "John Doe",
"friends": [ {
"name": "Jane Doe"
} ]
}
The fields query parameter determines the fields returned with the response payload object. For
instance, (name) returns users root object with only the name field, and (name,friends(name)) returns
the name and the nested friends object with only its name field.
OpenAPI doesn’t support you in formally specifying different return object schemes depending on a
parameter. When you define the field parameter, we recommend to provide the following
description: Endpoint supports filtering of return object fields as described in [Rule
#157](https://opensource.zalando.com/restful-api-guidelines/#157)
The syntax of the query fields value is defined by the following BNF grammar.
Note: Following the principle of least astonishment, you should not define the fields query
parameter using a default value, as the result is counter-intuitive and very likely not anticipated by
the consumer.
See MUST stick to conventional query parameters for naming, e.g. "embed" for steering of
61
embedded resource expansion. Please use the BNF grammar, as already defined above for filtering,
when it comes to an embedding query syntax.
Embedding a sub-resource can possibly look like this where an order resource has its order items
as sub-resource (/order/{orderId}/items):
{
"id": "123",
"_embedded": {
"items": [
{
"position": 1,
"sku": "1234-ABCD-7890",
"price": {
"amount": 71.99,
"currency": "EUR"
}
}
]
}
}
16. Pagination
MUST support pagination
Access to lists of data items must support pagination to protect the service against overload as well
as for best client side iteration and batch processing experience. This holds true for all lists that are
(potentially) larger than just a few hundred entries.
• Cursor/Limit-based — aka key-based — pagination: a unique key element identifies the first
page entry (see also Facebook’s guide)
The technical conception of pagination should also consider user experience related issues. As
mentioned in this article, jumping to a specific page is far less used than navigation via next/prev
page links.
Note: To provide a consistent look and feel of pagination patterns, you must stick to the common
query parameter names defined in MUST stick to conventional query parameters.
62
MAY use pagination links where applicable
To simplify client design, APIs should support simplified hypertext controls for paginating over
collections whenever applicable as follows
{
"self": "https://myorg.api.identitynow.com/v3/resources?cursor=<self-position>",
"first": "https://myorg.api.identitynow.com/v3/resources?cursor=<first-position>",
"prev": "https://myorg.api.identitynow.com/v3/resources?cursor=<previous-position>",
"next": "https://myorg.api.identitynow.com/v3/resources?cursor=<next-position>",
"last": "https://myorg.api.identitynow.com/v3/resources?cursor=<last-position>",
"query": {
"query-param-<1>": ...,
"query-param-<n>": ...
},
"items": [...]
}
Remarks:
• It is essential to include the query parameters from the original requests in the hypertext
controls provided for pagination. These parameters can either be encoded within the cursor or
provided separately.
• You should avoid providing a total count unless there is a clear need to do so. Very often, there
are significant system and performance implications when supporting full counts. Especially, if
the data set grows and requests become complex queries and filters drive full scans. While this
is an implementation detail relative to the API, it is important to consider the ability to support
serving counts over the life of a service.
• The hypertext controls such as self, first, prev, next and last are all optional. You should include
the relevant controls based on the specific requirements.
17. Hypermedia
MUST use REST maturity level 2
We strive for a good implementation of REST Maturity Level 2 as it enables us to build resource-
oriented APIs that make full use of HTTP verbs and status codes. You can see this expressed by
many rules throughout these guidelines, e.g.:
Although this is not HATEOAS, it should not prevent you from designing proper link relationships
63
in your APIs as stated in rules below.
Motivation: Exposing any form of relative URI (no matter if the relative URI uses an absolute or
relative path) introduces avoidable client side complexity. It also requires clarity on the base URI,
which might not be given when using features like embedding subresources. The primary
advantage of non-absolute URI is reduction of the payload size, which is better achievable by
following the recommendation to use gzip compression.
• href: The URI of the resource the hypertext control is linking to. All our API are using HTTP(s) as
URI scheme.
In API that contain any hypertext controls, the attribute name href is reserved for usage within
hypertext controls.
The schema for hypertext controls can be derived from this model:
HttpLink:
description: A base type of objects representing links to resources.
type: object
properties:
href:
description: Any URI that is using http or https protocol
type: string
format: uri
required:
- href
The name of an attribute holding such a HttpLink object specifies the relation between the object
that contains the link and the linked resource. Implementations should use names from the IANA
Link Relation Registry whenever appropriate. As IANA link relation names use hyphen-case
notation, while this guide enforces snake_case notation for attribute names, hyphens in IANA
names have to be replaced with underscores (e.g. the IANA link relation type version-history would
become the attribute version_history)
Specific link objects may extend the basic link type with additional attributes, to give additional
information related to the linked resource or the relationship between the source resource and the
linked one.
E.g. a service providing "Person" resources could model a person who is married with some other
64
person with a hypertext control that contains attributes which describe the other person (id, name)
but also the relationship "spouse" between the two persons (since):
{
"id": "446f9876-e89b-12d3-a456-426655440000",
"name": "Peter Mustermann",
"spouse": {
"href": "https://...",
"since": "1996-12-19",
"id": "123e4567-e89b-12d3-a456-426655440000",
"name": "Linda Mustermann"
}
}
Hypertext controls are allowed anywhere within a JSON model. While this specification would
allow HAL, we actually don’t recommend/enforce the usage of HAL anymore as the structural
separation of meta-data and data creates more harm than value to the understandability and
usability of an API.
If-Modified-Since
Accept-Encoding
Content-ID
Language
65
Note, HTTP standard defines headers as case-insensitive (RFC 7230, p.22). However, for sake of
readability and consistency you should follow the convention when using standard or proprietary
headers. Exceptions are common abbreviations like ID.
• Content-Disposition can indicate that the representation is supposed to be saved as a file, and
the proposed file name.
• Content-Language indicates that the body is meant for people literate in some human
language(s).
• Content-Location indicates where the body can be found otherwise (MAY use Content-Location
header for more details]).
• Content-Range is used in responses to range requests to indicate which part of the requested
resource representation is delivered with the body.
The Content-Location header can be used to support the following use cases:
• For reading operations GET and HEAD, a different location than the requested URI can be used to
indicate that the returned resource is subject to content negotiations, and that the value
provides a more specific identifier of the resource.
66
• For writing operations PUT and PATCH, an identical location to the requested URI can be used to
explicitly indicate that the returned resource is the current representation of the newly created
or updated resource.
• For writing operations POST and DELETE, a content location can be used to indicate that the body
contains a status report resource in response to the requested action, which is available at
provided location.
Note: When using the Content-Location header, the Content-Type header has to be set as well. For
example:
HTTP/1.1 200 OK
Content-Type: image/png
Content-Location: /products/123/images?format=raw
components:
headers:
- Prefer:
description: >
The RFC7240 Prefer header indicates that a particular server behavior
is preferred by the client but is not required for successful completion
of the request (see [RFC 7240](https://tools.ietf.org/html/rfc7240).
The following behaviors are supported by this API:
67
continue, if possible.
type: array
items:
type: string
required: false
Note: Please copy only the behaviors into your Prefer header specification that are supported by
your API endpoint. If necessary, specify different Prefer headers for each supported use case.
Supporting APIs may return the Preference-Applied header also defined in RFC 7240 to indicate
whether a preference has been applied.
To expose conflicts between concurrent update operations via PUT, POST, or PATCH, the If-Match:
<entity-tag> header can be used to force the server to check whether the version of the updated
entity is conforming to the requested <entity-tag>. If no matching entity is found, the operation is
supposed a to respond with status code 412 - precondition failed.
Beside other use cases, If-None-Match: * can be used in a similar way to expose conflicts in resource
creation. If any matching entity is found, the operation is supposed a to respond with status code
412 - precondition failed.
The ETag, If-Match, and If-None-Match headers can be defined as follows in the API definition:
components:
headers:
- ETag:
description: |
The RFC 7232 ETag header field in a response provides the entity-tag of
a selected resource. The entity-tag is an opaque identifier for versions
and representations of the same resource over time, regardless whether
multiple versions are valid at the same time. An entity-tag consists of
an opaque quoted string, possibly prefixed by a weakness indicator (see
[RFC 7232 Section 2.3](https://tools.ietf.org/html/rfc7232#section-2.3).
type: string
required: false
example: W/"xy", "5", "5db68c06-1a68-11e9-8341-68f728c1ba70"
68
- If-Match:
description: |
The RFC7232 If-Match header field in a request requires the server to
only operate on the resource that matches at least one of the provided
entity-tags. This allows clients express a precondition that prevent
the method from being applied if there have been any changes to the
resource (see [RFC 7232 Section
3.1](https://tools.ietf.org/html/rfc7232#section-3.1).
type: string
required: false
example: "5", "7da7a728-f910-11e6-942a-68f728c1ba70"
- If-None-Match:
description: |
The RFC7232 If-None-Match header field in a request requires the server
to only operate on the resource if it does not match any of the provided
entity-tags. If the provided entity-tag is `*`, it is required that the
resource does not exist at all (see [RFC 7232 Section
3.2](https://tools.ietf.org/html/rfc7232#section-3.2).
type: string
required: false
example: "7da7a728-f910-11e6-942a-68f728c1ba70", *
Please see Optimistic locking in RESTful APIs for a detailed discussion and options.
The unique request key is stored temporarily, e.g. for 24 hours, together with the response and the
request hash (optionally) of the first request in a key cache, regardless of whether it succeeded or
failed. The service can now look up the unique request key in the key cache and serve the response
from the key cache, instead of re-executing the request, to ensure idempotent behavior. Optionally,
it can check the request hash for consistency before serving the response. If the key is not in the key
store, the request is executed as usual and the response is stored in the key cache.
This allows clients to safely retry requests after timeouts, network outages, etc. while receive the
same response multiple times. Note: The request retry in this context requires to send the exact
same request, i.e. updates of the request that would change the result are off-limits. The request
hash in the key cache can protection against this misbehavior. The service is recommended to
reject such a request using status code 400.
Important: To grant a reliable idempotent execution semantic, the resource and the key cache
69
have to be updated with hard transaction semantics – considering all potential pitfalls of failures,
timeouts, and concurrent requests in a distributed systems. This makes a correct implementation
exceeding the local context very hard.
The Idempotency-Key header must be defined as follows, but you are free to choose your expiration
time:
components:
headers:
- Idempotency-Key:
description: |
The idempotency key is a free identifier created by the client to
identify a request. It is used by the service to identify subsequent
retries of the same request and ensure idempotent behavior by sending
the same response without executing the request a second time.
Clients should be careful as any subsequent requests with the same key
may return the same response without further check. Therefore, it is
recommended to use an UUID version 4 (random) or any other random
string with enough entropy to avoid collisions.
type: string
format: uuid
required: false
example: "7da7a728-f910-11e6-942a-68f728c1ba70"
Hint: The key cache is not intended as request log, and therefore should have a limited lifetime,
else it could easily exceed the data resource in size.
Note: The Idempotency-Key header unlike other headers in this section is not standardized in an
RFC. Our only reference are the usage in the Stripe API. However, as it fit not into our section about
proprietary-headers, and we did not want to change the header name and semantic, we decided to
treat it as any other common header.
IDN APIs are published from an internal GitHub repository. All other APIs will need to be submitted
to the Developer Relations team for publishing.
70
MUST monitor API usage
Owners of APIs used in production should monitor API service to get information about its using
clients. This information, for instance, is useful to identify potential review partner for API changes.
Hint: A preferred way of client detection implementation is by logging of the client-id retrieved
from the OAuth token.
Appendix A: References
This section collects links to documents to which we refer, and base our guidelines on.
OpenAPI specification
• OpenAPI specification
• RFC 4627: The application/json Media Type for JavaScript Object Notation (JSON)
• RFC 7159: The JavaScript Object Notation (JSON) Data Interchange Format
• RFC 7230: Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing
71
• BCP 47: Tags for Identifying Languages
Dissertations
• Roy Thomas Fielding - Architectural Styles and the Design of Network-Based Software
Architectures: This is the text which defines what REST is.
Books
• REST in Practice: Hypermedia and Systems Architecture
Blogs
• Lessons-learned blog: Thoughts on RESTful API Design
Appendix B: Tooling
This is not a part of the actual guidelines, but might be helpful for following them. Using a tool
mentioned here doesn’t automatically ensure you follow the guidelines.
The Swagger/OpenAPI homepage lists more Community-Driven Language Integrations, but most of
them do not fit our API First approach.
72
The cursor itself is an opaque string, transmitted forth and back between service and clients, that
must never be inspected or constructed by clients. Therefore, it is good practice to encode
(encrypt) its content in a non-human-readable form.
The cursor content usually consists of a pointer to the anchor element defining the page position in
the collection, a flag whether the element is included or excluded into/from the page, the retrieval
direction, and a hash over the applied query filters (or the query filter itself) to safely re-create the
collection. It is important to note, that a cursor should be always defined in relation to the current
page to anticipate all occurring changes when progressing.
Cursor:
descriptions: >
Cursor structure that contains all necessary information to efficiently
retrieve a page from the data store.
type: object
properties:
position:
description: >
Object containing the keys pointing to the anchor element that is
defining the collection resource page. Normally the position is given
by the first or the last page element. The position object contains all
values required to access the element efficiently via the ordered,
combined index, e.g `modified_at`, `id`.
type: object
properties: ...
element:
description: >
Flag whether the anchor element, which is pointed to by the `position`,
should be *included* or *excluded* from the result set. Normally, only
the current page includes the pointed to element, while all others are
exclude it.
type: string
enum: [ INCLUDED, EXCLUDED ]
direction:
description: >
Flag for the retrieval direction that is defining which elements to
choose from the collection resource starting from the anchor elements
position. It is either *ascending* or *descending* based on the
ordering combined index.
type: string
enum: [ ASCENDING, DESCENDING ]
query_hash:
description: >
Stable hash calculated over all query filters applied to create the
collection resource that is represented by this cursor.
73
type: string
query:
description: >
Object containing all query filters applied to create the collection
resource that is represented by this cursor.
type: object
properties: ...
required:
- position
- element
- direction
Note: In case of complex and long search requests, e.g. when GET with body is already required, the
cursor may not be able to include the query because of common HTTP parameter size restrictions.
In this case the query filters should be transported via body - in the request as well as in the
response, while the pagination consistency should be ensured via the query_hash.
Remark: It is also important to check the efficiency of the data-access. You need to make sure that
you have a fully ordered stable index, that allows to efficiently resolve all elements of a page. If
necessary, you need to provide a combined index that includes the id to ensure the full order and
additional filter criteria to ensure efficiency.
Further reading
• Paging in PostgreSQL
Optimistic locking might be used to avoid concurrent writes on the same entity, which might cause
data loss. A client always has to retrieve a copy of an entity first and specifically update this one. If
another version has been created in the meantime, the update should fail. In order to make this
work, the client has to provide some kind of version reference, which is checked by the service,
before the update is executed. Please read the more detailed description on how to update
resources via PUT in the HTTP Requests Section.
A RESTful API usually includes some kind of search endpoint, which will then return a list of result
entities. There are several ways to implement optimistic locking in combination with search
endpoints which, depending on the approach chosen, might lead to performing additional requests
to get the current version of the entity that should be updated.
74
ETag with If-Match header
An ETag can only be obtained by performing a GET request on the single entity resource before the
update, i.e. when using a search endpoint an additional request is necessary.
Example:
Or, if there was an update since the GET and the entity’s ETag has changed:
Pros
• RESTful solution
Cons
The ETag for every entity is returned as an additional property of that entity. In a response
containing multiple entities, every entity will then have a distinct ETag that can be used in
subsequent PUT requests.
In this solution, the etag property should be readonly and never be expected in the PUT request
75
payload.
Example:
Or, if there was an update since the GET and the entity’s ETag has changed:
Pros
Cons
• Information that only belongs in the HTTP header is part of the business objects
Version numbers
The entities contain a property with a version number. When an update is performed, this version
number is given back to the service as part of the payload. The service performs a check on that
version number to make sure it was not incremented since the consumer got the resource and
performs the update, incrementing the version number.
Since this operation implies a modification of the resource by the service, a POST operation on the
exact resource (e.g. POST /orders/O0000042) should be used instead of a PUT.
In this solution, the version property is not readonly since it is provided at POST time as part of the
payload.
Example:
76
< GET /orders
or if there was an update since the GET and the version number in the database is higher than the
one given in the request body:
Pros
Cons
• Functionality that belongs into the HTTP header becomes part of the business object
• Using POST instead of PUT for an update logic (not a problem in itself, but may feel unusual for
the consumer)
Last-Modified / If-Unmodified-Since
In HTTP 1.0 there was no ETag and the mechanism used for optimistic locking was based on a date.
This is still part of the HTTP protocol and can be used. Every response contains a Last-Modified
header with a HTTP date. When requesting an update using a PUT request, the client has to provide
this value via the header If-Unmodified-Since. The server rejects the request, if the last modified
date of the entity is after the given date in the header.
This effectively catches any situations where a change that happened between GET and PUT would be
overwritten. In the case of multiple result entities, the Last-Modified header will be set to the latest
date of all the entities. This ensures that any change to any of the entities that happens between GET
and PUT will be detectable, without locking the rest of the batch as well.
Example:
77
> HTTP/1.1 200 OK
> Last-Modified: Wed, 22 Jul 2009 19:15:56 GMT
> {
> "items": [
> { "id": "O0000042", ... },
> { "id": "O0000043", ... }
> ]
> }
Or, if there was an update since the GET and the entities last modified is later than the given date:
Pros
• Well established approach that has been working for a long time
• No interference with the business objects; the locking is done via HTTP headers only
Cons
• If a client communicates with two different instances and their clocks are not perfectly in sync,
the locking could potentially fail
Conclusion
We suggest to either use the ETag in result entities or Last-Modified / If-Unmodified-Since approach.
Appendix D: Changelog
This change log only contains major changes made after October 2021.
Non-major changes are editorial-only changes or minor changes of existing guidelines, e.g. adding
new error code. Major changes are changes that come with additional obligations, or even change
an existing guideline obligation. The latter changes are additionally labeled with "Rule Change"
here.
To see a list of all changes, please have a look at the commit list in Github.
(Note that recent changes might be missing, as we update this list only occasionally, not with each
78
pull request, to avoid merge commits.)
Rule Changes
<!-- Adds rule id as a postfix to all rule titles -->
<script>
var ruleIdRegEx = /(\d)+/;
var h3headers = document.getElementsByTagName("h3");
for (var i = 0; i < h3headers.length; i++) {
var header = h3headers[i];
if (header.id && header.id.match(ruleIdRegEx)) {
var a = header.getElementsByTagName("a")[0]
a.innerHTML += " [" + header.id + "]";
}
}
</script>
<!-- Add table of contents anchor and remove document title -->
<script>
var toc = document.getElementById('toc');
var div = document.createElement('div');
div.id = 'table-of-contents';
toc.parentNode.replaceChild(div, toc);
div.appendChild(toc);
var ul = toc.childNodes[3];
ul.removeChild(ul.childNodes[1]);
</script>
title.append(link);
nav.append(title);
var ul = document.createElement('ul');
ul.classList.add('sectlevel1');
79
li = document.createElement('li');
li.append(link);
ul.append(li);
document.body.classList.add('toc2');
document.body.classList.add('toc-left');
nav.append(ul);
header.prepend(nav);
</script>
[1] Per definition of R.Fielding REST APIs have to support HATEOAS (maturity level 3). Our guidelines do not strongly advocate for
full REST compliance, but limited hypermedia usage, e.g. for pagination (see Hypermedia). However, we still use the term "RESTful
API", due to the absence of an alternative established term and to keep it like the very majority of web service industry that also
use the term for their REST approximations — in fact, in today’s industry full HATEOAS compliant APIs are a very rare exception.
80