ArcticAuth

HTTP API

Drive your services, keys and vault from anywhere with your account token.

Enter jumps to the next match, shift-enter the previous.

Download documentation

One Markdown file covering the whole product. Last changed August 30, 2026.

Authentication

Every call below is authenticated with your account token, sent as a bearer token. Mint one on Account - it is shown once, in full, and after that only its last few characters are.

every request
curl https://api.arcticauth.com/api/v1/services \
  -H "Authorization: Bearer YOUR_ACCOUNT_TOKEN"
The token is your whole account. It is not scoped and there is no read-only variant. Anything the dashboard can do to your services, a token can do - including deleting one. Treat it like the password, keep it server-side, and never ship it inside a Roblox script: a loadstring is readable by everyone who runs it.

Regenerating the token on the account screen revokes the old one the moment the new one is issued. There is no grace period and no second active token, so rotate when you have somewhere ready to put the new value.

Signing in with a browser still works exactly as before. These endpoints accept either, and the token is only consulted when an Authorization header is present.

Services

A service is one product: its own keys, its own vault, its own checkpoint chain. The id in a key page URL is the same id used here.

GET /api/v1/services
Every service on your account, with its name, identifier, slug and whether it is paused.
POST /api/v1/services
Creates one. Body: { "name": "My Hub", "identifier": "my-hub" }. The identifier has to be unique across the platform; a clash answers 409.
GET /api/v1/services/{serviceId}
One service in full: name, identifier, slug, plan, checkpoint count, whether it is active, and when it was last used.
GET /api/v1/services/{serviceId}/stats
Today's activity. Executions served, keys live and ever issued, validation calls answered, checkpoint runs started and finished, and hardware ids refused.
GET /api/v1/services/{serviceId}/settings
Everything on the settings screen, including the HWID cooldown that gates the self-serve reset page.
PUT /api/v1/services/{serviceId}/settings
Saves them. The whole object goes in - this is a replace, not a patch, so read it first and send it back changed.
DELETE /api/v1/services/{serviceId}
Deletes the service and everything under it. There is no undo and no confirmation step on the API.
create a service
curl -X POST https://api.arcticauth.com/api/v1/services \
  -H "Authorization: Bearer YOUR_ACCOUNT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"My Hub","identifier":"my-hub"}'
today's activity
curl https://api.arcticauth.com/api/v1/services/SERVICE_ID/stats \
  -H "Authorization: Bearer YOUR_ACCOUNT_TOKEN"
Counters that answer null. A stat comes back null when the table behind it holds nothing yet, rather than zero. Null means "nothing recorded", zero means "recorded, and it was none" - worth keeping apart on a dashboard of your own.

Keys

Keys are minted in batches, edited one at a time, and deleted either way. A key binds to a machine the first time it is validated, not when it is created.

GET /api/v1/services/{serviceId}/keys
Paged. Takes page, pageSize, and a search term matched against the key value, note and Discord id.
GET /api/v1/services/{serviceId}/keys/counts
Totals only - live, expired, revoked, premium - without pulling a page of rows.
POST /api/v1/services/{serviceId}/keys
Mints a batch. Body: { prefix, count, expirationType, days, expiresAt, isPremium, hwidValidation }. Returns the minted values, once.
POST /api/v1/services/{serviceId}/keys/import
Adopts keys minted elsewhere, one per line, so a migration does not invalidate what customers already hold.
PATCH /api/v1/services/{serviceId}/keys/{keyId}
Partial: every field is optional and null means leave it. { note, expiresAt, isPremium, hwidLocked, clearHwid, revoked }.
DELETE /api/v1/services/{serviceId}/keys/{keyId}
Deletes one.
POST /api/v1/services/{serviceId}/keys/delete
Deletes many. Body: { "keyIds": ["...", "..."] }.
mint ten 30-day keys
curl -X POST https://api.arcticauth.com/api/v1/services/SERVICE_ID/keys \
  -H "Authorization: Bearer YOUR_ACCOUNT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "prefix": "HUB",
    "count": 10,
    "expirationType": 10,
    "days": 30,
    "isPremium": false,
    "hwidValidation": true
  }'
unbind a key from its machine
curl -X PATCH https://api.arcticauth.com/api/v1/services/SERVICE_ID/keys/KEY_ID \
  -H "Authorization: Bearer YOUR_ACCOUNT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"clearHwid": true}'
clearHwid is separate from hwid for a reason. Null already means "leave this alone" on every field, so there would otherwise be no way to say "unbind it". Clearing from here does not start the self-serve cooldown - that timer exists to stop a key being passed around a group, and an owner helping somebody who changed their motherboard should not have to wait it out.

Secure vault

Every script, its slug, and the loadstring your users paste. The slug is per script and rotatable on its own, which is what makes a leaked entry point survivable.

GET /api/v1/services/{serviceId}/vault/scripts
Every script: id, name, description, slug, whether it accepts a script_key, whether Lockmode is on, its obfuscation preset and its status.
GET /api/v1/services/{serviceId}/vault/overview
The same list plus the counters the vault screen shows.
GET /api/v1/services/{serviceId}/vault/scripts/{scriptId}
One script in full.
PATCH /api/v1/services/{serviceId}/vault/scripts/{scriptId}/hardened
Lockmode for this one script. Body: { "hardened": true }. This is the per-slug switch.
PATCH /api/v1/services/{serviceId}/vault/scripts/{scriptId}/status
Serves it or stops serving it. Body: { "enabled": false }. A disabled script answers nothing, which is the fastest kill switch you own.
DELETE /api/v1/services/{serviceId}/vault/scripts/{scriptId}
Deletes the script and its versions.

The loadstring is built from the slug rather than returned ready-made, because which of the two shapes a script uses is decided by its own mode:

from a slug
-- loader-based
_G.SlugID = "SCRIPT_SLUG"
loadstring(game:HttpGet("https://api.arcticauth.com/api/v1/loader"))()

-- direct
loadstring(game:HttpGet("https://api.arcticauth.com/api/v1/s/SCRIPT_SLUG"))()
Lockmode is not the same switch as platform lockdown. Lockmode is the per-script one above, and it is yours. Platform lockdown is POST /api/v1/admin/vault/lockdown, it is staff-only, and it stops vault delivery for every service on the platform at once. If you were looking for a per-slug lockdown, it is the hardened flag you want.

Self-serve pages

Two anonymous pages your users can be sent to instead of you. The key is the credential, so there is nothing for them to sign into.

POST /api/v1/public/keys/check
Body: { service, key }. Answers what the key is bound to, when it expires, whether it works, and whether a reset would be allowed right now.
POST /api/v1/public/keys/reset-hwid
Body: { service, key }. Unbinds it. Refusals answer 200 with reset: false and a sentence, because "the cooldown has not run out" is an ordinary answer rather than an error.

The pages themselves are /key-checker and /reset-hwid. Both accept the service and the key in the URL, so a link handed to a user arrives filled in:

a link worth pasting into your Discord
https://arcticauth.com/reset-hwid/SERVICE_ID
https://arcticauth.com/key-checker/SERVICE_ID
Resets are off until you switch them on. The reset page is gated by this service's HWID cooldown setting. With it off, every reset is refused with "ask its owner" - which is the documented meaning of that switch, and the safe default for a product whose promise is one key, one machine. Turn it on and set the window in Settings; free and premium keys can carry different ones.

Validation

What a script calls at runtime. No token: this one is anonymous by design, because it is called from an executor where any credential you shipped would already be public.

POST /api/validation/v1/validate
Body: { service, key, hwid }. Always 200 on a well-formed request, with the verdict in the body - a loader has to branch on the reason anyway, and a 4xx makes some executors throw where a value was expected.
GET /api/validation/v1/ping
Liveness, for a loader that wants to fail fast rather than hang.

Errors and limits

Failures answer application/problem+json with a title worth showing and, where it helps, a detail worth logging.

401
No token, a token that is not valid, or one whose account is disabled. All three answer the same, so a leaked token learns nothing.
403
The token's account exists but cannot do that.
404
No such service, or one your account does not own. The two are not told apart, so this cannot be used to find out which ids are real.
409
A conflict with something that already exists - most often a service identifier already taken.
429
Rate limited. The self-serve pages allow 20 calls a minute per address; validation allows 120.
Ids and slugs. Anywhere a service is named, both its id and its slug are accepted. The slug is the one to hand out: it is random rather than a timestamped uuid, and it can be rotated when a loadstring leaks.