ValetTech/ValetApi

Jamie Creighton

Backend Engineer
Software Engineer
C#
Microsoft SQL Server
.NET

Valet REST API

Introduction

This API is for the VALET Reservation Management System.

Table of Contents

[Valet API](#Valet REST API)

Requests

URLs

Prefix API endpoints

Prefix API endpoints with /api/ to separate them from other URLs like HTML views served on the same server.

Click to see examples

Make sure to prefix your API endpoints with /api/:

www.example.com/api/v1/products/1

API versioning

Versioning your API allows you to make non-backwards compatible changes to your API for newer clients by introducing new versions of endpoints while not breaking existing clients.

Include the API version in the URL. Versions start with 1 and are prefixed with v. The version path component should come right after the

api

path component.

In case of an existing API that doesn't have this versioning scheme but needs a new version, skip v1 and go straight to v2.

Click to see examples

Include the API's version in the URL:

www.example.com/api/v2/auth/login

⛔️

Don't depend on a header like "Accept" for versioning:

Accept = "application/vnd.example.v1+json"

REST resources

After the API prefix and the version comes the part of the URL path that identifies the resource -- the piece of data you are interested in. Refer to a type of resource with a plural noun (eg. "users"). Directly following such a noun can be an identifier that points to a single instance. A resource can also be nested, usually if there some sort of parent/child relationship. This can be expressed by appending another plural noun to the URL.

Click to see examples

Refer to a resource with a plural noun:

/api/v1/shops

Use an identifier following a noun to refer to a single entity:

/api/v1/products/42

Refer to a nested resource like so:

/api/v1/posts/1/comments

In some cases it can be ok to simplify and have the child object at the beginning as long as the child object's id is globally unique:

/api/v1/comments/87

Be careful with this since this approach lack the extra safety of asserting that the resource you are referring to belongs to the parent resource you think it does.

Query parameters

Query parameters are like meta data to the (usually GET) URL request. They can be used when you need more control over what data should be returned. Good use cases include filters and sorting. Some things are better suited for headers, such as providing authentication and indicating the preferred encoding type.

Click to see examples

Use query parameters for a paginated endpoint to define which page and with how many results per page you want to retrieve:

/api/v1/posts?page=2&perPage=10

⛔️

Do not use query parameters for authentication:

/api/v1/posts?apiKey=a7dhas8u

HTTP Methods

HTTP methods are used to indicate what action to perform with the resource.

GET

A GET call is used to retrieve data and should not result in changes to the accessed resource. Multiple identical requests should have the same effect as a single request (idempotency).

POST

POST is used to create new resources.

PATCH

PATCH requests modify existing resources. Only fields that need to be updated need to be included - all others will be left as they are. In order to "unset" optional properties use null for the value.

PUT

With PUT calls we can replace entire objects. Only the database identifier should not be changed.

DELETE

To delete a resource, use the DELETE method.

HEAD

A HEAD call must never return a body. It can be used to see if an object exists and to see if a cached value is still up to date.

Request Headers

Protected endpoints

Use the Authorization header to consume protected endpoints. See the

Auth

section for more information on how to handle authorization and authentication.

Click to see examples

Use Authorization to authorize:

Authorization = "Basic QWxhZGRpbjpPcGVuU2VzYW1l"

⛔️

Avoid using custom headers for authorization:

UserToken = "QWxhZGRpbjpPcGVuU2VzYW1l"

Supporting localization

In order to support localization now and in the future, the Accept-Language should be used to indicate the client's language towards the API.

Click to see examples

Use

ISO 639-1

codes to indicate the preferred language of the response.

Accept-Language = "da"

Use a prioritized list of languages to influence the fallback language:

Accept-Language = "da, en"

⛔️

Avoid using other standards than ISO 639-1 for specifying the preferred language:

Accept-Language = "danish"

Making debugging easier

Use headers to give the API information about the consumer to ease debugging. There's no industry standard, so feel free to make your own convention, just remember to use it consistently.

Click to see examples

Client-Meta-Information = iOS;staging;v1.2;iOS12;iPhone13

See:

Responses

Response Body

Object at the root level

A body should always return an object at the root level. This enables including additional data about the response such as metadata separate from the object(s). We recommend using data for successful requests with meaningful response data and error for unsuccessful requests with error data being returned.

Click to see examples

Returning a collection should be encapsulated in a key:

{ "data": [ { "username": "..." }, { "username": "..." } ] }

Returning an object (e.g. a user) should also use the data key:

{ "data": { "username": "..." } }

Returning an error should use the error key:

{ "error": { "description": "..." } }

Please see the

error section

for more information.

⛔️

Avoid returning collections at the top level in the response:

[ { "email": "..." }, { "email": "..." } ]

Avoid returning data that are not encapsulated in a root key (data or error):

{ "error": true, "description": "..." }

Return an empty collection when there are no results

To make it easier for the API consumer, return HTTP status code 200 with an empty collection instead of e.g. 204 with no body.

Click to see examples

Combine HTTP status code 200 with empty collections:

{ "data": [] }

⛔️

Avoid using HTTP status code 204 for empty collections.

Use null or unset keys that are not set

In case of missing values return them as null or don't include them. Do not use empty objects or empty strings.

Click to see examples

Return a value as null:

{ "data": { "email": null, "name": "..." } }

Unset a key without a value:

{ "data": { "name": "..." } }

⛔️

Avoid including a key without a meaningful value:

{ "data": { "name": "" } }

Status Codes

Click to see examples

It's ok to use all available response codes,

See list

Here is a list of the commonly used

2xx

200 -> OK, used when on GET request with successful response

201 -> Created, used on POST creating a record in DB

202 -> Accepted, used when request has been received, but processed async

204 -> No Content, used when no response is send, e.g. on DELETE

3xx

301-> Moved Permanently, used if the resource has been moved to another URI

304 -> Not Modified, used if If-Modified-Since header is send and nothing has changed since

4xx

400 -> Bad Request, used when request cannot be processed, remember to give more info

401 -> Unauthorized, used when authorization session is invalid or missing

403 -> Forbidden, used when a route / entity was requested, but users access level does not permit it

404 -> Not Found, used when a route / entity was not found

405 -> Method Not Allowed, used when a route was hit with wrong method

409 -> Conflict, used when an entity conflicts with another entity, e.g. duplicate entities / IDs

422 -> Unprocessable Entity, used when validation rules on POST/PATCH/PUT are not followed

429 -> Too Many Requests, used when you want to rate limit your API

5xx

500 -> Internal Server Error, used for undefined server errors, should store a record in an bug tracking tool like Bugsnag, Crashlytics, Rollbar, New relic

501 -> Not Implemented, used when you want to indicate that the feature/functionality is not implemented (yet)

502 -> Bad Gateway, used when an internal service was not reachable, e.g. in micro service architecture

503 -> Service Unavailable, used when an external service was not reachable, e.g. twilio.com

504 -> Gateway Timeout, used to indicate that a request timed out (e.g. third party service took too long)

⛔️

Custom response codes, eg:

490

205

512

Use response body for the message instead

Auth

TODO

Click to see examples

⛔️

Error Handling

Click to see examples

The error object needs to have the following:

Be consistent

Have all required info

Easily parsable

Should be possible to build a solid UI on top, guiding the user what happened, and how to move on

{ "error": { "localizedTitle": "Title goes here", // Optional title localized for end user "localizedMessage": "Message goes here", // Optional message localized for end user "message": "Invalid format, digits required", // Message for developer "isRecoverable": true, // Is the error handled in the UI is fatal or can it be recovered, eg: try again "identifier": "PASSWORD_NOT_FOLLOWING_PATTERN", // Identifier which the consumer of the API can parse and switch case on "source": "LoginService" // In micro services architecture, you might want to understand what service }, "payload": { "validationErrors": [{ "field": "password", "errors": [{ "type": "required", "localizedMessage": "Please enter a password" }, { "type": "regex", "localizedMessage": "Password format should have following: 8 characters, 1 small letter, 1 big letter & 1 number" }, ] }] }, "metadata": { "errorID": "1234-ABC" // Optional ID for if the error is stored in DB, APM, Bug tracking tools like Bugsnag, Sentry, Rollbar, New Relic etc. } }

⛔️

{ "error": "Internal server error" }

Localization

TODO

Click to see examples

⛔️

Timeouts

Generally APIs should respond in less than 250ms on a wired connection. There needs to be a special reason for exceeding that. Further, it is important to understand that it will be harder and more expensive to scale the backend if response times are high.

It's common that the webserver configuration will timeout the request after 30 or 60 seconds.

Client to server

Since you never know what network the client is on, if they are in a metro or 100mBit wifi. Response time can vary a lot for several reason. Therefore set timeouts to:

Click to see examples

Default: 15 sec

File upload APIs: Align with web server timeout (eg 30 or 60 sec)

If you are going to upload files above 5mb, consider having client upload directly to AWS S3, Dropbox etc. And sending path to server.

⛔️

+ 30sec

If API requests are taking more than 2 sec on a wired connection, consider changing the API design. Eg: Put the operation in a queue system like SQS, Redis, Beanstalkd and inform the client about operation is complete by push notification, web socket, email etc.

Server to server

Server to service APIs should always be very stable due to connection being wired and stable. Therefore we can be much more aggressive about timeouts.

Click to see examples

Depending on service: 1-5 sec timeouts.

Implementing a retry system is strongly advised. If the server is not responding in 1-5 sec, there is a high chance they never respond. Just fail and retry, up to a max of 3-5 retries, and then throw an exception.

⛔️

+10 sec

Pagination

TODO

Click to see examples

⛔️

Partner With Jamie
View Services

More Projects by Jamie