skapi-js 2.0.0 reached npm on September 4. In the twenty days after it we published more releases. This is a short account of the ones that matter, and of why the work did not stop when the big version shipped.

A release is where the work starts
There is a comfortable story about major versions. You build for months, you ship, and then you rest. We have never managed to believe it.
The day 2.0 went out, it met real projects, and real projects found the things no test suite would have. A project in our newest region could not connect. A newsletter subscription could be created and then never read back. A request timing told you how long your queue was, when what you wanted to know was how slow the destination had been. None of that is glamorous. All of it is the difference between a backend that works in a demo and one you can build a business on.
So we kept going. Here is the shape of those twenty days.
| Version | Published | What it brought |
|---|---|---|
| 2.0.1 | September 7 | Projects in the newest regions connect |
| 2.0.2 | September 7 | Several newsletters in one project |
| 2.0.3 and 2.0.5 | September 9 and 10 | Request timing that measures the call, not the queue |
| 2.1.0 | September 20 | forwardRequest(), safer logins, stricter admin rules |
| 2.2.0 | September 22 | Tickets as a general webhook handler |
| 2.2.2 | September 24 | Push notifications to subscribers, getUsers() by a list of any attribute |
We are not going to walk through every line. The version history does that, entry by entry, and it is the page to read before you upgrade. What follows is the part we would tell you over coffee.
One method to call any API
Calling a third-party API from a browser has one hard problem: the key. Put it in your front end and it belongs to everyone who opens the developer tools. 2.1.0 gave that problem one method.
skapi.forwardRequest({ report: 'quarterly' }, {
secretName: 'my_secret',
url: 'https://api.example.com/v1/report',
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer $CLIENT_SECRET'
}
}).then(res => console.log(res));
The key is filled in on the server, so it never reaches the page. The first argument is the form itself: a submit event, a form element, a FormData, a plain object. With multipart: true the form travels byte for byte, files included. PATCH and HEAD joined the methods it speaks, and a destination learns who is calling only when you ask for it with skapiHeaders.
It replaces clientSecretRequest() and the whole family around it, and it is the same machinery underneath: the queue, the polling, the streaming and the history are shared. Every old name still works. Nothing you wrote against 2.0 stops running. See Forwarding Requests.
Webhooks without a server
A ticket used to be a narrow thing. As of 2.2.0 it is a general webhook handler: an address that accepts a request from your payment provider, a link in an email, or a signed-in user, checks it against conditions you set, and runs a chain of actions on your project.
The conditions now follow one rule in every part. A signature is verified with a secret key you name. Every value an action uses is written as a reference, such as ${data[order][id]}, and everything outside a reference is literal, so a ticket reads the way it behaves. An action can call another API with a stored key and act on what comes back.
That is an order confirmed, a record written and a user upgraded, with no server of yours in between. See Registering a Ticket.
Your users hear about it
Subscriptions between users have been in Skapi for a long time. In 2.2.1 they learned to speak up. A record posted with notify_subscribers sends a push notification to the uploader's subscribers, with the text you choose:
skapi.postRecord({ title: 'New post' }, {
table: {
name: 'Posts',
access_group: 'authorized',
subscription: { notify_subscribers: true }
},
notification: {
title: 'User A posted',
body: 'Hello subscribers!'
}
});
A comment on a post can notify too. Only subscribers who are allowed to read the record are told, a private record never notifies anyone, and each subscriber decides for themselves with get_notified. We also fixed what that uncovered: calling subscribe() again now changes only the options you pass, where it used to switch the others off. See Notifications.
Several newsletters, one project
2.0.2 added named newsletter groups. One project can run a product list next to a launch list, each with its own subscribers, its own sending address and its own history, and a subscriber of one never receives the other. We wrote about what that makes possible in Newsletters on Skapi.
Stricter where it counts
Some of the most important work of these weeks is the kind you hope never to notice.
- An e-mail logs an account in only once it is verified. A changed address used to be able to log in before anyone had proved it belonged to the user. It cannot now.
- Admins cannot act above their rank. An admin can no longer change, block or delete an account whose access group is the same as or higher than their own.
- A private record stays private, even from you. A private record of another user now comes back without its data to the project owner and to admins, however it was queried.
- An upload is signed for exactly the size it declares, and storage refuses a file of any other size.
None of these needed a line of your code to change. They shipped with the API, and every project got them the day they went out. The rules are written down in Admin Permissions.
The small things
- Every region connects. 2.0.1 fixed the project ID decoding that kept projects in the newest regions from connecting, which is what let Paris and then São Paulo open.
- Records of any size. A record's data larger than 32 KB is stored in file storage and handed back in
record.dataas if nothing had happened. - Timing you can trust. Request history carries
executed, the moment a request began running, so you can time a destination without counting your own queue. - A copied example just runs.
new Skapi("<Project ID>")with the placeholder still in it now asks for your project ID instead of refusing to start. - Look up many users at once.
getUsers()takes a list of values for any attribute, not only user IDs. - Bigger batches. Requests are processed fifty at a time, up from thirty.
What we were careful not to break
Moving fast is only worth something if your code keeps working. Through all eight releases the old method names still answer, the older newsletter and template addresses still deliver, and tickets saved before 2.2.0 keep running by the rules they were saved with.
One change does ask something of you, and we would rather say so here than let you find it. The ticket types in 2.2.0 are a type break: code that sets request on a ticket condition, or secret on a signature, no longer compiles. The version history names the replacement for each.
Why we keep going
We build Skapi because we think a web developer should be able to have a real backend without running one. That promise is only as good as the worst day you have with it. So the job is not the feature list. The job is the project that would not connect, the option that reset itself, the rule that was written down and not enforced.
Eight releases in twenty days is not a number we are aiming at. It is what it looks like when every report gets read and the answer is never "that is good enough for now". After 2.0 there was always one more thing worth making better, and there still is. We expect that to be true for as long as you are building on Skapi, and we intend to keep earning it.
Upgrading
npm i skapi-js@2.2.2
That installs 2.2.2 today. Read the version history first, keep every client of a project on the same version, and tell us what you find. It will probably be in the next release.