{"id":19319762,"url":"https://github.com/hyperoslo/api-playbook","last_synced_at":"2026-03-01T12:33:04.101Z","repository":{"id":66286525,"uuid":"36788513","full_name":"hyperoslo/api-playbook","owner":"hyperoslo","description":"A place to define the conventions we use to build APIs","archived":false,"fork":false,"pushed_at":"2015-06-11T21:01:29.000Z","size":224,"stargazers_count":4,"open_issues_count":2,"forks_count":1,"subscribers_count":10,"default_branch":"master","last_synced_at":"2025-11-17T12:23:36.149Z","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/hyperoslo.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}},"created_at":"2015-06-03T07:53:22.000Z","updated_at":"2018-12-17T20:43:23.000Z","dependencies_parsed_at":"2023-02-20T02:30:55.658Z","dependency_job_id":null,"html_url":"https://github.com/hyperoslo/api-playbook","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/hyperoslo/api-playbook","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hyperoslo%2Fapi-playbook","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hyperoslo%2Fapi-playbook/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hyperoslo%2Fapi-playbook/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hyperoslo%2Fapi-playbook/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hyperoslo","download_url":"https://codeload.github.com/hyperoslo/api-playbook/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hyperoslo%2Fapi-playbook/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29969243,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-01T11:43:06.159Z","status":"ssl_error","status_checked_at":"2026-03-01T11:43:03.887Z","response_time":124,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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-10T01:25:11.013Z","updated_at":"2026-03-01T12:33:04.066Z","avatar_url":"https://github.com/hyperoslo.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# API styleguide\n\nA place to define the conventions we use to build APIs\n\n\u003c!-- TOC depth:6 withLinks:1 updateOnSave:1 --\u003e\n- [API styleguide](#api-styleguide)\n\t- [Basic Tools](#basic-tools)\n\t- [Localization](#localization)\n\t- [Heroku conventions](#heroku-conventions)\n\t\t- [Foundations](#foundations)\n\t\t\t- [Separate Concerns](#separate-concerns)\n\t\t\t- [Require Secure Connections](#require-secure-connections)\n\t\t\t- [Support ETags for Caching](#support-etags-for-caching)\n\t\t- [Requests](#requests)\n\t\t\t- [Accept serialized JSON in request bodies](#accept-serialized-json-in-request-bodies)\n\t\t\t- [Use consistent path formats](#use-consistent-path-formats)\n\t\t\t\t- [Resource names](#resource-names)\n\t\t\t- [Minimize path nesting](#minimize-path-nesting)\n\t\t- [Responses](#responses)\n\t\t\t- [Return appropriate status codes](#return-appropriate-status-codes)\n\t\t\t- [Provide full resources where available](#provide-full-resources-where-available)\n\t\t\t- [Provide standard timestamps](#provide-standard-timestamps)\n\t\t\t- [Use UTC times formatted in ISO8601](#use-utc-times-formatted-in-iso8601)\n\t\t\t- [Nest foreign key relations](#nest-foreign-key-relations)\n\t\t\t- [Generate structured errors](#generate-structured-errors)\n\t\t\t- [Show rate limit status](#show-rate-limit-status)\n\t\t\t- [Keep JSON minified in all responses](#keep-json-minified-in-all-responses)\n\n\u003c!-- /TOC --\u003e\n\n## Basic Tools\n\nIt's quite common in Hyper to build APIs with [Ruby on Rails](http://rubyonrails.org/), so most of these\nconventions are designed to work well with Rails.\n\n## Localization\n\nWhen the responses need to be localized, it's recommended for the client to send\nthe [`Accept-Language`](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4)\nheader:\n\n```\nAccept-Language: nb-no, no, en\n```\n\n## Heroku conventions\n\nWe take in account most of [Heroku's conventions for building APIs][6ea5c025].\n\n  [6ea5c025]: https://github.com/interagent/http-api-design \"Heroku's conventions for building APIs\"\n\n### Foundations\n\n#### Separate Concerns\n\nKeep things simple while designing by separating the concerns between the\ndifferent parts of the request and response cycle. Keeping simple rules here\nallows for greater focus on larger and harder problems.\n\nRequests and responses will be made to address a particular resource or\ncollection. Use the path to indicate identity, the body to transfer the\ncontents and headers to communicate metadata. Query params may be used as a\nmeans to pass header information also in edge cases, but headers are preferred\nas they are more flexible and can convey more diverse information.\n\n#### Require Secure Connections\n\nRequire secure connections with TLS to access the API, without exception.\nIt’s not worth trying to figure out or explain when it is OK to use TLS\nand when it’s not. Just require TLS for everything.\n\nIdeally, simply reject any non-TLS requests by not responding to requests for\nhttp or port 80 to avoid any insecure data exchange. In environments where this\nis not possible, respond with `403 Forbidden`.\n\nRedirects are discouraged since they allow sloppy/bad client behaviour without\nproviding any clear gain.  Clients that rely on redirects double up on\nserver traffic and render TLS useless since sensitive data will already\n have been exposed during the first call.\n\n#### Support ETags for Caching\n\nInclude an `ETag` header in all responses, identifying the specific\nversion of the returned resource. This allows users to cache resources\nand use requests with this value in the `If-None-Match` header to determine\nif the cache should be updated.\n\n### Requests\n\n#### Accept serialized JSON in request bodies\n\nAccept serialized JSON on `PUT`/`PATCH`/`POST` request bodies, either\ninstead of or in addition to form-encoded data. This creates symmetry\nwith JSON-serialized response bodies, e.g.:\n\n```bash\n$ curl -X POST https://service.com/apps \\\n    -H \"Content-Type: application/json\" \\\n    -d '{\"name\": \"demoapp\"}'\n\n{\n  \"id\": \"01234567-89ab-cdef-0123-456789abcdef\",\n  \"name\": \"demoapp\",\n  \"owner\": {\n    \"email\": \"username@example.com\",\n    \"id\": \"01234567-89ab-cdef-0123-456789abcdef\"\n  },\n  ...\n}\n```\n\n#### Use consistent path formats\n\n##### Resource names\n\nUse the plural version of a resource name unless the resource in question is a\nsingleton within the system (for example, in most systems a given user would\nonly ever have one account). This keeps it consistent in the way you refer to\nparticular resources.\n\n#### Minimize path nesting\n\nIn data models with nested parent/child resource relationships, paths\nmay become deeply nested, e.g.:\n\n```\n/orgs/{org_id}/apps/{app_id}/dynos/{dyno_id}\n```\n\nLimit nesting depth by preferring to locate resources at the root\npath. Use nesting to indicate scoped collections. For example, for the\ncase above where a dyno belongs to an app belongs to an org:\n\n```\n/orgs/{org_id}\n/orgs/{org_id}/apps\n/apps/{app_id}\n/apps/{app_id}/dynos\n/dynos/{dyno_id}\n```\n\n### Responses\n\n#### Return appropriate status codes\n\nReturn appropriate HTTP status codes with each response. Successful\nresponses should be coded according to this guide:\n\n* `200`: Request succeeded for a `GET` call, for a `DELETE` or\n  `PATCH` call that completed synchronously, or for a `PUT` call that\n  synchronously updated an existing resource\n* `201`: Request succeeded for a `POST` call that completed\n  synchronously, or for a `PUT` call that synchronously created a new\n  resource\n* `202`: Request accepted for a `POST`, `PUT`, `DELETE`, or `PATCH` call that\n  will be processed asynchronously\n* `206`: Request succeeded on `GET`, but only a partial response\n  returned: see [above on ranges](#divide-large-responses-across-requests-with-ranges)\n\nPay attention to the use of authentication and authorization error codes:\n\n* `401 Unauthorized`: Request failed because user is not authenticated\n* `403 Forbidden`: Request failed because user does not have authorization to\n  access a specific resource\n\nReturn suitable codes to provide additional information when there are errors:\n\n* `422 Unprocessable Entity`: Your request was understood, but contained invalid parameters\n* `429 Too Many Requests`: You have been rate-limited, retry later\n* `500 Internal Server Error`: Something went wrong on the server, check status\n  site and/or report the issue\n\nRefer to the [HTTP response code spec](https://tools.ietf.org/html/rfc7231#section-6)\nfor guidance on status codes for user error and server error cases.\n\n#### Provide full resources where available\n\nProvide the full resource representation (i.e. the object with all\nattributes) whenever possible in the response. Always provide the full\nresource on 200 and 201 responses, including `PUT`/`PATCH` and `DELETE`\nrequests, e.g.:\n\n```bash\n$ curl -X DELETE \\  \n  https://service.com/apps/1f9b/domains/0fd4\n\nHTTP/1.1 200 OK\nContent-Type: application/json;charset=utf-8\n...\n{\n  \"created_at\": \"2012-01-01T12:00:00Z\",\n  \"hostname\": \"subdomain.example.com\",\n  \"id\": \"01234567-89ab-cdef-0123-456789abcdef\",\n  \"updated_at\": \"2012-01-01T12:00:00Z\"\n}\n```\n\n202 responses will not include the full resource representation,\ne.g.:\n\n```bash\n$ curl -X DELETE \\  \n  https://service.com/apps/1f9b/dynos/05bd\n\nHTTP/1.1 202 Accepted\nContent-Type: application/json;charset=utf-8\n...\n{}\n```\n\n#### Provide standard timestamps\n\nProvide `created_at` and `updated_at` timestamps for resources by default,\ne.g:\n\n```javascript\n{\n  // ...\n  \"created_at\": \"2012-01-01T12:00:00Z\",\n  \"updated_at\": \"2012-01-01T13:00:00Z\",\n  // ...\n}\n```\n\nThese timestamps may not make sense for some resources, in which case\nthey can be omitted.\n\n#### Use UTC times formatted in ISO8601\n\nAccept and return times in UTC only. Render times in ISO8601 format,\ne.g.:\n\n```\n\"finished_at\": \"2012-01-01T12:00:00Z\"\n```\n\n#### Nest foreign key relations\n\nSerialize foreign key references with a nested object, e.g.:\n\n```javascript\n{\n  \"name\": \"service-production\",\n  \"owner\": {\n    \"id\": \"5d8201b0...\"\n  },\n  // ...\n}\n```\n\nInstead of e.g.:\n\n```javascript\n{\n  \"name\": \"service-production\",\n  \"owner_id\": \"5d8201b0...\",\n  // ...\n}\n```\n\nThis approach makes it possible to inline more information about the\nrelated resource without having to change the structure of the response\nor introduce more top-level response fields, e.g.:\n\n```javascript\n{\n  \"name\": \"service-production\",\n  \"owner\": {\n    \"id\": \"5d8201b0...\",\n    \"name\": \"Alice\",\n    \"email\": \"alice@heroku.com\"\n  },\n  // ...\n}\n```\n\n#### Generate structured errors\n\nGenerate consistent, structured response bodies on errors. Include a\nmachine-readable error `id`, a human-readable error `message`, and\noptionally a `url` pointing the client to further information about the\nerror and how to resolve it, e.g.:\n\n```\nHTTP/1.1 429 Too Many Requests\n```\n\n```json\n{\n  \"id\":      \"rate_limit\",\n  \"message\": \"Account reached its API rate limit.\",\n  \"url\":     \"https://docs.service.com/rate-limits\"\n}\n```\n\nDocument your error format and the possible error `id`s that clients may\nencounter.\n\n#### Show rate limit status\n\nRate limit requests from clients to protect the health of the service\nand maintain high service quality for other clients. You can use a\n[token bucket algorithm](http://en.wikipedia.org/wiki/Token_bucket) to\nquantify request limits.\n\nReturn the remaining number of request tokens with each request in the\n`RateLimit-Remaining` response header.\n\n#### Keep JSON minified in all responses\n\nExtra whitespace adds needless response size to requests, and many\nclients for human consumption will automatically \"prettify\" JSON\noutput. It is best to keep JSON responses minified e.g.:\n\n```json\n{\"beta\":false,\"email\":\"alice@heroku.com\",\"id\":\"01234567-89ab-cdef-0123-456789abcdef\",\"last_login\":\"2012-01-01T12:00:00Z\",\"created_at\":\"2012-01-01T12:00:00Z\",\"updated_at\":\"2012-01-01T12:00:00Z\"}\n```\n\nInstead of e.g.:\n\n```json\n{\n  \"beta\": false,\n  \"email\": \"alice@heroku.com\",\n  \"id\": \"01234567-89ab-cdef-0123-456789abcdef\",\n  \"last_login\": \"2012-01-01T12:00:00Z\",\n  \"created_at\": \"2012-01-01T12:00:00Z\",\n  \"updated_at\": \"2012-01-01T12:00:00Z\"\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhyperoslo%2Fapi-playbook","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhyperoslo%2Fapi-playbook","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhyperoslo%2Fapi-playbook/lists"}