Skillion API
Read and change your own skills, tasks and time-blocking routines from your scripts, shortcuts and integrations. Part of Skillion Premium.
https://api.skillion.app/functions/v1/api/v1 On this page
Your first call
-
Get your key in the app: Settings → tap the version 7 times → Developer API. Step by step, in detail
Heads up
You need Skillion Premium and a registered account (not a guest one). Without Premium the row shows a lock and opens the paywall.
-
Ask for your profile:
export SKILLION_KEY="sk_live_…" # yours, from the app curl https://api.skillion.app/functions/v1/api/v1/me \ -H "Authorization: Bearer $SKILLION_KEY" -
You get your level, XP, coins and energy, and your limits:
{ "data": { "level": 7, "total_xp": 140, "xp_to_next_level": 350, "coins": 820, "energy_points": 64, "max_energy": 100, "health_points": 100, "max_health": 100, "nickname": "Luna", "updated_at": "2026-10-21T18:00:00Z" }, "limits": { "per_minute": 120, "per_day": 10000, "page_max": 100 } }
Get your key
-
Open Skillion and go to Settings.
-
Scroll to the bottom and tap the line «Version …» seven times in a row, with less than 2 seconds between taps. Your phone vibrates and you see «Developer options unlocked».
-
A new row appears in Settings: «Developer API». Open it.
-
Tap «Create key», give it a name and tap «Create». Then tap «Copy»: the key is shown only once. You can have up to 5 active keys and revoke any of them on that screen.
Heads up
Treat a key like a password: anyone with it can change your tasks. If one leaks, revoke it on that same screen.
Authentication
Send the key as a Bearer token on every request. All bodies are JSON.
curl https://api.skillion.app/functions/v1/api/v1/me \
-H "Authorization: Bearer sk_live_…" Completing a task is not instant
Heads up
XP, coins, energy and streaks are calculated by the app, so completing through the API is a request: you get 202 at once and your phone applies it the next time Skillion syncs (when you open it). Follow it in /v1/completions: pending → claimed → applied, or rejected with a reason (already_completed, insufficient_energy…).
Pact tasks are completed with a check-in in the app and bad habits are never completed: both answer 409.
Endpoints
| Method | Path | What it does |
|---|---|---|
| Account | ||
| GET | /v1/me | Your level, XP, coins, energy and the limits |
| Skills | ||
| GET | /v1/skills | List skills |
| POST | /v1/skills | Create a skill |
| GET | /v1/skills/{id} | One skill |
| PATCH | /v1/skills/{id} | Change a skill |
| DELETE | /v1/skills/{id} | Archive a skill |
| Tasks | ||
| GET | /v1/tasks | List tasks, each with its skills |
| POST | /v1/tasks | Create a task |
| GET | /v1/tasks/{id} | One task |
| PATCH | /v1/tasks/{id} | Change a task |
| DELETE | /v1/tasks/{id} | Delete or archive a task |
| POST | /v1/tasks/{id}/complete | Ask the app to complete it (202) |
| Completions | ||
| GET | /v1/completions | Your completion requests and what happened |
| Routines | ||
| GET | /v1/routines | List routines, each with its blocks |
| POST | /v1/routines | Create a routine with its blocks |
| GET | /v1/routines/{id} | One routine |
| PATCH | /v1/routines/{id} | Change a routine or its blocks |
| DELETE | /v1/routines/{id} | Delete a routine and its blocks |
| Read-only | ||
| GET | /v1/categories | Your categories (read-only) |
| GET | /v1/history | Your history: completions, level-ups… (read-only) |
Lists and paging
Lists come oldest change first. Pass next_cursor back as cursor until it is null. Deleted and archived items are left out unless you ask for them.
| Parameter | Rule |
|---|---|
limit | 1 to 100, default 50 |
cursor | the next_cursor of the previous page |
updated_since | ISO date: only what changed after it |
include_deleted | true to also get deleted items (deleted_at set) |
include_archived | true to also get archived skills and tasks |
category_id, status | tasks only: filter by category or status |
event_type | history only, for example taskCompleted |
Task fields you can set
| Field | Rule |
|---|---|
titlerequired | text, 1–128 |
description | text up to 2,000 or null |
category_id | one of your categories; default: your first default one |
skills | [{"skill_id": "…", "xp": -100…100}], up to 20; xp defaults to 10, negative takes XP away |
difficulty | very_easy, easy, medium (default), hard, very_hard |
is_recurring | true / false |
recurrence_pattern | daily, weekly, monthly, yearly, custom (needed if recurring) |
custom_days | "1,3,5": 1 = Monday … 7 = Sunday (for custom) |
deadline | ISO date, only for one-off tasks |
Show 15 more fields
| Field | Rule |
|---|---|
priority | 1–5, default 3 |
energy_cost | -50…50, default -1 (negative spends energy, positive gives it) |
coins | 0–100, default 10 |
health_effect | -50…50 |
estimated_duration | seconds, 0–86,400 |
recurrence_target | 1–99 times per period |
recurrence_day_of_month | 1–31 (monthly, yearly) |
recurrence_month | 1–12 (yearly) |
recurrence_end_date | ISO date |
daily_deadline_time | "HH:mm" |
reminder_mode | auto (default), at_deadline, before_15, before_60, morning, custom, none |
reminder_minute | 0–1439, minute of the day (with custom) |
is_penalty | true: lose rewards when the deadline passes |
icon | an emoji, or assets/images/icons/<name>.png |
color | #RRGGBB |
Only title is required. Anything else (XP, level, status, coins earned) is calculated by the app and is read-only. Unknown fields are refused with 422, so a typo never goes unnoticed.
Skill fields you can set
| Field | Rule |
|---|---|
name | text, 1–32 (required) |
description | text up to 500 or null |
icon | an emoji, or assets/images/icons/<name>.png |
color | #RRGGBB or null |
is_active | true / false |
level, total_xp (the XP inside the current level) and the streaks are read-only. Free accounts keep 3 active skills, but the API itself is Premium only.
Routines and their blocks
A routine is a day plan of the time blocking screen («Monday», «Work day»…) with its blocks inside. Plan a whole week with seven POST /v1/routines, one per day.
Routine
| Field | Rule |
|---|---|
name | text, 1–64 (required) |
blocks | list of up to 96 blocks (below) |
is_default | true: the one the app falls back to; marking one unmarks the others. If you have none yet, the new one is |
icon | an emoji, or assets/images/icons/<name>.png, or null |
color | #RRGGBB or null |
Each block in blocks
| Field | Rule |
|---|---|
start_minute | minutes from midnight, 0–2879 in steps of 5 (420 = 07:00; 1440 and up is the next day, for a routine that runs past midnight) |
duration_minute | 5–1440 in steps of 5 |
title | text, 1–128; required unless the block has task_id |
task_id | one of your tasks: the block is that task |
note | text up to 2,000 or null |
icon | an emoji, or assets/images/icons/<name>.png, or null |
color | #RRGGBB or null |
is_pinned | true: «Start now» does not move it |
id | only to change a block the routine already has |
blocks is always the whole list: a block with id is updated, one without id is created and any block you leave out is deleted. Leave blocks out of a PATCH to keep them as they are. Blocks come back with start_time and end_time («07:30») to read them easily.
Heads up
Which routine your phone shows today is chosen on the phone: the API creates and changes routines but does not switch the one you see.
Errors
Errors always have the same shape. details lists each field that was not valid.
{
"error": {
"code": "invalid_field",
"message": "Some fields are not valid.",
"details": { "custom_days": "must be weekdays like \"1,3,5\" …" }
}
} | HTTP | Code | What it does |
|---|---|---|
| 400 | invalid_request / invalid_json | bad query parameter or body |
| 401 | unauthorized | missing, malformed or revoked key |
| 403 | premium_required | the account is not Premium |
| 404 | not_found | no such item (or not yours) |
| 409 | pact_task / bad_habit / too_many_pending | the app owns this change |
| 413 | payload_too_large | body over 64 KB |
| 422 | invalid_field | see details |
| 429 | rate_limited | wait retry-after seconds |
| 405 | method_not_allowed | that method does not exist on this path |
| 500 | internal | our fault: try again later |
Limits
- 120 requests per minute
- per account, all your keys together
- 10,000 requests per day
- per account, the day resets at 00:00 UTC
- 100 items per page
- default 50
- 64 KB per body
Over a limit you get 429 with a retry-after header (seconds). Every answer carries x-ratelimit-limit and x-ratelimit-remaining.
Deleting
DELETE does what the app does: skills and recurring tasks are archived (their history stays), one-off tasks are deleted, and a routine is deleted with its blocks. Either way they disappear from your phone on its next sync.
Good to know
- Changes reach your phone on its next sync, usually when you open the app.
- If you edit the same task on your phone before it syncs, the phone wins.
- Recurring tasks get their next due date from your phone, which knows your time zone and daily reset hour.
- Dates are ISO 8601 with a time zone, for example
2026-10-21T18:00:00Z.
Examples
Create a recurring task linked to a skill
curl -X POST https://api.skillion.app/functions/v1/api/v1/tasks \
-H "Authorization: Bearer $SKILLION_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Gym",
"is_recurring": true,
"recurrence_pattern": "custom",
"custom_days": "1,3,5",
"difficulty": "hard",
"skills": [{ "skill_id": "<skill id>", "xp": 20 }]
}' Complete it (from a shortcut, a cron, a button)
curl -X POST https://api.skillion.app/functions/v1/api/v1/tasks/<task id>/complete \
-H "Authorization: Bearer $SKILLION_KEY"
# 202 → { "data": { "id": "…", "status": "pending", … } } Read everything that changed since yesterday
const since = new Date(Date.now() - 86_400_000).toISOString()
let cursor = null
do {
const url = new URL('https://api.skillion.app/functions/v1/api/v1/tasks')
url.searchParams.set('updated_since', since)
if (cursor) url.searchParams.set('cursor', cursor)
const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.SKILLION_KEY}` } })
const page = await res.json()
for (const task of page.data) console.log(task.title, task.status)
cursor = page.next_cursor
} while (cursor) Plan your week: one routine per day
const days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
for (const name of days) {
const weekend = name === 'Saturday' || name === 'Sunday'
const res = await fetch('https://api.skillion.app/functions/v1/api/v1/routines', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.SKILLION_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
name,
blocks: [
{ start_minute: weekend ? 540 : 420, duration_minute: 30, title: 'Read' },
{ start_minute: 1080, duration_minute: 60, title: 'Gym', color: '#F29092' },
{ start_minute: 1380, duration_minute: 60, title: 'Wind down' },
],
}),
})
if (!res.ok) console.error(name, (await res.json()).error)
} Help? Write to us from the contact page. Contact