API
The API your app calls over its binding to Chaski, and the user routes its frontend calls.
ChaskiEntrypoint
Your app calls Chaski through its service binding to ChaskiEntrypoint, as
Connect your app sets it up. A binding exposes methods only, so the groups are
methods: env.CHASKI.feed().list(query) is still a single round trip.
| Call | Returns | |
|---|---|---|
ready() | { channels, paused, problems, environment, templates } | Checks the setup and the applied migrations, lists the channels that are on, the paused ones and the saved settings that stopped working (problems: { channel, message }[]), names the environment from CHASKI_ENVIRONMENT (null without one) and counts the templates, changing nothing; throws the config error a send would. |
send(request) | SendResult | Sends now; see Sending. |
schedule(request) | ScheduleResult | Sends at scheduledAt; see Scheduling. |
schedules().list({ recipientId? }) | ScheduledItem[] | Pending schedules, soonest first. |
schedules().cancel(query) | { cancelled } | Cancels by id, idempotencyKey or groupKey. |
feed().list(query) | FeedPage | A recipient’s feed, newest first, a page at a time. |
feed().unreadCount(recipientId) | { unread } | |
feed().markRead(input) | { marked } | Marks ids, everything before an instant, or the whole feed. |
preferences().get(recipientId) | ChaskiPreferences | |
preferences().set(recipientId, update) | ChaskiPreferences | Merges channels; mutedTypes overwrites the list. |
preferences().topics() | { topic, types }[] | Template types grouped by topic. |
push().register(request) | { id } | Registers a web push subscription, an iOS or macOS device token, an Android FCM token or a Windows channel URI. |
push().unregister(query) | { unregistered } | By recipientId, endpoint or deviceToken. |
push().vapidPublicKey() | { publicKey } | The key for pushManager.subscribe(), or null. |
templates().import(file, { preview? }) | TemplateImportResult | Creates or updates templates by type, and their translations, from a template file; preview only reports. Takes version 1 and 2 files. |
templates().export() | TemplateFile | Every template with its translations, as a version 2 file templates().import() takes. |
ready() suits a health route of your app: it asks the database each time, so a database that
doesn’t answer fails it too. templates counts the templates outside the trash; at 0, every send
is refused as an unknown type, as on a fresh database or after a deploy that forgot to load them.
paused names the channels someone paused in the dashboard,
whose deliveries wait as held, and problems the channels whose deliveries fail until someone
saves their settings again.
app.get('/health', async (c) => {
try {
const status = await c.env.CHASKI.ready();
const healthy = status.templates > 0 && status.problems.length === 0;
return c.json({ chaski: status }, healthy ? 200 : 503);
} catch (error) {
return c.json({ chaski: String(error) }, 503);
}
});Requests
type SendRequest = {
type: string;
recipient: Recipient;
payload?: Record<string, unknown>;
channels?: Array<'email' | 'in_app' | 'push'>;
transactional?: boolean;
email?: { fromName?: string; replyTo?: string; attachments?: AttachmentInput[] };
// silent shows the push without a sound, on Apple devices, on Windows and in browsers;
// collapseId replaces a push already shown with the same id and becomes its tag.
push?: { silent?: boolean; collapseId?: string };
// For your clients, as is: the feed item and every push carry it. Strings only, since FCM
// data and WNS launch arguments hold nothing else; up to 20 keys.
data?: Record<string, string>;
// The language of this send alone, ahead of recipient.locale; a BCP 47 tag. One that isn't
// counts as absent.
locale?: string;
idempotencyKey?: string;
// Delivers the one transactional email before send() returns; see Sending.
inline?: boolean;
};
type ScheduleRequest = Omit<SendRequest, 'inline'> & {
// ISO 8601 with an explicit offset: Z or ±hh:mm.
scheduledAt: string;
groupKey?: string;
};
type Recipient = {
id: string;
email?: string;
name?: string;
phone?: string;
timezone?: string;
locale?: string;
};
type AttachmentInput = {
filename: string;
contentType: string;
// Exactly one of content (base64) or url.
content?: string;
url?: string;
disposition?: 'attachment' | 'inline';
contentId?: string;
};Feed items
type FeedItem = {
id: string;
// The notification it came from; its pushes carry the same id as their tag, unless the send
// named a collapseId.
notificationId: string;
type: string;
title: string;
body: string;
href?: string;
// The send's data, or null.
data: Record<string, string> | null;
readAt: string | null;
createdAt: string;
};
type FeedPage = { items: FeedItem[]; nextCursor: string | null };feed.list() takes a recipientId, and optionally a cursor from the last page, a limit up to
100 and unreadOnly.
User routes
userRoutes() answers these paths, relative to where your app mounts it, for the user identify
returns, and forwards each one to Chaski. chaskijs/client calls them for you.
| Method and path | |
|---|---|
GET /feed | The feed; cursor, limit and unreadOnly as query parameters. |
GET /feed/unread-count | { unread } |
POST /feed/read | Marks items read: { ids?, before? }, or {} for the whole feed. |
GET /feed/socket | A WebSocket that announces new items, whole, and reads made in other tabs and devices. ?device=<id> names a device the client registered, whose pushes the feed takes over while the socket is open. |
GET /preferences | The user’s preferences. |
PATCH /preferences | Updates them. |
GET /preferences/topics | Template types grouped by topic. |
GET /push/vapid-public-key | { publicKey } |
POST /push/subscriptions | Registers this browser’s subscription, an iOS or macOS device token, an Android FCM token or a Windows channel URI; answers { id }. |
DELETE /push/subscriptions | Drops one, by endpoint or deviceToken. |
A path the mount doesn’t serve, including those of a group left out of groups, reaches your
app’s own 404, and identify doesn’t run for it. So a second mount can sit under the first
one’s path (/me/notifications/team under /me/notifications) and both answer, in whichever
order you register them.
Writes and socket upgrades from an origin other than the request’s own or one in allowedOrigins
answer 403. The mount also serves GET /t/o/:deliveryId, the open-tracking pixel of Chaski’s
emails, to anyone. Errors answer as { error, message }, with the codes in
Errors.
Package entries
| Entry | Runs in | |
|---|---|---|
chaskijs/worker | The Chaski Worker | The Worker’s handlers, ChaskiEntrypoint, the Durable Objects and the Workflow, which a Chaski project’s main module re-exports. |
chaskijs | Your app’s Worker | userRoutes(), the errors, type ChaskiEntrypoint and every type of the API, in a few kilobytes. |
chaskijs/client | The browser | createChaskiClient, chaskiQueries, ChaskiRequestError. |
chaskijs/sw | Your service worker | handlePushEvents. |
chaskijs/react | The browser, with React 18 or 19 | ChaskiProvider and the hooks. |
Updated Oct 30, 2026
MIT License · chaski.sh