As described at https://graphql.org/
, GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools.
The rules of the language are governed by the RFC Specification. It is the goal of this document to outline the set of rules, standards, and conventions that apply to the design of GraphQL APIs at PayPal.
It is important to remember that your API is created for and consumed by human beings, so your first priority should always be on enabling other developers to do their jobs effectively.
The following terms and concepts from domain-driven design (DDD) are used throughout this document.
An object primarily identified by its identity and has a thread or continuity. All entities have a lifecycle, from creation through archival or deletion. A service describes an activity or action, stands on its own in the domain, and is solely identified by the action/activity it implements. Services usually operate on many entities to complete an activity and don't have states of their own. Services implement procedural or controller style actions. **Note**: The term "Service" here MUST not be confused with a Micro-service. The API lifecycle operations that operate on an entity, from creation through deletion or archival. Example of entity lifecycle operations are: Create, Read, Update, Delete, and other entity state change operatiions. The API operation that expose a service (action or activity) to its clients.All GraphQL API requests are routed through the URL `https://apihost/graphql. The requests are then federated to each domain's GraphQL server based on where it is hosted.
- All field names SHOULD use the following conventions; if the field name consists of only one word, express/spell the word in all lower case letters (e.g. amount). If it contains more than one word, capitalize the first letter of each subsequent word (e.g.
checkingAccountNumber
). - Entries (values) of an enumeration SHOULD be composed of only upper-case alphanumeric characters and the underscore character (
_
). - Array names SHOULD be in plurals.
- Acronyms SHOULD be avoided (e.g. use
fooSpecificBalance
instead ofFSB
). The exception is the popular industry acronym for a concept (example: cvv, csc etc.), if it exists, SHOULD be used. - Collection sounding suffixes in field names or query parameters SHOULD be avoided, e.g. use
errors
in lieu oferrorList
. - Names SHOULD NOT use the words, such as
encrypted
,decrypted
,encoded
,info
,additional
,auxiliary
,supplementary
, or any name that describes the underlying implementaton (e.g. Names likehashedId
aliasHmac
etc. SHOULD be avoided). - All names (fields, query and path) SHOULD be in American english, e.g. favor vs favour, authorized vs authorised.
- Generally accepted conventions for names SHOULD be preferred over other names for fields or query parameters, e.g.
id
SHOULD be used to describe an identifier everywhere; long names like identifier SHOULD be avoided; in case of a conflict, an appropriate prefix or suffix SHOULD be used, such asmerchantId
,partnerId
etc. - Field or query parameter names that represent a RFC3339 full-date SHOULD end with the suffix
Date
, e.g.createDate
,updateDate
,deleteDate
,dueDate
. - Field or query parameter names that represent a RFC3339 date-time or full-time SHOULD end with the suffix
Time
, e.g.createTime
,updateTime
,deleteTime
,dueTime
. - All GraphQL type names SHOULD be in CamelCase, with the first letter of each word in uppercase (e.g. LanguageCode).
- All GraphQL queries on entity collections MUST use plural nouns as names. Examples are,
orders
,invoices
, anddisputes
- GraphQL queries that explicitly lookup an entity by its primary key SHOULD use a singular noun as names. Examples are,
order
,invoice
,dispute
. - For queries that define generic searches, the prefix
search
MUST be used. Example:searchContent
For all Entity Lifecycle Mutations, the following conventions MUST be used:
- For create operation on an enity, the prefix
Create
MUST be used. - For update operation on an enity, the prefix
Update
MUST be used. - For all deletion operation on an enity, the prefix
Delete
MUST be used. - The entity name MUST follow the prefix
Create
,Update
, orDELETE
. For example,createOrder
,updateOrder
,DeleteOrder
.
For all Service mutations (mutations that implement an action
or activity
instead of a thing
), a verb MUST be used to describe the activity or action. For example, notifyUser
, evaluateCreditEligibility
.
Lastly, when naming mutations, wherever possible, names that describe the intent of the operations SHOULD be preferred over generic CRUD style names. For example, a name setUserProfile
is preferred over updateUser
, for making the user profile changes.
- All input objects MUST use
Input
as the suffix.
- All entity lifecycle mutations MUST have the response object named after the Entity name. For example, CreateOrder/UpdateOrder/DeleteOrder mutation MUST have the response object named as
Order
. - All service mutations MUST have the response object named after the service result, in the form of a noun. For example, the service API operation,
createCreditEligibility
SHOULD have the response object named asCreditEligibilityEvaluation
. - Queries that operate on an Entity Collections, the response object MUST have the suffix
Connection
. For example, a GraphQL queryorders
would nameOrderConnection
as the response object (Note: Queries on an entity collection MUST implement pagination. Please check GraphQL API patterns for details on pagination).
The authPolicy
directive SHOULD be used to describe all authorization requirements for queries, mutatons and their request/respons objects (as applicable).
Example:
type Query {
orders(...): [Order]
@authPolicy(
scopes: ["FOO_SCOPE"]
)
}
The dataClassification
directive SHOULD be used to describe the data classification of request and response elements.
Example:
type User {
email: String! @dataClassification(dataClass: "Foo",dataCategory:"Bar")
}
The directive visibility
MUST be used to describe the visibility of a query, mutation, or a request/response element. The values can be one of the following: EXTERNAL, PARTNER, INTERNAL
For type field elements:
type User {
email: String! @visibility(extent: "PARTNER")
}
For Operations (Queries/Mutations):
type Query {
assets(): [Asset] @visibility(extent: "PARTNER")
}
The directive labels
is used for the purpose of grouping several related queries or mutations. A label name is usually the bounded context (aka namespace) names in the domain. The value of the directive is also used for the purpose of discovery
For Operations (Queries/Mutations):
type mutation {
createUser(input: CreateUserInput): User @labels(value: ["identity"])
addBank(input: addBankInput): Bank @labels(value: ["wallet"])
}
The directive slo
SHOULD be used to describe the service level objectives of a GraphQL operation. Given below is the example usage;
For Operations (Queries/Mutations):
type mutation {
createUser(input: CreateUserInput): User
@slo(responseTime95thPercentile: 100, errorRate: 0.001)
}
The directive stringLength
is used to describe lenth validation requirements of a string. Given below is the example usage;
type User {
firstName: String! @stringLength(min: 1, max: 255)
}
The directive pattern
is used to describe format validation requirements of a string. Given below is the example usage;
type User {
userId: String! @pattern(regexp: "^[0-9a-zA-Z]*$")
}
The directive listLength
is used to describe the length validation requirements of an array. Given below is the example usage;
type User {
aliases: [String!] @listLength(min: 1, max: 5)
}
The directive range
is used to describe the range validation requirements of a number. Given below is the example usage;
type User {
total_items: Int! @range(min: 1, max: 32767)
}
The directive requestHeader
is used to describe the request headers of a GraphQL Query/Mutation. The directive SHOULD be repeated for each request header.
For Operations (Queries/Mutations):
type mutation {
createCardArt(input: CardArtInput): CardArt
@requestHeader(name: "Idempotency-Key",
description: "The request idempotency key.", required: true)
}
The directive responseHeader
is used to describe the response headers of a GraphQL Query/Mutation. The directive SHOULD be repeated for each response header.
For Operations (Queries/Mutations):
type mutation {
createCardArt(input: CardArtInput): CardArt
@responseHeader(name: "Foo-Id",
description: "The response correlationId.", required: true)
}
All the GraphQL errors SHOULD be propagated via the extensions
element of the errors array. Given below is the schema:
extensions
: Must have a map as its value. This entry is reserved for implementors to add additional information to errors, and there are no additional restrictions on its contents.name
: A human-readable name for the error. Thename
describes the class of an error. The name is aCAP_SNAKE_CASE
fixed string described in the section Error Class.details
: An array that contains detailed information as to how the error occurred.issue
: An application level error code.issue
SHOULD be aCAP_SNAKE_CASE
string.description
: A human-readable explanation, describing anissue
. Thedescription
MAY change over the lifetime of an API, so clients SHOULD NOT depend on this value remaining constant.
Example:
Status: 200 OK
{
"errors": [
{
"message": "The requested action could not be performed, semantically incorrect, or failed business validation.",
"path": [
"sender"
],
"locations": [
{
"line": 2,
"column": 3
}
],
"extensions": {
"name": "UNPROCESSABLE_ENTITY",
"details": [
{
"issue": "SENDER_HAS_NEGATIVE_BALANCE",
"description": "Cannot process the payment as the sender has negative balance."
}
]
}
}
],
"extensions" : {
"FooId":"123456789"
}
}
The following listed error classes SHOULD be used in all GraphQL error responses.
Error Class | Description |
---|---|
INVALID_REQUEST |
Request is not well-formed, syntactically incorrect, or violates schema. |
AUTHENTICATION_FAILURE |
Authentication failed due to missing Authorization header, or invalid authentication credentials. |
NOT_AUTHORIZED |
Authorization failed due to insufficient permissions. |
UNPROCESSABLE_ENTITY |
The requested action could not be performed, semantically incorrect, or failed business validation. |
INTERNAL_ERROR |
An internal server error has occurred. |
In validating requests, APIs MAY further describe fine grained validation errors using a sub category of error codes using the issue
field of details
array in the error. Given below are some of the suggested fine grained error codes.
Issue value | Description |
---|---|
MISSING_REQUIRED_PARAMETER |
A required field or parameter is missing. |
INVALID_STRING_MIN_LENGTH |
The value of a field is too short. |
INVALID_STRING_MAX_LENGTH |
The value of a field is too long. |
INVALID_STRING_LENGTH |
The value of a field is either too short or too long. |
INVALID_PARAMETER_SYNTAX |
The value of a field does not conform to the expected format. |
INVALID_INTEGER_MIN_VALUE |
The integer value of a field is too small. |
INVALID_INTEGER_MAX_VALUE |
The integer value of a field is too large. |
INVALID_PARAMETER_VALUE |
The value of a field is invalid. |
INVALID_ARRAY_MIN_ITEMS |
The number of items in an array parameter is too small. |
INVALID_ARRAY_MAX_ITEMS |
The number of items in an array parameter is too large. |
INVALID_ARRAY_LENGTH |
The number of items in an array parameter is too small or too large. |
INVALID_PATCH_PATH |
The value of path in a HTTP PATCH request item is invalid. |
CANNOT_REUSE_IDEMPOTENCY_KEY |
The idempotency key cannot be reused for a different request payload. |
READ_ONLY |
The field in the request body is read only so it can't be modified. |
RESOURCE_CONFLICT |
The server has detected a conflict while processing this request. |