Use the TypeScript/JavaScript package on your server, or call the REST API directly.
https://portal.easystreak.devEvery request except GET / and GET /routes needs a USER_API_KEY header — your personal key, generated in the dashboard (Settings), valid for 90 days and regenerable any time. Missing, invalid or expired keys return 401 with a JSON error. Keep both keys on your server; never expose them in browser code.
User, streak and trophy routes also need the project's own key, sent as PROJECT_API_KEY. Both headers are required together — a valid account key alone isn't enough to reach a project's data. Project creation, status changes, key rotation and deletion require only the personal key and verify ownership.
A project is active or stopped, toggled from the dashboard or via POST /project/status. Stopping a project keeps its users, streak days and trophies, but every project/user, streak/* and trophy/* request against it is rejected with 403 until it's turned back on. Only active projects — and the users inside them — count against your plan's limits, so stopping one frees a slot to create or reactivate another.
If your account's usage ever ends up over your plan (a downgrade, or a temporary override expiring), project-data calls are rejected with 402 until it's resolved — stop or delete projects to get back under your limits, or upgrade. This is checked automatically the moment your plan or usage changes, not just when you try to create something new.
Register your user first with POST /project/user (project_public_id and user_id), then mark today done and read the streak back. Writes use POST; health and route discovery use GET.
const res = await fetch("https://portal.easystreak.dev/streak/today", {
method: "POST",
headers: {
"Content-Type": "application/json",
"USER_API_KEY": process.env.EASYSTREAK_USER_KEY!,
"PROJECT_API_KEY": process.env.EASYSTREAK_PROJECT_KEY!,
},
body: JSON.stringify({
project_public_id: process.env.EASYSTREAK_PROJECT!,
user_public_id: "u_92f1",
}),
});
const { day, done } = await res.json();
// → { day: "2026-09-07", done: true }const res = await fetch("https://portal.easystreak.dev/streak/stats", {
method: "POST",
headers: {
"Content-Type": "application/json",
"USER_API_KEY": process.env.EASYSTREAK_USER_KEY!,
"PROJECT_API_KEY": process.env.EASYSTREAK_PROJECT_KEY!,
},
body: JSON.stringify({
project_public_id: process.env.EASYSTREAK_PROJECT!,
user_public_id: "u_92f1",
}),
});
const { current_streak, best_streak, total_days_done, trophies } = await res.json();
// → { current_streak: 36, best_streak: 41, total_days_done: 58, trophies: [] }const res = await fetch("https://portal.easystreak.dev/streak/days", {
method: "POST",
headers: {
"Content-Type": "application/json",
"USER_API_KEY": process.env.EASYSTREAK_USER_KEY!,
"PROJECT_API_KEY": process.env.EASYSTREAK_PROJECT_KEY!,
},
body: JSON.stringify({
project_public_id: process.env.EASYSTREAK_PROJECT!,
user_public_id: "u_92f1",
page: 1,
page_size: 31,
}),
});
const { days, page, page_size, total, total_pages } = await res.json();
// → { days: [{ day: "2026-09-08", done: true }, ...], page: 1, page_size: 31, total: 58, total_pages: 2 }A server-side SDK with ESM, CommonJS and TypeScript declarations. No runtime dependencies. Call it from your backend after authenticating your own user.
npm install easystreakimport { EasyStreak } from "easystreak";
const easystreak = new EasyStreak({
userKey: process.env.EASYSTREAK_USER_KEY!,
projectKey: process.env.EASYSTREAK_PROJECT_KEY!,
project: process.env.EASYSTREAK_PROJECT!,
});
await easystreak.addUser({ user: "u_92f1" }); // Required once; safe to repeat
await easystreak.check({ user: "u_92f1" });
const { streak, longest, alive, trophies } = await easystreak.stats({ user: "u_92f1" });
// First check-in: { streak: 1, longest: 1, alive: true, trophies: [] }userKey is your personal account key, projectKey is the project key, and project is its public ID. baseUrl defaults to https://portal.easystreak.dev; set it to http://localhost:3011 for local development. Project methods accept a project override, but keep the same key: use a separate client for each project/key pair.
| Method | Returns |
|---|---|
addUser({ user }) | { publicId } |
check({ user }) | { day, done } |
toggle({ user, date, done }) | { day, done } |
days({ user, page?, pageSize? }) | { days, page, pageSize, total, totalPages } |
stats({ user }) | { streak, longest, totalDaysDone, alive, trophies } |
createTrophy({ name, description? }) | { publicId } |
giveTrophy({ user, trophy }) | { trophyPublicId, awardedAt } |
removeTrophy({ user, trophy }) | { removed } |
createProject({ ownerId, name }) | { publicId } |
setProjectStatus({ status }) | { status } |
setProjectApiKey({ apiKey }) | { apiKey } |
deleteProject({ project? }?) | { deleted } |
health() | { status: "ok" } |
All methods return promises. Project-scoped methods accept project?. Project administration checks account ownership and does not require projectKey. ownerId is your account UUID, not a tracked user ID. health sends no credentials, although the constructor requires userKey.
check marks today in UTC. toggle sets a valid YYYY-MM-DD date to true or false. Statistics count completed dates; alive means streak > 0. Project periods are calendar-aligned: weekly needs one check-in per calendar week (Monday–Sunday), biweekly per 14-day block, monthly per calendar month. stats also returns trophies, the list of trophies the user currently holds — each { publicId, name, description, awardedAt }, or an empty array if none.
days returns the raw streak_day history for a user — every date ever checked in or toggled, done true or false, most recent first — paginated so you can build a calendar. page is 1-indexed and defaults to 1; pageSize defaults to 30 and is capped at 100.
const { days, total, totalPages } = await easystreak.days({ user: "u_92f1", page: 1, pageSize: 31 });
// days: [{ day: "2026-09-08", done: true }, { day: "2026-09-07", done: true }, ...], most recent firstawait easystreak.toggle({ user: "u_92f1", date: "2026-09-08", done: false });
const { publicId: trophy } = await easystreak.createTrophy({ name: "First week" });
await easystreak.giveTrophy({ user: "u_92f1", trophy });
await easystreak.removeTrophy({ user: "u_92f1", trophy });Trophies are awarded explicitly, not automatically. Registering a user, checking today and awarding the same trophy are idempotent. Creating projects or trophies is not. setProjectStatus accepts active or stopped. setProjectApiKey accepts a non-empty key or null; recreate your client after rotation. deleteProject permanently deletes its users, streak dates and trophies.
import { EasyStreakError } from "easystreak";
try {
await easystreak.check({ user: "u_92f1" });
} catch (error) {
if (error instanceof EasyStreakError) {
console.error(error.status, error.message);
} else {
throw error;
}
}400: invalid request. 401: invalid keys or owner. 402: account over quota. 403: stopped project or creation limit. 404: resource not found. 500/503: service unavailable. HTTP errors throw EasyStreakError with status and message; configuration and network errors are ordinary errors. There is no automatic retry or SDK timeout. Verify the result before retrying a write.
Stripe billing, account authentication and webhooks belong to the dashboard, not the package. The npm README includes the complete configuration and local-development guide.
Full machine-readable reference at GET /routes. Click a route to see what to send and what you get back.