, 43 tweets, 8 min read
My Authors
Read all threads
there's a lot to like here, I.. might live tweet as I go through it
Section 5: Taxonomy: Faults vs. Errors – I appreciate how they've separated out the two classes of errors (faults affect availability, errors do not.) Also, the metrics recommendations here are spot-on re: latency vs overall time-to-complete long-running operations.
Section 6: client guidance – A+, they've given clear names to these rules of thumb:
- "ignore rule": clients ignore extra content they don't understand
- "variable order": clients shouldn't rely on a particular order within objs
- & "silent fail" - you can lead a server to water,
7.3 has a nice rule requiring friendly urls (like github's "/user" endpoint) to have a corresponding unique stable identifier ("/user/chrisdickinson", say, in GitHub's case.)
7.4.3 notes that PATCH can be used as UPSERT; which is new to me but I'm not opposed! The given rationale is that PUT requires a full representation of the object, which clients may not be prepared to provide.
I love, love, love 7.4.4, so much so that I'm going to link to it: github.com/microsoft/api-…

Adding a `Link: <{url}>, rel="help"` response header to point to docs is SO GOOD
Buried gem in 7.5: "Prefer: minimal|representation" request header: this lets client operations that request creation or update operations specify whether they need the resulting object returned from the server. "minimal" means "I'm okay without the data, thanks."
TIME TRAVEL because this reminded me I missed commenting on 7.4.1: always return a 201 with a "Location" header pointing at the canonical URL for resources newly created with POST (that were not explicitly named in the process of creation)
(i.e., if you POST /frobnicators, and that creates /frobnicators/10102, the server should respond to the initial POST with a "Location: /frobnicators/10102" header.)
OK, back to present day and date. "Preference-Applied" means the server received and respected the client's "Prefer" request header. Smart! This way the client knows it got the representation it preferred; this upholds the "silent failure" rule of client-sent preferences.
7.8 has useful insight: [if you're a browser-facing service,] headers pose a challenge for some clients, who may be subject to strict CORS rules. As a result, they stipulate that all custom req headers must be able to be put into query params. (Std headers MAY be query-paramable)
7.9: Don't put personally identifiable information (PII) into URL parameters! Use headers! GOOD ADVICE.

Explicitly mentioned: comply with your org's privacy policy.
Implicitly recommended: HAVE A PRIVACY POLICY. Decide what info is sensitive UP FRONT.
7.10 makes the WILD, ABSOLUTELY WILD recommendation that JSON should be the default response content type. (A-hem, old versions of restify? Ok ok, boltzmann is guilty here too.)

Also: camelCase attributes. Good to have a recommendation here.
7.10.2: Error condition responses.

As expected, there is a fairly meaty treatise on how to error.

There's a lot of good stuff here: github.com/microsoft/api-…

Breaking it down a bit:
1. Errors must have a single key, "error", pointing to an error obj at top level.
2. Errors should have a human-readable, dev-targeted message.
3. Errors should have a string "code", limited to about 20 possibilities.
4. Errors may have a "target" prop saying what input was wrong
5. IMPORTANTLY, errors may nest "inner errors", which can further nest inner errors. Inner errors have codes but aren't required to have much else. This is not a stack trace so much as it is a higher-level causality chain. "You have bad input because your password was reused."
Codes added at innerError level are not breaking changes. Clients are expected to only handle the set of top-level error codes. That means that adding a new top-level error class is a breaking API change. This is smart.
Another nice detail here is that toplevel errors may contain "details", which may contain a list of inner errors, since sometimes things go _horribly_ wrong and you end up with multiple reasons why a request failed.
Section 8 is all about CORS – pretty straightforward, solid advice. TIL that any client POST with a content-type that's not text/plain, urlencoded, or multipart will incur a preflight (even without cookies.)
(They refer to sending a "dynamic canary" along with any cookie-based requests, which is a cross-site request forgery protection header, IIUC. Please correct me if I'm wrong!)
Section 9, "Collections," is where they zig and I zag, a bit.

They follow the "{serviceRoot}/{collection}/{id}" pattern - e.g., "sprockets․biz/v1.0/cogs/10" – "cogs" are the collection, "10" is the id.

(I personally like to nest further: "/cogs/cog/10", but w/e!)
Section 9 also gets into how to filter, specs on how to drive pagination from both the server and the client – I like the former, but the latter can be useful in certain cases! They call out std query tokens with a leading $, which is a Good Idea.
They touch on creating items in a collection (POST /frobnicators and a note about PUT putting the id in the client's control), but haven't gotten into how PATCH upsert works in that model.

Wishing for a bestiary of tokens & JSON props. Maybe later in the doc!
Taking a little break for lunch. This is definitely one of the most complete sets of REST guidelines I've seen. More after I've eaten!
OK, kicking back off. The sorting and filtering and filtering query param language in 9.6 and 9.7 is nice, the most important thing being that it's standardized; there's some interesting nuggets of wisdom behind pagination in 9.8.3...
9.8.3, re "missing or repeated results" – creation and deletion of other resources may or may not show up in your paginated collection, IOW. This carves out space for a really handy implementation pattern: materializing an entire collection and caching it
I.e., a client searches for "/cats?type=seal point", and gets back a paginated list. Internally, the server might materialize all 100k rows (in a minimal form) and shove 'em into a cache temporarily, while the client navigates pages from that cache.
Section 10 has a thoughtful design for delta links, for watching a collection change over time.

Section 11 has various safe handling recommendations for JSON, with some novel rules around Date that are worth checking out: github.com/microsoft/api-…
Section 12, versioning describes putting the major version in the url path (my fav) or as a query parameter; then talks about "group versioning" which is date-based and neat and I haven't seen it outside of Stripe's API.

(Notably absent: the Accept-Version req header.)
Ooh, Section 13 on long running operations:

"The state of the system SHOULD be discoverable and testable at all times. Clients SHOULD be able to determine the system state even if the operation tracking resource is no longer active."

yes, yes, yes.
(my rule of thumb here is: "assuming privileged network access, how would you test this system from a bash script using curl? could you?")

(alright alright, it doesn't have to be bash. bash is just a forcing function for simplification here.)
Oh, I really like this section (13.2) on stepwise long-running operations. TL;DR: expose a top-level "/operations" collection enumerating all operations: pending, running, completed.

Creating a resource may return an Operation-Location header in addition to a Location header.
This Operation-Location points to a operation within the /operations collection, suitable for polling.

They go on to give advice on setting up a push notification flow as well, but I love the idea of layering that on top of an existing visibility mechanism
"As a rule of thumb any API call that's expected to take >0.5s in the 99th percentile, should consider using the Long-running Operations pattern for those calls."

"Services should respond quickly with an error when they are overloaded, rather than simply respond slowly."

YES
(I've started section 14 on throttling, quotas, and limits, btw. 14.1, "Principles", which I've just quoted, is a great list of 'how not to burn your hand on a hot stove'.)

It continues by drawing distinctions between 429 "client can fix it" and 503 "out of client control" resps
The stick here is that if the client doesn't respect Retry-After from a 429, they're liable to get blocked by DoS protection.

(Sidenote: it's surprising that they go for a quota over a window of time, vs. something continuous like token-bucket ratelimiting!)
Whew, section 15 is a guide to how to set up two different kinds of webhooks. It's very good! It's also a bit much to cover here. (tbh, it might be best as a standalone doc or appendix, though it's important enough to get right that including it in the mainline doc makes sense.)
Section 16 is (more) good advice: the server should error loudly when the client requests a feature the server doesn't support. (Otherwise, how would the client know the server doesn't support the feature?)
Section 17 covers naming. 17.7:

"For the overall name of a resource typically shown to users, services MUST use the property name 'displayName'."

Yes, yes, yes. Always make it clear _which_ name you want displayed in UI, or else you'll end up with identifiers looped through UI.
I like the common property list in 17.9. Using common names consistently helps folks move faster when using your APIs, and giving authors a ready list to pull from encourages use. A win-win.

(I'd love a list of well-known sigil-names here, too – ﹫next, ﹩filter, etc)
OK. Wrapping up. This is a really useful doc, @SpaceTrainz and co. did great work!
A closing thought: I'd probably use this as a springboard for an internal set of guidelines rather than verbatim. It does a great job of exposing decision points and giving you good default answers.

However, your team has to read it, agree to it, and walk the walk.
It's easier to approach (& more considerate to your teammates, I think!) in a stone soup approach. "These are the decisions we need to make, this is where MS landed, how do we feel about them" involves them.

Once you get buy-in, then build tooling around _that_.
Missing some Tweet in this thread? You can try to force a refresh.

Enjoying this thread?

Keep Current with Chris Dickinson

Profile picture

Stay in touch and get notified when new unrolls are available from this author!

Read all threads

This Thread may be Removed Anytime!

Twitter may remove this content at anytime, convert it as a PDF, save and print for later use!

Try unrolling a thread yourself!

how to unroll video

1) Follow Thread Reader App on Twitter so you can easily mention us!

2) Go to a Twitter thread (series of Tweets by the same owner) and mention us with a keyword "unroll" @threadreaderapp unroll

You can practice here first or read more on our help page!

Follow Us on Twitter!

Did Thread Reader help you today?

Support us! We are indie developers!


This site is made by just three indie developers on a laptop doing marketing, support and development! Read more about the story.

Become a Premium Member ($3.00/month or $30.00/year) and get exclusive features!

Become Premium

Too expensive? Make a small donation by buying us coffee ($5) or help with server cost ($10)

Donate via Paypal Become our Patreon

Thank you for your support!