{
"$type": "site.standard.document",
"bskyPostRef": {
"cid": "bafyreibftm4nmgxrpi5wgomk5boaj2phjry7p3tfebesxznezx4fvdwdmm",
"uri": "at://did:plc:4jrrhj35kg4jynugbk4lo7mt/app.bsky.feed.post/3l6ncivarjaq2"
},
"path": "/discovering-features-with-http-options/",
"publishedAt": "2026-05-21T09:56:17.010Z",
"site": "https://evertpot.com",
"tags": [
"OPTIONS",
"CORS",
"Allow",
"Accept",
"Accept-Encoding",
"Accept-Patch",
"Accept-Post",
"Accept-Query",
"PATCH",
"POST",
"QUERY",
"JSON Patch",
"JSON Merge Patch",
"read my other article",
"service-doc",
"service-desc",
"IANA link relationship types",
"Web linking",
"mimetypes",
"This post on Mastodon",
"This post on Bluesky",
"Webmention"
],
"textContent": "Say you have an API, and you want to communicate what sort of things a user can do on a specific endpoint. You can use external description formats like OpenAPI or JSON Schema, but sometimes it’s nice to also dynamically communicate this on the API itself.\n\nOPTIONS is the method used for that. You may know this HTTP method from CORS, but it’s general purpose is for clients to passively find out ‘What can I do here?’.\n\nAll HTTP clients typically support making `OPTIONS` request. For example with `fetch()`:\n\n\n const response = await fetch( 'https://example.org', {method: 'OPTIONS'} );\n\nA basic `OPTIONS` response might might look like this:\n\n\n HTTP/1.1 204 No Content Date: Mon, 23 Sep 2024 02:57:38 GMT Server: KKachel/1.2 Allow: GET, PUT, POST, DELETE, OPTIONS\n\nBased on the Allow header you can quickly tell which HTTP methods are available at a given endpoint. Many web frameworks emit this automatically and generate the list of methods dynamically per route, so chances are that you get this one for free.\n\nTo find out if your server does, try running the command below (with your URL!):\n\n\n curl -X OPTIONS http://localhost:3000/some/endpoint/\n\nOne nice thing you could do with the `Allow` header, is that you could also communicate access-control information on a very basic level. For example, you could only include `DELETE` and `PUT` if a user has write access to a resource.\n\n## Accept and Accept-Encoding\n\nThere’s server other standard headers for discovery. Here’s an example showing a few at once:\n\n\n HTTP/1.1 204 No Content Date: Mon, 23 Sep 2024 02:57:38 GMT Server: KKachel/1.2 Allow: GET, PUT, POST, DELETE, OPTIONS Accept: application/vnd.my-company-api+json, application/json, text/html Accept-Encoding: gzip,brotli,identity\n\nYou may already be familiar with Accept and Accept-Encoding from HTTP requests, but they can also appear in responses. `Accept` in a response lets you tell the client which kind of mimetypes are available at an endpoint. I like adding `text/html` to every JSON api endpoint and making sure that API urls can be opened in browsers and shared between devs for easy debugging.\n\nThe `Accept-Encoding` lets a client know in this case that they can compress their request bodies with either `gzip` or `brotli` (`identity` means no compression).\n\n## Patching, posting and querying\n\n3 other headers that can be used are Accept-Patch, Accept-Post and Accept-Query. These three headers are used to tell a client what content-types are available for the PATCH, POST and QUERY http methods respectively.\n\nFor all of these headers, their values effectively dictate what valid values are for the `Content-Type` header when making the request.\n\n\n HTTP/1.1 204 No Content Date: Mon, 23 Sep 2024 02:57:38 GMT Server: KKachel/1.2 Allow: OPTIONS, QUERY, POST, PATCH Accept-Patch: application/json-patch+json, application/merge-patch+json Accept-Query: application/graphql Accept-Post: multipart/form-data, application/vnd.custom.rpc+json\n\nIn the above response, the server indicates it supports both JSON Patch and JSON Merge Patch content-types in `PATCH` requests. It also suggests that GraphQL can be used via the `QUERY` method, and for `POST` it supports both standard file uploads and some custom JSON-based format.\n\nTypically you wouldn’t find all of these at the same endpoint, but I wanted to show a few examples together.\n\n## Where’s PUT?\n\nOddly, there’s no specific header for `PUT` requests. Arguably you could say that `GET` and `PUT` are symmetrical, so perhaps the `Accept` header kind of extends to both. But the spec is not clear on this.\n\nI think the actual reality is that `Accept-Patch` was the first header in this category that really clearly defined this as a means of feature discovery on `OPTIONS`. `Accept-Post` and `Accept-Query` followed suit. I think `Accept-Patch` in `OPTIONS` was modelled after in-the-wild usage of `Accept` in `OPTIONS`, even though the HTTP specific doesn’t super clearly define this.\n\nIf I’m wrong with my interpretation here, I would love to know!\n\n_Aside: If you’re wondering about`DELETE`, `DELETE` should never have a body, so all a user would need to know is _can_ they delete, which you can see in the `Allow` header. If this is new to you to, read my other article about `GET` request bodies. Most of the information there is applicable to `DELETE` as well. _\n\n## Linking to documentation\n\nThe `OPTIONS` response is also a great place to tell users where to find additional documentation. In the below example, I included both a machine-readable link to a documentation site, a link to an OpenAPI definition, and a message intended for humans in the response body:\n\n\n HTTP/1.1 200 OK Date: Mon, 23 Sep 2024 04:45:38 GMT Allow: GET, QUERY, OPTIONS Link: <https://docs.example.org/api/some-endpoint>; rel=\"service-doc\" Link: <https://api.example.org/openapi.yml>; rel=\"service-desc\" type=\"application/openapi+yaml\" Content-Type: text/plain Hey there! Thanks for checking out this API. You can find the docs for this specific endpoint at: https://docs.example.org/api/some-endpoint Cheers, The dev team\n\nI recommend keeping the response body as mostly informal and minimal any real information should probably just live on its own URL and be linked to.\n\nI used the service-doc and service-desc link relationships here, but you can of course use any of the IANA link relationship types here or a custom one. Also see the Web linking spec for more info.\n\n## Obscure uses\n\n### WebDAV usage\n\nWebDAV, CalDAV and CardDAV also use OPTIONS for feature discovery. For example:\n\n\n HTTP/1.1 204 No Content Date: Mon, 23 Sep 2024 05:01:50 GMT Allow: GET, PROPFIND, ACL, PROPPATCH, MKCOL, LOCK, UNLOCK DAV: 1, 2, 3, access-control, addressbook, calendar-access\n\n### The server-wide asterisk request\n\nNormally HTTP requests are made to a path on the server, and the first line looks a bit like the following in HTTP/1.1:\n\n\n GET /path HTTP/1.1\n\nBut, there are a few other “request line” formats that are rarely used. One of them lets you discover features available on an entire server, using the asterisk:\n\n\n OPTIONS * HTTP/1.1\n\nThe asterisk here is not a path. Normally asterisks aren’t even allowed in URIs. Many HTTP clients (including `fetch()`) don’t even support this request.\n\nClassic webservers like Apache and Nginx should support this. To try it out, use CURL\n\n\n curl -vX OPTIONS --request-target '*' http://example.org\n\n## Final notes\n\nIf you have a reason to allow clients to discover features on an endpoint, consider using `OPTIONS` instead of a proprietary approach! As you can see in many of these examples, it’s especially useful if you use mimetypes well.\n\nIf you have questions, other novel uses of `OPTIONS` or other ideas around feature discovery, you can respond via:\n\n * This post on Mastodon\n * This post on Bluesky\n * Via the Webmention protocol!\n\n",
"title": "Discovering features using HTTP OPTIONS",
"updatedAt": "2024-10-16T13:44:00.000Z"
}