{"id":19502753,"url":"https://github.com/ml-opensource/api-manifesto","last_synced_at":"2026-02-28T20:35:39.888Z","repository":{"id":106622035,"uuid":"219774124","full_name":"ml-opensource/API-manifesto","owner":"ml-opensource","description":"Documents how to write APIs","archived":false,"fork":false,"pushed_at":"2022-12-01T17:09:45.000Z","size":55,"stargazers_count":7,"open_issues_count":20,"forks_count":5,"subscribers_count":16,"default_branch":"master","last_synced_at":"2025-02-25T21:42:55.097Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ml-opensource.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-11-05T15:09:08.000Z","updated_at":"2024-09-30T15:01:57.000Z","dependencies_parsed_at":null,"dependency_job_id":"e2a5e6b8-e0b9-471c-a34b-43bb1e63eb78","html_url":"https://github.com/ml-opensource/API-manifesto","commit_stats":null,"previous_names":["ml-opensource/api-manifesto"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ml-opensource/API-manifesto","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ml-opensource%2FAPI-manifesto","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ml-opensource%2FAPI-manifesto/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ml-opensource%2FAPI-manifesto/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ml-opensource%2FAPI-manifesto/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ml-opensource","download_url":"https://codeload.github.com/ml-opensource/API-manifesto/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ml-opensource%2FAPI-manifesto/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":285129557,"owners_count":27119601,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-11-18T02:00:05.759Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-10T22:18:08.053Z","updated_at":"2025-11-18T20:03:41.576Z","avatar_url":"https://github.com/ml-opensource.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003c!--\nTable of contents created by [gh-md-toc](https://github.com/ekalinin/github-markdown-toc)\n\nuse the following command: `gh-md-toc README.md | grep -v g-emoji` to filter out lines with emoji\n--\u003e\n\n\u003c!--\n---\ntitle: API Manifesto\ntags: [api,manifesto,best practice]\nlink: https://github.com/monstar-lab-oss/API-manifesto\n---\n--\u003e\n\n\u003c!--\nNew section template:\n\n## Section\n\n### TODO\n\u003cdetails\u003e\n\u003csummary\u003eClick to see examples\u003c/summary\u003e\n\n#### ✅\n\n#### ⛔️\n\n\u003c/details\u003e\n--\u003e\n\n# API Manifesto\nDocuments how to write APIs\n\n## Introduction\n\nThis API Manifesto is a fast and easy overview of the most important elements for building a rock-solid API, which both the backend and frontend team enjoys working with.\nAPIs are suppose to be very strict, it's a contract between Backend \u0026 Frontend. In the same time, there is no reason for APIs to be different depending on which language / framework was used in the Backend. \n\nThis is not a full blown manifesto, check the [Inspiration section](#inspiration) for references to other manifestos with more details.\n\nTable of Contents\n=================\n\n   * [API Manifesto](#api-manifesto)\n      * [Introduction](#introduction)\n   * [Table of Contents](#table-of-contents)\n   * [Requests](#requests)\n      * [URLs](#urls)\n         * [Prefix API endpoints](#prefix-api-endpoints)\n         * [API versioning](#api-versioning)\n      * [Request Headers](#request-headers)\n         * [Protected endpoints](#protected-endpoints)\n         * [Supporting localization](#supporting-localization)\n         * [Making debugging easier](#making-debugging-easier)\n   * [Responses](#responses)\n      * [Response Body](#response-body)\n         * [Object at the root level](#object-at-the-root-level)\n         * [Return an empty collection when there are no results](#return-an-empty-collection-when-there-are-no-results)\n      * [Use null or unset keys that are not set](#use-null-or-unset-keys-that-are-not-set)\n   * [Status Codes](#status-codes)\n   * [Auth](#auth)\n      * [TODO](#todo-1)\n   * [Error Handling](#error-handling)\n   * [Localization](#localization)\n      * [TODO](#todo-3)\n   * [Timeouts](#timeouts)\n      * [Client to Server](#client-to-server)\n      * [Server to Server](#client-to-server)\n   * [Pagination](#pagination)\n      * [TODO](#todo-5)\n   * [Inspiration](#inspiration)\n   \n# Requests\n\n## URLs\n\n### Prefix API endpoints\n\nPrefix API endpoints with `/api/` to separate them from other URLs like HTML views served on the same server.\n\n\u003cdetails\u003e\n\u003csummary\u003eClick to see examples\u003c/summary\u003e\n\n#### ✅\n\nMake sure to prefix your API endpoints with `/api/`:\n\n```bash\nwww.example.com/api/v1/products/1\n```\n\n\u003c/details\u003e\n\n### API versioning\n\nVersioning 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.\n\nInclude 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`](#prefix-api-endpoints) path component.\n\nIn case of an existing API that doesn't have this versioning scheme but needs a new version, skip `v1` and go straight to `v2`.\n\n\u003e We're not recommending to use the headers (typically the Accept header) for versioning. The URL based approach is more obvious, usually simpler to implement, and testing URLs can be done in the browser.\n\n\u003cdetails\u003e\n\u003csummary\u003eClick to see examples\u003c/summary\u003e\n\n#### ✅\n\nInclude the API's version in the URL:\n\n```bash\nwww.example.com/api/v2/auth/login\n```\n\n#### ⛔️\n\nDon't depend on a header like \"Accept\" for versioning:\n\n```bash\nAccept = \"application/vnd.example.v1+json\"\n```\n\n\u003c/details\u003e\n\n### REST resources\nAfter 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.\nA 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.\n\n\u003cdetails\u003e\n\u003csummary\u003eClick to see examples\u003c/summary\u003e\n\n#### ✅\n\nRefer to a resource with a plural noun:\n\n```bash\n/api/v1/shops\n```\n\n#### ✅\n\nUse an identifier following a noun to refer to a single entity:\n\n```bash\n/api/v1/products/42\n```\n\n#### ✅\n\nRefer to a nested resource like so:\n\n```bash\n/api/v1/posts/1/comments\n```\n\nIn 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:\n\n```bash\n/api/v1/comments/87\n```\n\nBe 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.\n\n\u003c/details\u003e\n\n### Query parameters\n\nQuery 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.\n\n\u003cdetails\u003e\n\u003csummary\u003eClick to see examples\u003c/summary\u003e\n\n#### ✅\n\nUse query parameters for a paginated endpoint to define which page and with how many results per page you want to retrieve:\n\n```bash\n/api/v1/posts?page=2\u0026perPage=10\n```\n\n#### ⛔️\n\nDo not use query parameters for authentication:\n\n```bash\n/api/v1/posts?apiKey=a7dhas8u\n```\n\n\u003c/details\u003e\n\n### HTTP Methods\n\nHTTP methods are used to indicate what action to perform with the resource.\n\n#### GET\n\nA 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).\n\n#### POST\n\nPOST is used to create new resources.\n\n#### PATCH\n\nPATCH 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.\n\n#### PUT\n\nWith PUT calls we can replace entire objects. Only the database identifier should not be changed.\n\n#### DELETE\n\nTo delete a resource, use the DELETE method.\n\n#### HEAD\n\nA 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.\n\n## Request Headers\n\n### Protected endpoints\n\nUse the `Authorization` header to consume protected endpoints. See the [Auth](#auth) section for more information on how to handle authorization and authentication.\n\n\u003cdetails\u003e\n\u003csummary\u003eClick to see examples\u003c/summary\u003e\n\n#### ✅\n\nUse `Authorization` to authorize:\n\n```bash\nAuthorization = \"Basic QWxhZGRpbjpPcGVuU2VzYW1l\"\n```\n\n#### ⛔️\n\nAvoid using custom headers for authorization:\n\n```bash\nUserToken = \"QWxhZGRpbjpPcGVuU2VzYW1l\"\n```\n\n\u003c/details\u003e\n\n### Supporting localization\n\nIn order to support localization now and in the future, the `Accept-Language` should be used to indicate the client's language towards the API. \n\n\u003cdetails\u003e\n\u003csummary\u003eClick to see examples\u003c/summary\u003e\n\n#### ✅\n\nUse [ISO 639-1](http://www.loc.gov/standards/iso639-2/php/code_list.php) codes to indicate the preferred language of the response.\n\n```bash\nAccept-Language = \"da\"\n```\n\nUse a prioritized list of languages to influence the fallback language:\n\n```bash\nAccept-Language = \"da, en\"\n```\n\n#### ⛔️\n\nAvoid using other standards than ISO 639-1 for specifying the preferred language:\n\n```bash\nAccept-Language = \"danish\"\n```\n\n\u003c/details\u003e\n\n### Making debugging easier\n\nUse 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.\n\n\u003cdetails\u003e\n\u003csummary\u003eClick to see examples\u003c/summary\u003e\n\n#### ✅\n\n```bash\nClient-Meta-Information = iOS;staging;v1.2;iOS12;iPhone13\n```\n\n\u003c/details\u003e\n\nSee:\n - [N-Meta-Vapor](https://github.com/nodes-vapor/n-meta)    \n - [N-Meta-PHP](https://github.com/monstar-lab-oss/n-meta-php)\n - [N-Meta-Laravel](https://github.com/monstar-lab-oss/n-meta-laravel)\n\n# Responses\n\n## Response Body\n\n### Object at the root level\n\nA 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.\n\n\u003cdetails\u003e\n\u003csummary\u003eClick to see examples\u003c/summary\u003e\n\n#### ✅\n\nReturning a collection should be encapsulated in a key:\n\n```json\n{\n    \"data\": [\n        {\n            \"username\": \"...\"\n        },\n        {\n            \"username\": \"...\"\n        }\n    ]\n}\n```\n\nReturning an object (e.g. a user) should also use the `data` key:\n\n```json\n{\n    \"data\": {\n        \"username\": \"...\"\n    }\n}\n```\n\nReturning an error should use the `error` key:\n\n```json\n{\n    \"error\": {\n        \"description\": \"...\"\n    }\n}\n```\n\nPlease see the [error section](#errors) for more information.\n\n#### ⛔️\n\nAvoid returning collections at the top level in the response:\n\n```json\n[\n    {\n        \"email\": \"...\"\n    },\n    {\n        \"email\": \"...\"\n    }\n]\n```\n\nAvoid returning data that are not encapsulated in a root key (`data` or `error`):\n\n```json\n{\n    \"error\": true,\n    \"description\": \"...\"\n}\n```\n\n\u003c/details\u003e\n\n### Return an empty collection when there are no results\n\nTo make it easier for the API consumer, return HTTP status code `200` with an empty collection instead of e.g. `204` with no body.\n\n\u003cdetails\u003e\n\u003csummary\u003eClick to see examples\u003c/summary\u003e\n\n#### ✅\n\nCombine HTTP status code `200` with empty collections:\n\n```json\n{\n    \"data\": []\n}\n```\n\n#### ⛔️\n\nAvoid using HTTP status code `204` for empty collections.\n\n\u003c/details\u003e\n\n### Use `null` or unset keys that are not set\n\nIn case of missing values return them as `null` or don't include them. Do not use empty objects or empty strings.\n\n\u003cdetails\u003e\n\u003csummary\u003eClick to see examples\u003c/summary\u003e\n\n#### ✅\n\nReturn a value as `null`:\n\n```json\n{\n    \"data\": {\n        \"email\": null,\n        \"name\": \"...\"\n    }\n}\n```\n\nUnset a key without a value:\n\n```json\n{\n    \"data\": {\n        \"name\": \"...\"\n    }\n}\n```\n\n#### ⛔️\n\nAvoid including a key without a meaningful value:\n\n```json\n{\n    \"data\": {\n        \"name\": \"\"\n    }\n}\n```\n\u003c/details\u003e\n\n## Status Codes\n\n\u003cdetails\u003e\n\u003csummary\u003eClick to see examples\u003c/summary\u003e\n\n#### ✅\n\nIt's ok to use all available response codes, [See list](https://www.restapitutorial.com/httpstatuscodes.html)\n\nHere is a list of the commonly used\n\n#### 2xx\n - `200` -\u003e **OK**, used when on `GET` request with successful response \n - `201` -\u003e **Created**, used on `POST` creating a record in DB\n - `202` -\u003e **Accepted**, used when request has been received, but processed async\n - `204` -\u003e **No Content**, used when no response is send, e.g. on `DELETE`\n\n#### 3xx\n - `301`-\u003e **Moved Permanently**, used if the resource has been moved to another `URI`\n - `304` -\u003e **Not Modified**, used if `If-Modified-Since` header is send and nothing has changed since\n \n#### 4xx\n\n - `400` -\u003e **Bad Request**, used when request cannot be processed, remember to give more info\n - `401` -\u003e **Unauthorized**, used when authorization session is invalid or missing\n - `403` -\u003e **Forbidden**, used when a route / entity was requested, but users access level does not permit it \n - `404` -\u003e **Not Found**, used when a route / entity was not found\n - `405` -\u003e **Method Not Allowed**, used when a route was hit with wrong method\n - `409` -\u003e **Conflict**, used when an entity conflicts with another entity, e.g. duplicate entities / IDs\n - `422` -\u003e **Unprocessable Entity**, used when validation rules on `POST/PATCH/PUT` are not followed\n - `429` -\u003e **Too Many Requests**, used when you want to rate limit your API\n \n#### 5xx \n\n- `500` -\u003e **Internal Server Error**, used for undefined server errors, should store a record in an bug tracking tool like Bugsnag, Crashlytics, Rollbar, New relic\n- `501` -\u003e **Not Implemented**, used when you want to indicate that the feature/functionality is not implemented (yet)\n- `502` -\u003e **Bad Gateway**, used when an internal service was not reachable, e.g. in micro service architecture\n- `503` -\u003e **Service Unavailable**, used when an external service was not reachable, e.g. twilio.com \n- `504` -\u003e **Gateway Timeout**, used to indicate that a request timed out (e.g. third party service took too long)\n   \n#### ⛔️\n\nCustom response codes, eg:\n\n- 490 \n- 205\n- 512\n\nUse response body for the message instead\n\n\u003c/details\u003e\n\n# Auth\n\nAuthentication is one of the most essential and important parts of the API. Authentication implementations is highly dependent on the requirements and features of each specific project, so we will not cover all all possible options of implementation. However we will specify a bunch of common requirements that apply to any authentication method:\n- **Always** use TLS-encrypted connection, when trying to authenticate an user.\n- **Always** store passwords/secrets hashed/encrypted. **Never** store passwords/secrets as a plain text. **Never** implement your own encryption algorithm, use time-tested solutions available for your stack.\n- **Never** pass sensitive information as query string parameters. It can be logged by a web server, proxy or load balancer and make a risk of data leak.\n- You should return an user’s API token **only** in these cases:\n   - user is **successfully created**\n   - user is **successfully authenticated**\n   - tokens are **successfully refreshed**\n\nWhen authentication is implemented by us, we highly encourage following these recommendations:\n\n## User authentication\n- Use [Authorization](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization) HTTP header.\n- Use Bearer scheme (described in [RFC6750](https://www.rfc-editor.org/rfc/rfc6750)).\n - Use JWT (described in [RFC7591](https://www.rfc-editor.org/rfc/rfc7519)) as a Bearer token.\n - Avoid implementing authorization flow by yourself, use well-known libraries and frameworks instead.\n\n## Token payload\nToken payload should contain following data:\n```json\n{\n    \"access_token\": \"\u003caccess_token\u003e\",\n    \"refresh_token\": \"\u003crefresh_token\u003e\",\n    \"expires_in\": \u003cseconds\u003e\n}\n```\n\n## 3rd party authentication and SSO\nFor implementing authentication with 3rd party services (e.g. Facebook, Github etc.) or SSO we recommend to use OAuth2.0 or/and OIDC. Client may demand using their IdP such as KeyCloak or Azure Active Directory, but as soon as all these providers implement standard protocols (OAuth2.0, OIDC), the choice of a specific provider does not make any significant changes in implementation of API.\n\n\n\n## TODO\n\n\u003cdetails\u003e\n\u003csummary\u003eClick to see examples\u003c/summary\u003e\n\n### ✅\n\n### ⛔️\n\n\u003c/details\u003e\n\n# Error Handling\n\n\u003cdetails\u003e\n\u003csummary\u003eClick to see examples\u003c/summary\u003e\n\n### ✅\n\nThe error object needs to have the following: \n - Be consistent\n - Have all required info\n - Easily parsable\n - Should be possible to build a solid UI on top, guiding the user what happened, and how to move on\n\n```js\n{\n    \"error\": {\n        \"localizedTitle\": \"Title goes here\", // Optional title localized for end user\n        \"localizedMessage\": \"Message goes here\", // Optional message localized for end user\n        \"message\": \"Invalid format, digits required\", // Message for developer\n        \"isRecoverable\": true, // Is the error handled in the UI is fatal or can it be recovered, eg: try again\n        \"identifier\": \"PASSWORD_NOT_FOLLOWING_PATTERN\", // Identifier which the consumer of the API can parse and switch case on\n        \"source\": \"LoginService\" // In micro services architecture, you might want to understand what service\n    },\n    \"payload\": {\n        \"validationErrors\": [{\n            \"field\": \"password\",\n            \"errors\": [{\n                    \"type\": \"required\",\n                    \"localizedMessage\": \"Please enter a password\"\n                },\n                {\n                    \"type\": \"regex\",\n                    \"localizedMessage\": \"Password format should have following: 8 characters, 1 small letter, 1 big letter \u0026 1 number\"\n                },\n            ]\n        }]\n    },\n    \"metadata\": {\n        \"errorID\": \"1234-ABC\" // Optional ID for if the error is stored in DB, APM, Bug tracking tools like Bugsnag, Sentry, Rollbar, New Relic etc.\n    }\n}\n```\n\n### ⛔️\n\n```json\n{\n  \"error\": \"Internal server error\"\n}\n```\n\n\u003c/details\u003e\n\n# Localization\n\n## TODO\n\n\u003cdetails\u003e\n\u003csummary\u003eClick to see examples\u003c/summary\u003e\n\n### ✅\n\n### ⛔️\n\n\u003c/details\u003e\n\n# Timeouts\n\nGenerally 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.\n\nIt's common that the webserver configuration will timeout the request after 30 or 60 seconds. \n\n## Client to server\n\nSince 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.\nTherefore set timeouts to:\n\n\u003cdetails\u003e\n\u003csummary\u003eClick to see examples\u003c/summary\u003e\n\n### ✅\n\n__Default:__ 15 sec\n\n__File upload APIs:__ Align with web server timeout (eg 30 or 60 sec)\n\n_If you are going to upload files above 5mb, consider having client upload directly to AWS S3, Dropbox etc. And sending path to server._\n\n### ⛔️\n\n__+ 30sec__\n\nIf API requests are taking more than 2 sec on a wired connection, consider changing the API design. \nEg: 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.\n\n\u003c/details\u003e\n\n## Server to server\n\nServer to service APIs should always be very stable due to connection being wired and stable. Therefore we can be much more aggressive about timeouts.\n\u003cdetails\u003e\n\u003csummary\u003eClick to see examples\u003c/summary\u003e\n\n### ✅\n\nDepending on service: 1-5 sec timeouts.\n\nImplementing 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.\n\n### ⛔️\n\n__+10 sec__\n\n\u003c/details\u003e\n\n# Pagination\n\n## TODO\n\n\u003cdetails\u003e\n\u003csummary\u003eClick to see examples\u003c/summary\u003e\n\n### ✅\n\n### ⛔️\n\n\u003c/details\u003e\n\n# Inspiration\n\nThese guidelines have been made with inspiration from the following API guidelines:\n\n- [Microsoft API Guidelines](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md)\n- [Zalando API Guidelines](https://opensource.zalando.com/restful-api-guidelines/)\n- [PayPal API Guidelines](https://github.com/paypal/api-standards/blob/master/api-style-guide.md)\n- [Atlassian API Guidelines](https://developer.atlassian.com/server/framework/atlassian-sdk/atlassian-rest-api-design-guidelines-version-1)\n- [JSON:API](https://jsonapi.org)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fml-opensource%2Fapi-manifesto","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fml-opensource%2Fapi-manifesto","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fml-opensource%2Fapi-manifesto/lists"}