<?xml version="1.0" encoding="utf-8"?>
<feed xml:lang="en-us" xmlns="http://www.w3.org/2005/Atom"><title>Simon Willison's Weblog: api-design</title><link href="http://simonwillison.net/" rel="alternate"/><link href="http://simonwillison.net/tags/api-design.atom" rel="self"/><id>http://simonwillison.net/</id><updated>2026-01-04T16:40:39+00:00</updated><author><name>Simon Willison</name></author><entry><title>Quoting Addy Osmani</title><link href="https://simonwillison.net/2026/Jan/4/addy-osmani/#atom-tag" rel="alternate"/><published>2026-01-04T16:40:39+00:00</published><updated>2026-01-04T16:40:39+00:00</updated><id>https://simonwillison.net/2026/Jan/4/addy-osmani/#atom-tag</id><summary type="html">
    &lt;blockquote cite="https://addyosmani.com/blog/21-lessons/"&gt;&lt;p&gt;With enough users, every observable behavior becomes a dependency - regardless of what you promised. Someone is scraping your API, automating your quirks, caching your bugs.&lt;/p&gt;
&lt;p&gt;This creates a career-level insight: you can’t treat compatibility work as “maintenance” and new features as “real work.” Compatibility is product.&lt;/p&gt;
&lt;p&gt;Design your deprecations as migrations with time, tooling, and empathy. Most “API design” is actually “API retirement.”&lt;/p&gt;&lt;/blockquote&gt;
&lt;p class="cite"&gt;&amp;mdash; &lt;a href="https://addyosmani.com/blog/21-lessons/"&gt;Addy Osmani&lt;/a&gt;, 21 lessons from 14 years at Google&lt;/p&gt;

    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/api-design"&gt;api-design&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/google"&gt;google&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/careers"&gt;careers&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/addy-osmani"&gt;addy-osmani&lt;/a&gt;&lt;/p&gt;



</summary><category term="api-design"/><category term="google"/><category term="careers"/><category term="addy-osmani"/></entry><entry><title>Jiff</title><link href="https://simonwillison.net/2024/Jul/22/jiff/#atom-tag" rel="alternate"/><published>2024-07-22T04:48:35+00:00</published><updated>2024-07-22T04:48:35+00:00</updated><id>https://simonwillison.net/2024/Jul/22/jiff/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/BurntSushi/jiff"&gt;Jiff&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Andrew Gallant (aka BurntSushi) implemented &lt;a href="https://github.com/rust-lang/regex"&gt;regex&lt;/a&gt; for Rust and built the fabulous &lt;a href="https://github.com/BurntSushi/ripgrep"&gt;ripgrep&lt;/a&gt;, so it's worth paying attention to their new projects.&lt;/p&gt;
&lt;p&gt;Jiff is a brand new datetime library for Rust which focuses on "providing high level datetime primitives that are difficult to misuse and have reasonable performance". The API design is heavily inspired by the &lt;a href="https://tc39.es/proposal-temporal/docs/index.html"&gt;Temporal&lt;/a&gt; proposal for JavaScript.&lt;/p&gt;
&lt;p&gt;The core type provided by Jiff is &lt;code&gt;Zoned&lt;/code&gt;, best imagine as a 96-bit integer nanosecond time since the Unix each combined with a geographic region timezone and a civil/local calendar date and clock time.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://docs.rs/jiff/latest/jiff/"&gt;documentation&lt;/a&gt; is comprehensive and a fascinating read if you're interested in API design and timezones.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://twitter.com/burntsushi5/status/1815196645392527519"&gt;@burntsushi5&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/api-design"&gt;api-design&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/timezones"&gt;timezones&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/rust"&gt;rust&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/andrew-gallant"&gt;andrew-gallant&lt;/a&gt;&lt;/p&gt;



</summary><category term="api-design"/><category term="timezones"/><category term="rust"/><category term="andrew-gallant"/></entry><entry><title>Designing a write API for Datasette</title><link href="https://simonwillison.net/2022/Nov/9/designing-a-write-api-for-datasette/#atom-tag" rel="alternate"/><published>2022-11-09T19:44:32+00:00</published><updated>2022-11-09T19:44:32+00:00</updated><id>https://simonwillison.net/2022/Nov/9/designing-a-write-api-for-datasette/#atom-tag</id><summary type="html">
    &lt;p&gt;Building out &lt;a href="https://datasette.cloud/"&gt;Datasette Cloud&lt;/a&gt; has made one thing clear to me: Datasette needs a write API for ingesting new data into its attached SQLite databases.&lt;/p&gt;
&lt;p&gt;I had originally thought that this could be left entirely to plugins: my &lt;a href="https://datasette.io/plugins/datasette-insert"&gt;datasette-insert&lt;/a&gt; plugin already provides a JSON API for inserting data, and other plugins like &lt;a href="https://datasette.io/plugins/datasette-upload-csvs"&gt;datasette-upload-csvs&lt;/a&gt; also implement data import functionality.&lt;/p&gt;
&lt;p&gt;But some things deserve to live in core. An API for manipulating data is one of them, because it can hopefully open up a floodgate of opportunities for other plugins and external applications to build on top of it.&lt;/p&gt;
&lt;p&gt;I've been working on this over the past two weeks, in between getting &lt;a href="https://simonwillison.net/2022/Nov/5/mastodon/"&gt;distracted by Mastodon&lt;/a&gt; (it's &lt;a href="https://simonwillison.net/2022/Nov/8/mastodon-is-just-blogs/"&gt;just blogs!&lt;/a&gt;).&lt;/p&gt;
&lt;h4&gt;Designing the API&lt;/h4&gt;
&lt;p&gt;You can follow my progress in this tracking issue: &lt;a href="https://github.com/simonw/datasette/issues/1850"&gt;Write API in Datasette core #1850&lt;/a&gt;. I'm building the new functionality in a branch (called &lt;a href="https://github.com/simonw/datasette/tree/1.0-dev"&gt;1.0-dev&lt;/a&gt;, because this is going to be one of the defining features of Datasette 1.0 - and will be previewed in alphas of that release).&lt;/p&gt;
&lt;p&gt;Here's the functionality I'm aiming for in the first alpha:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;API for writing new records (singular or plural) to a table&lt;/li&gt;
&lt;li&gt;API for updating an existing record&lt;/li&gt;
&lt;li&gt;API for deleting an existing record&lt;/li&gt;
&lt;li&gt;API for creating a new table - either with an explicit schema or by inferring it from a set of provided rows&lt;/li&gt;
&lt;li&gt;API for dropping a table&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I have a bunch of things I plan to add later, but I think the above represents a powerful, coherent set of initial functionality.&lt;/p&gt;
&lt;p&gt;In terms of building this, I have a secret weapon: &lt;a href="https://sqlite-utils.datasette.io/"&gt;sqlite-utils&lt;/a&gt;. It already has both a Python client library and a comprehensive CLI interface for inserting data and creating tables. I've evolved the design of those over multiple major versions, and I'm confident that they're solid. Datasette's write API will mostly implement the same patterns I've eventually settled on for sqlite-utils.&lt;/p&gt;
&lt;p&gt;I still need to design the higher level aspects of the API though - the endpoint URLs and the JSON format that will be used.&lt;/p&gt;
&lt;p&gt;This is still in flux, but my current design looks like this.&lt;/p&gt;
&lt;p&gt;To &lt;a href="https://github.com/simonw/datasette/issues/1866"&gt;insert records&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;POST /database/table/-/insert
&lt;/code&gt;&lt;/pre&gt;
&lt;div class="highlight highlight-source-json"&gt;&lt;pre&gt;{
    &lt;span class="pl-ent"&gt;"rows"&lt;/span&gt;: [
        {&lt;span class="pl-ent"&gt;"id"&lt;/span&gt;: &lt;span class="pl-c1"&gt;1&lt;/span&gt;, &lt;span class="pl-ent"&gt;"name"&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;Simon&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;},
        {&lt;span class="pl-ent"&gt;"id"&lt;/span&gt;: &lt;span class="pl-c1"&gt;2&lt;/span&gt;, &lt;span class="pl-ent"&gt;"name"&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;Cleo&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;}
    ]
}&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Or use &lt;code&gt;"row": {...}&lt;/code&gt; to insert a single row.&lt;/p&gt;
&lt;p&gt;To &lt;a href="https://github.com/simonw/datasette/issues/1882"&gt;create a new table&lt;/a&gt; with an explicit schema:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;POST /database/-/create
&lt;/code&gt;&lt;/pre&gt;
&lt;div class="highlight highlight-source-json"&gt;&lt;pre&gt;{
    &lt;span class="pl-ent"&gt;"name"&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;people&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;,
    &lt;span class="pl-ent"&gt;"columns"&lt;/span&gt;: [
        {
            &lt;span class="pl-ent"&gt;"name"&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;id&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;,
            &lt;span class="pl-ent"&gt;"type"&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;integer&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;
        },
        {
            &lt;span class="pl-ent"&gt;"name"&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;title&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;,
            &lt;span class="pl-ent"&gt;"type"&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;text&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;
        }
    ]
   &lt;span class="pl-ent"&gt;"pk"&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;id&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;
}&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To create a new table with a schema automatically derived from some initial rows:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;POST /database/-/create
&lt;/code&gt;&lt;/pre&gt;
&lt;div class="highlight highlight-source-json"&gt;&lt;pre&gt;{
    &lt;span class="pl-ent"&gt;"name"&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;my new table&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;,
    &lt;span class="pl-ent"&gt;"rows"&lt;/span&gt;: [
        {&lt;span class="pl-ent"&gt;"id"&lt;/span&gt;: &lt;span class="pl-c1"&gt;1&lt;/span&gt;, &lt;span class="pl-ent"&gt;"name"&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;Simon&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;},
        {&lt;span class="pl-ent"&gt;"id"&lt;/span&gt;: &lt;span class="pl-c1"&gt;2&lt;/span&gt;, &lt;span class="pl-ent"&gt;"name"&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;Cleo&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;}
    ]
   &lt;span class="pl-ent"&gt;"pk"&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;id&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;
}&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To &lt;a href="https://github.com/simonw/datasette/issues/1863"&gt;update a record&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;POST /database/table/134/-/update
&lt;/code&gt;&lt;/pre&gt;
&lt;div class="highlight highlight-source-json"&gt;&lt;pre&gt;{
    &lt;span class="pl-ent"&gt;"update"&lt;/span&gt;: {
        &lt;span class="pl-ent"&gt;"name"&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;New name&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;
    }
}&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Where &lt;code&gt;134&lt;/code&gt; in the URL is the primary key of the record. Datasette supports compound primary keys too, so this could be &lt;code&gt;/database/docs/article,242/-/update&lt;/code&gt; for a table with a compound primary key.&lt;/p&gt;
&lt;p&gt;I'm using a &lt;code&gt;"update"&lt;/code&gt; nested object here rather than having everything at the root of the document because that frees me up to add extra future fields that control the update - &lt;code&gt;"alter": true&lt;/code&gt; to specify that the table schema should be updated to add new columns, for example.&lt;/p&gt;
&lt;p&gt;To &lt;a href="https://github.com/simonw/datasette/issues/1864"&gt;delete a record&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;POST /database/table/134/-/delete
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I thought about using the HTTP &lt;code&gt;DELETE&lt;/code&gt; verb here and I'm ready to be convinced that it's a good idea, but thinking back over my career I can't see any times where I've seen &lt;code&gt;DELETE&lt;/code&gt; offered a concrete benefit over just sticking with POST for this kind of thing.&lt;/p&gt;
&lt;p&gt;This isn't going to be a pure REST API, and I'm OK with that.&lt;/p&gt;
&lt;h4&gt;So many details&lt;/h4&gt;
&lt;p&gt;There are so many interesting details to consider here - especially given that Datasette is designed to support ANY schema that's possible in SQLite.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Should you be allowed to update the primary key of an existing record?&lt;/li&gt;
&lt;li&gt;What happens if you try to insert a record that violates a foreign key constraint?&lt;/li&gt;
&lt;li&gt;What happens if you try to insert a record that violates a unique constraint?&lt;/li&gt;
&lt;li&gt;How should inserting binary data work, given that JSON doesn't have a binary type?&lt;/li&gt;
&lt;li&gt;What permissions should the different API endpoints require (I'm looking to add a bunch of new ones)&lt;/li&gt;
&lt;li&gt;How should compound primary keys be treated?&lt;/li&gt;
&lt;li&gt;Should the API return a copy of the records that were just inserted? Initially I thought yes, but it turns out to be a big impact on insert speeds, at least in SQLite versions before the &lt;a href="https://www.sqlite.org/lang_returning.html"&gt;RETURNING clause&lt;/a&gt; was added in SQLite 3.35.0 (in March 2021, so not necessarily widely available yet).&lt;/li&gt;
&lt;li&gt;How should the interactive API explorer work? I've been &lt;a href="https://github.com/simonw/datasette/issues/1871"&gt;building that in this issue&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I'm working through these questions in the various issues attached to my tracking issue. If you have opinions to share you're welcome to join me there!&lt;/p&gt;
&lt;h4&gt;Token authentication&lt;/h4&gt;
&lt;p&gt;This is another area that I've previously left to plugins. &lt;a href="https://datasette.io/plugins/datasette-auth-tokens"&gt;datasette-auth-tokens&lt;/a&gt; adds &lt;code&gt;Authorization: Bearer xxx&lt;/code&gt; authentication to Datasette, but if there's a write API in core there really needs to be a default token authentication mechanism too.&lt;/p&gt;
&lt;p&gt;I've implemented a default mechanism based around generating signed tokens, described in &lt;a href="https://github.com/simonw/datasette/issues/1852"&gt;issue #1852&lt;/a&gt; and described in this &lt;a href="https://docs.datasette.io/en/1.0-dev/authentication.html#api-tokens"&gt;in-progress documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The basic idea is to support tokens that are signed JSON objects (similar to JWT but not JWT, because JWT is a &lt;a href="https://simonwillison.net/tags/jwt/"&gt;flawed standard&lt;/a&gt; - I rolled my own using &lt;a href="https://itsdangerous.palletsprojects.com/"&gt;itsdangerous&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;The signed content of a token looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
    "a": "user_id",
    "t": 1668022423,
    "d": 3600
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;"a"&lt;/code&gt; field captures the ID of the user created that token. The token can then inherit the permissions of that user.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;"t"&lt;/code&gt; field shows when the token was initially created.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;"d"&lt;/code&gt; field is optional, and indicates after how many seconds duration the token should expire. This allows for the creation of time-limited tokens.&lt;/p&gt;
&lt;p&gt;Tokens can be created using the new &lt;code&gt;/-/create-token&lt;/code&gt; page or the new &lt;code&gt;datasette create-token&lt;/code&gt; CLI command.&lt;/p&gt;
&lt;p&gt;It's important to note that this is not intended to be the &lt;em&gt;only&lt;/em&gt; way tokens work in Datasette. There are plenty of applications where database-backed tokens makes more sense, since it allows tokens to be revoked individually without rotating secrets and revoking every issued token at once. I plan to implement this pattern myself for Datasette Cloud.&lt;/p&gt;
&lt;p&gt;But I think this is a reasonable default scheme to include in Datasette core. It can even be turned off entirely using the new &lt;code&gt;--setting allow_signed_tokens off&lt;/code&gt; option.&lt;/p&gt;
&lt;p&gt;I'm also planning a variant of these tokens that can apply additional restrictions. Let's say you want to issue a token that acts as your user but is only allowed to insert rows into the &lt;code&gt;docs&lt;/code&gt; table in the &lt;code&gt;primary&lt;/code&gt; database. You'll be able to create a token that looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
    "a": "simonw",
    "t": 1668022423,
    "r": {
        "t": {
            "primary: {
                "docs": ["ir"]
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;"r"&lt;/code&gt; means restrictions. The &lt;code&gt;"t"&lt;/code&gt; key indicates per-table restrictions, and the &lt;code&gt;"ir"&lt;/code&gt; is an acronym for the &lt;code&gt;insert-row&lt;/code&gt; permission.&lt;/p&gt;
&lt;p&gt;I'm still fleshing out &lt;a href="https://github.com/simonw/datasette/issues/1855"&gt;how this will work&lt;/a&gt;, but it feels like an important feature of any permissions system. I find it frustrating any time I'm working with a a system that doesn't allow me to create scoped-down tokens.&lt;/p&gt;
&lt;h4&gt;Releases this week&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/json-flatten"&gt;json-flatten&lt;/a&gt;&lt;/strong&gt;: &lt;a href="https://github.com/simonw/json-flatten/releases/tag/0.3"&gt;0.3&lt;/a&gt; - (&lt;a href="https://github.com/simonw/json-flatten/releases"&gt;2 releases total&lt;/a&gt;) - 2022-10-29
&lt;br /&gt;Python functions for flattening a JSON object to a single dictionary of pairs, and unflattening that dictionary back to a JSON object&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/datasette-edit-templates"&gt;datasette-edit-templates&lt;/a&gt;&lt;/strong&gt;: &lt;a href="https://github.com/simonw/datasette-edit-templates/releases/tag/0.1"&gt;0.1&lt;/a&gt; - (&lt;a href="https://github.com/simonw/datasette-edit-templates/releases"&gt;2 releases total&lt;/a&gt;) - 2022-10-27
&lt;br /&gt;Plugin allowing Datasette templates to be edited within Datasette&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/datasette"&gt;datasette&lt;/a&gt;&lt;/strong&gt;: &lt;a href="https://github.com/simonw/datasette/releases/tag/0.63"&gt;0.63&lt;/a&gt; - (&lt;a href="https://github.com/simonw/datasette/releases"&gt;116 releases total&lt;/a&gt;) - 2022-10-27
&lt;br /&gt;An open source multi-tool for exploring and publishing data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/sqlite-utils"&gt;sqlite-utils&lt;/a&gt;&lt;/strong&gt;: &lt;a href="https://github.com/simonw/sqlite-utils/releases/tag/3.30"&gt;3.30&lt;/a&gt; - (&lt;a href="https://github.com/simonw/sqlite-utils/releases"&gt;104 releases total&lt;/a&gt;) - 2022-10-25
&lt;br /&gt;Python CLI utility and library for manipulating SQLite databases&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/datasette-indieauth"&gt;datasette-indieauth&lt;/a&gt;&lt;/strong&gt;: &lt;a href="https://github.com/simonw/datasette-indieauth/releases/tag/1.2.1"&gt;1.2.1&lt;/a&gt; - (&lt;a href="https://github.com/simonw/datasette-indieauth/releases"&gt;10 releases total&lt;/a&gt;) - 2022-10-25
&lt;br /&gt;Datasette authentication using IndieAuth and RelMeAuth&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/shot-scraper"&gt;shot-scraper&lt;/a&gt;&lt;/strong&gt;: &lt;a href="https://github.com/simonw/shot-scraper/releases/tag/1.0.1"&gt;1.0.1&lt;/a&gt; - (&lt;a href="https://github.com/simonw/shot-scraper/releases"&gt;24 releases total&lt;/a&gt;) - 2022-10-24&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;TIL this week&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://til.simonwillison.net/python/os-remove-windows"&gt;os.remove() on Windows fails if the file is already open&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://til.simonwillison.net/sqlite/sqlite-version-websql-chrome"&gt;Finding the SQLite version used by Web SQL in Chrome&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://til.simonwillison.net/git/git-bisect"&gt;git bisect&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://til.simonwillison.net/python/pdb-interact"&gt;The pdb interact command&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://til.simonwillison.net/github/github-pages"&gt;GitHub Pages: The Missing Manual&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://til.simonwillison.net/mastodon/custom-domain-mastodon"&gt;Getting Mastodon running on a custom domain&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://til.simonwillison.net/mastodon/export-timeline-to-sqlite"&gt;Export a Mastodon timeline to SQLite&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/api-design"&gt;api-design&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/projects"&gt;projects&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/weeknotes"&gt;weeknotes&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette-cloud"&gt;datasette-cloud&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sqlite-utils"&gt;sqlite-utils&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="api-design"/><category term="projects"/><category term="datasette"/><category term="weeknotes"/><category term="datasette-cloud"/><category term="sqlite-utils"/></entry><entry><title>Quoting Hyrum's Law</title><link href="https://simonwillison.net/2019/Nov/21/hyrums-law/#atom-tag" rel="alternate"/><published>2019-11-21T22:45:55+00:00</published><updated>2019-11-21T22:45:55+00:00</updated><id>https://simonwillison.net/2019/Nov/21/hyrums-law/#atom-tag</id><summary type="html">
    &lt;blockquote cite="https://www.hyrumslaw.com/"&gt;&lt;p&gt;With a sufficient number of users of an API,
it does not matter what you promise in the contract:
all observable behaviors of your system
will be depended on by somebody.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p class="cite"&gt;&amp;mdash; &lt;a href="https://www.hyrumslaw.com/"&gt;Hyrum&amp;#x27;s Law&lt;/a&gt;&lt;/p&gt;

    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/api-design"&gt;api-design&lt;/a&gt;&lt;/p&gt;



</summary><category term="api-design"/></entry><entry><title>Quoting Hyrum's Law</title><link href="https://simonwillison.net/2018/Aug/11/hyrums-law/#atom-tag" rel="alternate"/><published>2018-08-11T00:33:29+00:00</published><updated>2018-08-11T00:33:29+00:00</updated><id>https://simonwillison.net/2018/Aug/11/hyrums-law/#atom-tag</id><summary type="html">
    &lt;blockquote cite="https://brandur.org/fragments/go-http2"&gt;&lt;p&gt;With a sufficient number of users of an API, it does not matter what you promise in the contract: all observable behaviors of your system will be depended on by somebody.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p class="cite"&gt;&amp;mdash; &lt;a href="https://brandur.org/fragments/go-http2"&gt;Hyrum&amp;#x27;s Law&lt;/a&gt;&lt;/p&gt;

    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/api-design"&gt;api-design&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/brandur-leach"&gt;brandur-leach&lt;/a&gt;&lt;/p&gt;



</summary><category term="api-design"/><category term="brandur-leach"/></entry><entry><title>Quoting Malte Ubl</title><link href="https://simonwillison.net/2018/Apr/15/malte-ubl/#atom-tag" rel="alternate"/><published>2018-04-15T17:23:18+00:00</published><updated>2018-04-15T17:23:18+00:00</updated><id>https://simonwillison.net/2018/Apr/15/malte-ubl/#atom-tag</id><summary type="html">
    &lt;blockquote cite="https://medium.com/@cramforce/designing-very-large-javascript-applications-6e013a3291a3"&gt;&lt;p&gt;The way I would talk about myself as a senior engineer is that I’d say “I know how I would solve the problem” and because I know how I would solve it I could also teach someone else to do it. And my theory is that the next level is that I can say about myself “I know how others would solve the problem”. Let’s make that a bit more concrete. You make that sentence: “I can anticipate how the API choices that I’m making, or the abstractions that I’m introducing into a project, how they impact how other people would solve a problem.”&lt;/p&gt;&lt;/blockquote&gt;
&lt;p class="cite"&gt;&amp;mdash; &lt;a href="https://medium.com/@cramforce/designing-very-large-javascript-applications-6e013a3291a3"&gt;Malte Ubl&lt;/a&gt;&lt;/p&gt;

    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/api-design"&gt;api-design&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/careers"&gt;careers&lt;/a&gt;&lt;/p&gt;



</summary><category term="api-design"/><category term="careers"/></entry><entry><title>Implementing Stripe-like Idempotency Keys in Postgres</title><link href="https://simonwillison.net/2017/Oct/27/stripe-like-idempotency-keys-in-postgres/#atom-tag" rel="alternate"/><published>2017-10-27T17:51:52+00:00</published><updated>2017-10-27T17:51:52+00:00</updated><id>https://simonwillison.net/2017/Oct/27/stripe-like-idempotency-keys-in-postgres/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://brandur.org/idempotency-keys"&gt;Implementing Stripe-like Idempotency Keys in Postgres&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Having clients send “idempotency keys” with API requests in order to be able to safely retry them if something’s goes wrong is a really neat trick for making transactional APIs more robust. Here Brandur Leach talks implementation strategies.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://news.ycombinator.com/item?id=15569478"&gt;Hacker News&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/api-design"&gt;api-design&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/idempotency"&gt;idempotency&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/postgresql"&gt;postgresql&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/brandur-leach"&gt;brandur-leach&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/stripe"&gt;stripe&lt;/a&gt;&lt;/p&gt;



</summary><category term="api-design"/><category term="idempotency"/><category term="postgresql"/><category term="brandur-leach"/><category term="stripe"/></entry><entry><title>jQuery 1.4.3 Released</title><link href="https://simonwillison.net/2010/Oct/17/jquery/#atom-tag" rel="alternate"/><published>2010-10-17T00:15:00+00:00</published><updated>2010-10-17T00:15:00+00:00</updated><id>https://simonwillison.net/2010/Oct/17/jquery/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://blog.jquery.com/2010/10/16/jquery-143-released/"&gt;jQuery 1.4.3 Released&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Once again, the thing that impresses me most about this jQuery release is how stable the core API is. Hardly any new methods added, but the existing methods are made faster, more flexible and more predictable. The same as been true for the past several releases as well. It just keeps getting more and more polished.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/api-design"&gt;api-design&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/javascript"&gt;javascript&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/jquery"&gt;jquery&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/recovered"&gt;recovered&lt;/a&gt;&lt;/p&gt;



</summary><category term="api-design"/><category term="javascript"/><category term="jquery"/><category term="recovered"/></entry><entry><title>Unimpressed by NodeIterator</title><link href="https://simonwillison.net/2009/Jun/19/nodeiterator/#atom-tag" rel="alternate"/><published>2009-06-19T21:53:58+00:00</published><updated>2009-06-19T21:53:58+00:00</updated><id>https://simonwillison.net/2009/Jun/19/nodeiterator/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://ejohn.org/blog/unimpressed-by-nodeiterator/"&gt;Unimpressed by NodeIterator&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
John Resig, one of the most talented API designers I’ve ever come across, posts some well earned criticism of the document.createNodeIterator DOM traversal API.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/api-design"&gt;api-design&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/dom"&gt;dom&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/javascript"&gt;javascript&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/john-resig"&gt;john-resig&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/nodeiterator"&gt;nodeiterator&lt;/a&gt;&lt;/p&gt;



</summary><category term="api-design"/><category term="dom"/><category term="javascript"/><category term="john-resig"/><category term="nodeiterator"/></entry><entry><title>The Little Manual of API Design (PDF)</title><link href="https://simonwillison.net/2009/May/18/apidesign/#atom-tag" rel="alternate"/><published>2009-05-18T10:14:24+00:00</published><updated>2009-05-18T10:14:24+00:00</updated><id>https://simonwillison.net/2009/May/18/apidesign/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://chaos.troll.no/~shausman/api-design/api-design.pdf"&gt;The Little Manual of API Design (PDF)&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
A concise, highly readable guide to designing APIs that are “Complete, Easy to learn and memorize, lead to readable code, hard to misuse, and easy to extend”, based on lessons learnt over many years of development of the Qt framework.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/api-design"&gt;api-design&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/apis"&gt;apis&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/programming"&gt;programming&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/qt"&gt;qt&lt;/a&gt;&lt;/p&gt;



</summary><category term="api-design"/><category term="apis"/><category term="programming"/><category term="qt"/></entry><entry><title>Building a JavaScript Library</title><link href="https://simonwillison.net/2007/Aug/24/building/#atom-tag" rel="alternate"/><published>2007-08-24T16:02:59+00:00</published><updated>2007-08-24T16:02:59+00:00</updated><id>https://simonwillison.net/2007/Aug/24/building/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://www.slideshare.net/jeresig/building-a-javascript-library"&gt;Building a JavaScript Library&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Slides from John Resig’s Google Tech Talk. Some great tips in here, including: make your APIs orthogonal, look for common patterns, keep things extensible and write the documentation yourself.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/api-design"&gt;api-design&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/googletechtalk"&gt;googletechtalk&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/javascript"&gt;javascript&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/john-resig"&gt;john-resig&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/jquery"&gt;jquery&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/libraries"&gt;libraries&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/slideshare"&gt;slideshare&lt;/a&gt;&lt;/p&gt;



</summary><category term="api-design"/><category term="googletechtalk"/><category term="javascript"/><category term="john-resig"/><category term="jquery"/><category term="libraries"/><category term="slideshare"/></entry></feed>