{"id":13625623,"url":"https://github.com/gocardless/http-api-design","last_synced_at":"2026-01-26T12:37:02.321Z","repository":{"id":18453934,"uuid":"21647582","full_name":"gocardless/http-api-design","owner":"gocardless","description":"HTTP Design Guidelines","archived":false,"fork":false,"pushed_at":"2021-06-16T06:07:37.000Z","size":37,"stargazers_count":417,"open_issues_count":0,"forks_count":29,"subscribers_count":98,"default_branch":"master","last_synced_at":"2025-01-14T08:52:53.185Z","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":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gocardless.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-07-09T09:57:46.000Z","updated_at":"2024-11-20T15:34:42.000Z","dependencies_parsed_at":"2022-09-11T17:13:09.382Z","dependency_job_id":null,"html_url":"https://github.com/gocardless/http-api-design","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gocardless%2Fhttp-api-design","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gocardless%2Fhttp-api-design/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gocardless%2Fhttp-api-design/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gocardless%2Fhttp-api-design/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gocardless","download_url":"https://codeload.github.com/gocardless/http-api-design/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241787488,"owners_count":20020099,"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","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-08-01T21:01:58.498Z","updated_at":"2026-01-26T12:37:02.292Z","avatar_url":"https://github.com/gocardless.png","language":null,"funding_links":[],"categories":["Others"],"sub_categories":[],"readme":"# HTTP API Design Standards\n\n## Guidelines\n\nThis document provides guidelines and examples for GoCardless APIs, encouraging consistency, maintainability, and best practices.\n\n### Sources:\n\n- https://github.com/interagent/http-api-design\n- https://www.gov.uk/service-manual/making-software/apis.html\n- http://www.mnot.net/blog/\n- http://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api\n- https://github.com/WhiteHouse/api-standards\n- http://apigee.com/about/content/api-fa%C3%A7ade-pattern\n- https://pages.apigee.com/web-api-design-ebook.html\n- https://groups.google.com/forum/#!forum/api-craft\n\n## JSON API\n\nAll endpoints must follow the core [JSON API spec](http://jsonapi.org/format/)\n\nChanges from JSON API:\n\n- The primary resource must be keyed by its resource type. The endpoint URL must also match the resource type.\n- API errors do not currently follow the JSON API spec.\n- Updates should always return `200 OK` with the full resource to simplify internal logic.\n\nExample of keying the resource by type:\n\n```json\nGET /posts/1\n```\n\n```json\n{\n  \"posts\": {\n    \"id\": 1\n  }\n}\n```\n\nAll action calls to an endpoint must be wrapped in a `data` envelope.\nThis is because actions are carried out on a resource but use a different data type than the core resource itself.\n\nExample of a action call:\n\n```json\nPOST /posts/action/share\n\n{\n  \"data\": {\n    \"recipientID\": \"U123\"\n  }\n}\n```\n\n## JSON only\n\nThe API should only support JSON.\n\nReference: http://www.mnot.net/blog/2012/04/13/json_or_xml_just_decide\n\n## General guidelines\n\n- A URL identifies a resource.\n- URLs should include nouns, not verbs.\n- For consistency, only use plural nouns (e.g. \"posts\" instead of \"post\").\n- Use HTTP verbs (`GET`, `POST`, `PUT`, `DELETE`) to operate on resources.\n- Use filtering instead of nested resources: `/payments?subscription=xyz` rather than\n  `/subscriptions/xyz/payments`. Nested resources enforce relationships that could\n  change and makes clients harder to write.\n- API versions should be represented as dates documented in a changelog. Version number should not be in the URL.\n- API should be behind a subdomain: `api.gocardless.com`\n\n## RESTful URLs\n\n### Good URL examples\n\n- List of payments:\n  - `GET https://api.gocardless.com/payments`\n- Filtering is a query:\n  - `GET https://api.gocardless.com/payments?status=failed\u0026sort_field=-created`\n  - `GET https://api.gocardless.com/payments?sort_field=created`\n- A single payment:\n  - `GET https://api.gocardless.com/payments/1234`\n- All amendments in (or belonging to) this subscription:\n  - `GET https://api.gocardless.com/subscription_amendments?subscription=1234`\n- Include nested resources in a comma separated list:\n  - `GET https://api.gocardless.com/payments/1234?include=events`\n- Include only selected fields in a comma separated list:\n  - `GET https://api.gocardless.com/payments/1234?fields=amount`\n- Get multiple resources:\n  - `GET https://api.gocardless.com/payments/1234,444,555,666`\n- Action on resource:\n  - `POST https://api.gocardless.com/payments/1234/actions/cancel`\n\n### Bad URL examples\n\n- Singular nouns:\n  - `GET https://api.gocardless.com/payment`\n  - `GET https://api.gocardless.com/payment/123`\n  - `GET https://api.gocardless.com/payment/action`\n- Verbs in the URL:\n  - `GET https://api.gocardless.com/payment/create`\n- Nested resources:\n  - `GET https://api.gocardless.com/subscriptions/1234/amendments`\n- Filtering outside of query string:\n  - `GET https://api.gocardless.com/payments/desc`\n- Filtering to get multiple resources:\n  - `GET https://api.gocardless.com/payments?id[]=11\u0026id[]=22`\n\n## HTTP verbs\n\nHere's an example of how HTTP verbs map to create, read, update, delete operations in a particular context:\n\n| HTTP METHOD | POST            | GET                 | PUT                                                | PATCH                                                         | DELETE           |\n| :---------- | :-------------- | :------------------ | :------------------------------------------------- | :------------------------------------------------------------ | :--------------- |\n| CRUD OP     | CREATE          | READ                | UPDATE                                             | UPDATE                                                        | DELETE           |\n| /plans      | Create new plan | List plans          | Bulk update                                        | Error                                                         | Delete all plans |\n| /plans/1234 | Error           | Show Plan If exists | If exists, full/partial update Plan; If not, error | If exists, update Plan using JSON Patch format; If not, error | Delete Plan      |\n\n## Actions\n\nAvoid resource actions. Create separate resources where possible.\n\n#### Good\n\n```http\nPOST /refunds?payment=ID\u0026amount=1000\n```\n\n#### Bad\n\n```http\nPOST /payments/ID/refund\n```\n\nWhere special actions are required, place them under an `actions` prefix.\nActions should always be idempotent.\n\n```http\nPOST /payments/ID/actions/cancel\n```\n\n## Responses\n\nDon’t set values in keys.\n\n#### Good\n\n```json\n\"tags\": [\n  {\"id\": \"125\", \"name\": \"Environment\"},\n  {\"id\": \"834\", \"name\": \"Water Quality\"}\n]\n```\n\n#### Bad\n\n```json\n\"tags\": [\n  {\"125\": \"Environment\"},\n  {\"834\": \"Water Quality\"}\n]\n```\n\n## String IDs\n\nAlways return string ids. Some languages, like JavaScript, don't support big ints. Serialize/deserialize ints to strings if storing ids as ints.\n\n## Error handling\n\nError responses should include a message for the user, an internal error type (corresponding to some\nspecific internally determined constant represented as a string), and links to info for developers.\n\nThere must only be one top level error. Errors should be returned in turn. This makes internal error\nlogic and dealing with errors as a consumer of the API easier.\n\nValidation and resource errors are nested in the top level error under `errors`.\n\nThe error is nested in `error` to make it possible to add, for example, deprecation errors on successful requests.\n\nThe HTTP status `code` is used as a top level error, `type` is used as a sub error, and nested\n`errors` may have more specific `type` errors, such as `invalid_field`.\n\n### Formating errors vs integration errors\n\nFormatting errors should be separate from errors handled by the integration.\n\nFormatting errors include things like field presence and length, and are returned when incorrect\ndata is sent to the API.\n\nAn example of an error that should be handled by the integration is attempting to create a payment\nagainst a mandate that has expired. This is an edge case that the API integration needs to\nhandle. Do not mask these errors as validation errors. Return them as a top level error instead.\n\n### Top level error\n\n- Top level errors MUST implement `request_id`, `type`, `code` and `message`.\n- `type` MUST relate to the `reason`. For example, use it to categorise the error: `reason: api_error`.\n- `message` MUST be specific.\n- Top level errors MAY implement `documentation_url`, `request_url` and `id`.\n- Only return `id` for server errors (5xx). The `id` should point to the exception you track internally.\n\n```json\n{\n  \"error\": {\n    \"documentation_url\": \"https://api.gocardless.com/docs/beta/errors#access_forbidden\",\n    \"request_url\": \"https://api.gocardless.com/requests/REQUEST_ID\",\n    \"request_id\": \"REQUEST_ID\",\n    \"id\": \"ERROR_ID\",\n    \"type\": \"access_forbidden\",\n    \"code\": 403,\n    \"message\": \"You don't have the right permissions to access this resource\"\n  }\n}\n```\n\n### Nested errors\n\n- Nested errors MUST implement `reason` and `message`.\n- `reason` MUST be specific to the error.\n- Nested errors MAY implement `field`.\n\n```json\n{\n  \"error\": {\n    \"top level errors\": \"...\",\n\n    \"errors\": [\n      {\n        \"field\": \"account_number\",\n        \"reason\": \"missing_field\",\n        \"message\": \"Account number is required\"\n      }\n    ]\n  }\n}\n```\n\n### HTTP status code summary\n\n- `200 OK` - everything worked as expected.\n- `400 Bad Request` - e.g. invalid JSON.\n- `401 Unauthorized` - no valid API key provided.\n- `402 Request Failed` - parameters were valid but request failed.\n- `403 Forbidden` - missing or invalid permissions.\n- `404 Not Found` - the requested item doesn’t exist.\n- `422 Unprocessable Entity` - parameters were invalid/validation failed.\n- `500, 502, 503, 504 Server errors` - something went wrong on GoCardless’ end.\n\n#### 400 Bad Request\n\n- When the request body contains malformed JSON.\n- When the JSON is valid but the document structure is invalid (e.g. passing an array when an object should be passed).\n\n#### 422 Unprocessable Entity\n\n- When model validations fail for fields (e.g. name too long).\n- Trying to create a resource when a related resource is in a bad state.\n\n## What changes are considered “backwards-compatible”?\n\n- Adding new API resources.\n- Adding new optional request parameters to existing API methods.\n- Adding new properties to existing API responses.\n- Changing the order of properties in existing API responses.\n- Changing the length or format of object IDs or other opaque strings.\n  - This includes adding or removing fixed prefixes (such as ch\\_ on charge IDs).\n  - You can safely assume object IDs we generate will never exceed 128 characters, but you should be\n    able to handle IDs of up to that length. If for example you’re using MySQL, you should store IDs\n    in a VARCHAR(128) COLLATE utf8_bin column (the COLLATE configuration ensures case-sensitivity in\n    lookups).\n- Adding new event types. Your webhook listener should gracefully handle unfamiliar events types.\n\n## Versioning changes\n\nThe versioning scheme is designed to promote incremental improvement to the API and discourage rewrites.\n\nServer initiated events such as webhooks should not contain serialised resources. If a resource\nchanged, provide its id instead and let the client request it using a version.\n\n### Format\n\nVersions should be dated as ISO8601 (YYYY-MM-DD)\n\n- Good: 2014-05-04\n- Bad: v-1.1, v1.2, 1.3, v1, v2\n\n### Version maintenance\n\nMaintain old API versions for at least 6 months.\n\n### Implementation guidelines\n\nThe API version must be set using a custom HTTP header. The API version must not be defined in the\nURL structure (e.g. `/v1`) because it makes incremental change impossible.\n\n#### HTTP Header\n\n`GoCardless-Version: 2014-05-04`\n\nEnforce the header on all requests.\n\nValidate the version against available versions. Do not allow dates up to a version.\n\nThe API changelog must only contain backwards-incompatible changes. All non-breaking changes are automatically available to old versions.\n\nReference: https://stripe.com/docs/upgrades\n\n## X-Headers\n\nThe use of `X-Custom-Header` has [been deprecated](http://tools.ietf.org/html/rfc6648).\n\n## Resource filtering\n\nResource filters MUST be in singular form.\n\nMultiple ids should be supplied to a filter as a comma separated list, and should be translated into an `OR` query. Chaining multiple filters with `\u0026` should be translated into an `AND` query.\n\n#### Good\n\n```http\nGET /refunds?payment=ID1,ID2\u0026customer=ID1\n```\n\n#### Bad\n\n```http\nGET /refunds?payments=ID1,ID2\u0026customer=ID1\n```\n\n## Pagination\n\nAll list/index endpoints must be paginated by default. Pagination must be reverse chronological.\n\nOnly support cursor or time based pagination.\n\n#### Defaults\n\n`limit=50`\n`after=NEWEST_RESOURCE`\n`before=null`\n\n#### Limits\n\n`limit=500`\n\nParameters:\n\n| Name     |  Type  |        Description |\n| -------- | :----: | -----------------: |\n| `after`  | string |  id to start after |\n| `before` | string | id to start before |\n| `limit`  | string |  number of records |\n\n### Response\n\nPaginated results are always enveloped:\n\n```\n{\n  \"meta\": {\n    \"cursors\": {\n      \"after\": \"abcd1234\",\n      \"before\": \"wxyz0987\"\n    },\n    \"limit\": 50\n  },\n  \"payments\": [{\n    …\n  },\n  …]\n}\n```\n\n## Updates\n\nFull or partial updates using `PUT` should replace any parameters passed and ignore fields not submitted.\n\n```\nGET /items/id_123\n{\n  \"id\": \"id_123\",\n  \"meta\": {\n    \"created\": \"date\",\n    \"published\": false\n  }\n}\n```\n\n```\nPUT /items/id_123 { \"meta\": { \"published\": true } }\n{\n  \"id\": \"id_123\",\n  \"meta\": {\n    \"published\": false\n  }\n}\n```\n\n### PATCH Updates\n\nPATCH is reserved for [JSON Patch](http://jsonapi.org/format/#patch) operations.\n\n## JSON encode POST, PUT \u0026 PATCH bodies\n\n`POST`, `PUT` and `PATCH` expect JSON bodies in the request. `Content-Type` header MUST be set to `application/json`.\nFor unsupported media types a `415` (Unsupported Media Type) response code is returned.\n\n## Caching\n\nMost responses return an `ETag` header. Many responses also return a `Last-Modified` header. The\nvalues of these headers can be used to make subsequent requests to those resources using the\n`If-None-Match` and `If-Modified-Since` headers, respectively. If the resource has not changed, the\nserver will return a `304 Not Modified`. Note that making a conditional request and receiving a 304\nresponse does _not_ count against your rate limit, so we encourage you to use it whenever possible.\n\n`Cache-Control: private, max-age=60`\n`ETag: \u003chash of contents\u003e`\n`Last-Modified: updated_at`\n\n#### Vary header\n\nThe following header values must be declared in the Vary header: `Accept`, `Authorization` and `Cookie`.\n\nAny of these headers can change the representation of the data and should invalidate a cached\nversion. This can be useful if users have different accounts to do admin, each with different\nprivileges and resource visibility.\n\nReference: https://www.mnot.net/cache_docs/\n\n## Compression\n\nAll responses should support gzip.\n\n## Result filtering, sorting \u0026 searching\n\nSee JSON-API: http://jsonapi.org/format/#fetching-filtering\n\n## Pretty printed responses\n\nJSON responses should be pretty printed.\n\n## Time zone/dates\n\nExplicitly provide an ISO8601 timestamp with timezone information (DateTime in UTC).\nUse the exact timestamp for API calls that allow a timestamp to be specified.\nThese timestamps look something like `2014-02-27T15:05:06+01:00`. ISO 8601 UTC format: YYYY-MM-DDTHH:MM:SSZ.\n\n## HTTP rate limiting\n\nAll endpoints must be rate limited. The current rate limit status is returned in the HTTP headers of\nall API requests.\n\n```http\nRate-Limit-Limit: 5000\nRate-Limit-Remaining: 4994\nRate-Limit-Reset: Thu, 01 Dec 1994 16:00:00 GMT\nContent-Type: application/json; charset=utf-8\nConnection: keep-alive\nRetry-After: Thu, 01 May 2014 16:00:00 GMT\n\nRateLimit-Reset uses the HTTP header date format: RFC 1123 (Thu, 01 Dec 1994 16:00:00 GMT)\n```\n\nExceeding rate limit:\n\n```http\n// 429 Too Many Requests\n{\n    \"message\": \"API rate limit exceeded.\",\n    \"type\": \"rate_limit_exceeded\",\n    \"documentation_url\": \"http://developer.gocardless.com/#rate_limit_exceeded\"\n}\n```\n\n## CORS\n\nSupport Cross Origin Resource Sharing (CORS) for AJAX requests.\n\nResources:\n\n- [CORS W3C working draft](https://www.w3.org/TR/cors/)\n- [HTML5 Rocks](http://www.html5rocks.com/en/tutorials/cors/)\n\nAny domain that is registered against the requesting account is accepted.\n\n```http\n$ curl -i https://api.gocardless.com -H \"Origin: http://dvla.com\"\nHTTP/1.1 302 Found\nAccess-Control-Allow-Origin: *\nAccess-Control-Expose-Headers: ETag, Link, RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset, OAuth-Scopes, Accepted-OAuth-Scopes\nAccess-Control-Allow-Credentials: false\n\n// CORS Preflight request\n// OPTIONS 200\nAccess-Control-Allow-Origin: *\nAccess-Control-Allow-Headers: Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, Requested-With\nAccess-Control-Allow-Methods: GET, POST, PATCH, PUT, DELETE\nAccess-Control-Expose-Headers: ETag, RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset\nAccess-Control-Max-Age: 86400\nAccess-Control-Allow-Credentials: false\n```\n\n## TLS/SSL\n\nAll API request MUST be made over SSL, including outgoing web hooks. Any non-secure requests\nreturn `ssl_required`, and no redirects are performed.\n\n```http\nHTTP/1.1 403 Forbidden\nContent-Length: 35\n\n{\n  \"message\": \"API requests must be made over HTTPS\",\n  \"type\": \"ssl_required\",\n  \"docs\": \"https://developer.gocardless.com/errors#ssl_required\"\n}\n```\n\n## Include related resource representations\n\nSee JSON-API: http://jsonapi.org/format/#fetching-includes\n\n## Limit fields in response\n\nSee JSON-API: http://jsonapi.org/format/#fetching-sparse-fieldsets\n\n## Unique request identifiers\n\nSet a `Request-Id` header to aid debugging across services.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgocardless%2Fhttp-api-design","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgocardless%2Fhttp-api-design","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgocardless%2Fhttp-api-design/lists"}