Skillion API

Read and change your own skills, tasks and time-blocking routines from your scripts, shortcuts and integrations. Part of Skillion Premium.

Premium only · JSON · Bearer token

Start with your first call
Base URL https://api.skillion.app/functions/v1/api/v1
On this page

Your first call

  1. 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.

  2. Ask for your profile:

    curl
    export SKILLION_KEY="sk_live_…"   # yours, from the app
    
    curl https://api.skillion.app/functions/v1/api/v1/me \
      -H "Authorization: Bearer $SKILLION_KEY"
  3. You get your level, XP, coins and energy, and your limits:

    Response · example
    {
      "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

  1. Open Skillion and go to Settings.

  2. 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».

  3. A new row appears in Settings: «Developer API». Open it.

  4. 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
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

MethodPathWhat 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.

ParameterRule
limit1 to 100, default 50
cursorthe next_cursor of the previous page
updated_sinceISO date: only what changed after it
include_deletedtrue to also get deleted items (deleted_at set)
include_archivedtrue to also get archived skills and tasks
category_id, statustasks only: filter by category or status
event_typehistory only, for example taskCompleted

Task fields you can set

FieldRule
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
FieldRule
priority1–5, default 3
energy_cost-50…50, default -1 (negative spends energy, positive gives it)
coins0–100, default 10
health_effect-50…50
estimated_durationseconds, 0–86,400
recurrence_target1–99 times per period
recurrence_day_of_month1–31 (monthly, yearly)
recurrence_month1–12 (yearly)
recurrence_end_dateISO date
daily_deadline_time"HH:mm"
reminder_modeauto (default), at_deadline, before_15, before_60, morning, custom, none
reminder_minute0–1439, minute of the day (with custom)
is_penaltytrue: lose rewards when the deadline passes
iconan 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

FieldRule
nametext, 1–32 (required)
descriptiontext up to 500 or null
iconan emoji, or assets/images/icons/<name>.png
color#RRGGBB or null
is_activetrue / 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

FieldRule
nametext, 1–64 (required)
blockslist of up to 96 blocks (below)
is_defaulttrue: the one the app falls back to; marking one unmarks the others. If you have none yet, the new one is
iconan emoji, or assets/images/icons/<name>.png, or null
color#RRGGBB or null

Each block in blocks

FieldRule
start_minuteminutes 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_minute5–1440 in steps of 5
titletext, 1–128; required unless the block has task_id
task_idone of your tasks: the block is that task
notetext up to 2,000 or null
iconan emoji, or assets/images/icons/<name>.png, or null
color#RRGGBB or null
is_pinnedtrue: «Start now» does not move it
idonly 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.

JSON
{
  "error": {
    "code": "invalid_field",
    "message": "Some fields are not valid.",
    "details": { "custom_days": "must be weekdays like \"1,3,5\" …" }
  }
}
HTTPCodeWhat it does
400invalid_request / invalid_jsonbad query parameter or body
401unauthorizedmissing, malformed or revoked key
403premium_requiredthe account is not Premium
404not_foundno such item (or not yours)
409pact_task / bad_habit / too_many_pendingthe app owns this change
413payload_too_largebody over 64 KB
422invalid_fieldsee details
429rate_limitedwait retry-after seconds
405method_not_allowedthat method does not exist on this path
500internalour 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
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
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

JavaScript
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

JavaScript
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