a–z, digits, _, -, ., up to 64 bytes), the same charset Arcane enforces on its side.
Unlocking is idempotent
Call it every time the condition holds. Unlocking something the player already has is a success, not an error: the answer carriesalready_unlocked: true and the original timestamp. You do not need a guard, a flag in your save file, or a check before the call.
Listing and the cache
list() returns everything the title defines, with this player’s state, and fills a cache inside the client:
is_unlocked reads that cache — no I/O, no failure, safe to call from anywhere:
None is not “locked”: it means the SDK has nothing to answer with. Call list() once — at launch, or when the achievements screen opens — and the answers become Some. Clones of the client share one cache, and unlock keeps it up to date.
Offline: queued
When the Arcane desktop app has no connection, it stores the unlock and answers queued: true. That is still Ok:
The one thing that is genuinely lost is a call made while the desktop app is not running at all — that returns
arcane_unavailable, and the SDK buffers nothing on disk. Under ARCANE_OFFLINE_ONLY both calls return network_required immediately, without touching the network.
Errors
Full table and what to do about each: Errors.
In C
arcane_sdk_achievements_json is the C form of list() — it makes the same call and fills the same cache, so arcane_sdk_achievement_is_unlocked returns -4 until it has succeeded once. Details: C ABI.
Not in scope
Progress-based achievements (set_progress) and leaderboards are not part of this SDK yet. An unlock is a loopback request that any local process could forge — validation of shape and plausibility belongs to the Arcane backend, not to the client.