We are creating an RPC-style HTTP API, similar to the Slack API. Our API endpoints follow a resource.action structure.
These are common actions for resources:
- resource.list - Get a list objects
- resource.info - Information about a single object
- resource.create - Create a new object (try to look for better domain language)
- resource.add - Adding existing objects to a collection (try to look for better domain language)
- resource.update - Update an existing object (try to look for better domain language)
- resource.delete - Delete an existing object (try to look for better domain language)
Usually there will be other actions available for your resource. We need to make sure we make these as explicit and clear as possible. Eg. invoice.draft is better than invoice.create.
For casing, we agreed the following:
- The resource and action of the API endpoint are camelCased
- The API endpoint parameters are snake_cased
- All json properties in a request/response are snake_cased
/myResource.myAction?my_parameter=value
We avoid abbreviations, for clarity. There are exceptions however:
- The
idfield - Abbreviations common in the domain such as
VAT
Ids are always returned as uuids. Currently these can not be defined by the client, and are generated on our side.
Paging is 1-based. This means that page.number: 1 returns the very first page.
All API endpoints support POST requests with a json body. For simplicity we also allow GET requests on endpoints which are used to retrieve data such as info or list endpoints.
POST deals.list
{
"page": {
"size": 10,
"number": 1
},
"filter": {
"company_id": 123
}
}We try to interpret HTTP Status codes the right way:
200- OK201- Created, when resources are created204- No Content, on resource updates or actions400- Bad Request, the request contains invalid data or references non-existing resources401- Unauthorized, invalid or missing access token403- Forbidden, not allowed to access this resource or update a property you are not allowed to change404- Not Found, resource not found429- Too Many Requests, client has reached the API rate limit500- Internal Server Error, something went wrong on our end
application/jsonfor request accept headersapplication/jsonfor response content-type headers
Endpoints used to read single or multiple objects MUST:
- Respond with the
200HTTP status code - Return a top level
dataproperty containing a single object or a list of objects
And MAY:
- Allow filters using the
filterrequest parameter - Allow an
idsfilter to fetch multiple objects by id - Allow pagination using the
pagerequest parameter - Return pagination information in the
metaresponse attribute
Endpoints used to create new objects MUST:
- Return the
idandtypeof the new object in a top-leveldataattribute - Respond with the
201HTTP status code
Endpoints used to update existing objects MUST:
- Not have unexpected side effects
- Respond with an empty response and the
204HTTP status code
When an error occurs, the endpoint MUST:
- Respond a status code that corresponds to the type of error
- Return a top-level
errorsattribute containing one or more errors
Each error object MUST:
- Have a
titleattribute; a short human readable summary of the problem
The error objects MAY have the following members:
code: an application specific error code to identify where the error is coming frommeta: additional non-standard meta information about this error
Example of a 400 Bad Request error:
{
"errors": [
{
"code": 123,
"title": "Company name must not be empty",
"status": 400,
"meta": {
"field": "name"
}
}
]
}Adding endpoints and properties are typically always non-breaking and backwards compatible. Modifying, renaming and removing endpoints and properties is allowed during the beta period. Once our API is released, we will tag our API with a new version for each breaking change.
Object properties without a value MUST:
- Return null for scalar values
- Return an empty array if the property returns a list of objects/values
Example:
{
"data": {
"id": "ee72ae60-d4df-4f27-beec-d105d5b3e170",
"name": "John Doe",
"email": null,
"tags": []
}
}When a User doesn't have permissions to access a specific property the endpoint MUST:
- Not return that property in the response.
- When writing to a resource that contains that property, the endpoint MUST return a
403error response with an appropriate message. - The property MUST be documented in API docs as optional for both cases.
Dates, times and datetimes returned must follow the ISO8601 standard. In PHP this can be generated using format(DATE_ATOM).
Date and datetime properties MUST:
- The property name ends on
_onfor dates. - The property name ends on
_atif they include time (time and datetime). - Always be returned in
UTC(except for some rare domain cases like Calendar events). It is the responsibility of the client to offset the timezone for users.
Examples:
{
"contacted_on": "2017-10-13",
"updated_at": "2017-10-15T10:01:49+00:00",
"available_at": "11:00:00",
}Writing datetime properties MUST accept every timezone.
Monetary values in requests and responses MUST be represented as the following data structure:
{
"amount": 123.03,
"currency": "EUR"
}Rich text fields MUST be returned (and accepted) in markdown.
If a relation can exist with different types of objects, the request MUST:
- Contain an object structure with both the
idandtypeof the related object
The support types of related objects will be documented.
Example of a customer that can be both a contact or company:
{
"customer": {
"type": "contact",
"id": "ee72ae60-d4df-4f27-beec-d105d5b3e170"
}
}If the relation can only exist with a single type of object, we allow a simplified request:
{
"project_id": "ee72ae60-d4df-4f27-beec-d105d5b3e170"
}Object relationships in responses MUST:
- Return the
idandtypeof the related object
Example:
{
"customer" : {
"type": "contact",
"id": "ee72ae60-d4df-4f27-beec-d105d5b3e170"
}
}This allows us to identify side loaded objects and add meta data in the future.