AmLang registry https://tucan.amlang.net/api

User API

Accounts and access tokens: signing up with an emailed code, logging in, carrying the token, and what your account can then do in the registry.

Reads are public: the catalog, the versions and the downloads need no credential at all, so an IDE that only installs extensions never has to ask anyone to sign in. Everything on this page is for the other case — publishing. You need an account to put an extension into the registry, and this is how you get one and prove it is yours. For what you then do with it, see the Extension API.

Two kinds of request

credentialhowwhat it reaches
none— every read: the catalog, versions, resource downloads
Authorization: Bearer <jwt> an access token from login your account, and the extensions it owns — publishing included

Reads need nothing at all. A token is what turns a caller into somebody: it identifies your account, and through it the extensions you own. See the Extension API for what you can then do with them.

1. Sign up

Anyone can create an account, but it is not usable until the address on it is confirmed. Sign-up mails a four-digit code; entering that code is what turns the account on — and logs it in, since by then its holder has proved both the password and the address.

curl -X POST "https://tucan.amlang.net/api/users/signup" \
  -H 'Content-Type: application/json' -d '{
    "username":    "anders",
    "email":       "anders@example.com",
    "password":    "correct horse battery staple",
    "displayName": "Anders"
  }'
{"username":"anders","email":"anders@example.com","verification":"sent","expiresIn":1800}

Then, with the code from the mail — the response is the same token a login returns:

curl -X POST "https://tucan.amlang.net/api/users/verify" \
  -H 'Content-Type: application/json' -d '{"username":"anders","code":"2452"}'

# a fresh code, which replaces the outstanding one
curl -X POST "https://tucan.amlang.net/api/users/verify/resend" \
  -H 'Content-Type: application/json' -d '{"username":"anders"}'

Sign-up answers 503 on a deployment with no mail or no signing key configured, rather than creating accounts whose codes go nowhere, and 403 where it has been switched off (registry.signup_enabled=false). Neither affects the admin path below.

2. Log in

curl -X POST "https://tucan.amlang.net/api/users/login" \
  -H 'Content-Type: application/json' \
  -d '{"username":"anders","password":"correct horse battery staple"}'
{
  "accessToken":      "eyJhbGciOiJIUzI1NiJ9…",
  "tokenType":        "Bearer",
  "expiresIn":        86400,
  "refreshToken":     "Yy0xQk9…",
  "refreshExpiresIn": 31536000,
  "username":         "anders",
  "roles":            ["Administrator"]
}

Two tokens, with two jobs. The access token goes on every request and lasts 24 hours; it cannot be called back, which is why it is short. The refresh token lasts a year, is sent to one endpoint only, and can be called back — so a lost machine is a revoked session rather than a password change.

Every failure — unknown user, wrong password, disabled account, no password set — returns the same 401 and the same message. That is deliberate: distinguishing them tells an attacker which usernames exist. The one exception is an unverified account, which answers 403 with "verification":"required": that is only reachable with the right password, so it reveals nothing the caller did not just prove they knew, and it is the difference between retrying the password and going to read the mail.

3. Stay logged in

curl -X POST "https://tucan.amlang.net/api/users/token/refresh" \
  -H 'Content-Type: application/json' -d '{"refreshToken":"Yy0xQk9…"}'

The reply is the same shape as a login: a new access token and a new refresh token. Store both — the one you sent is spent.

To end a session deliberately, and to see what is signed in:

curl -X POST "https://tucan.amlang.net/api/users/logout" \
  -H 'Content-Type: application/json' -d '{"refreshToken":"Yy0xQk9…"}'

curl "https://tucan.amlang.net/api/users/me/sessions" -H "Authorization: Bearer $ACCESS_TOKEN"
curl -X DELETE "https://tucan.amlang.net/api/users/me/sessions/12" -H "Authorization: Bearer $ACCESS_TOKEN"
curl -X DELETE "https://tucan.amlang.net/api/users/me/sessions"    -H "Authorization: Bearer $ACCESS_TOKEN"

One row per machine signed in as you: what asked for it, where it was last used from, when it expires. The name shown is the User-Agent the login came with, so send something a person will recognise. An access token already issued stays valid until it runs out — nothing can recall it — which is exactly why its life is measured in hours.

4. Pass the token

curl "https://tucan.amlang.net/api/users/me" -H "Authorization: Bearer $ACCESS_TOKEN"

# and what that account participates in, invitations included
curl "https://tucan.amlang.net/api/users/me/extensions" -H "Authorization: Bearer $ACCESS_TOKEN"

The Bearer prefix is optional but conventional, and a token cookie is accepted as a fallback for browser clients. GET /users/me returns the account the token names — the cheapest way to check that a token is still good and see its roles.

What is in the token

HS256, signed by the registry.

{
  "sub":                "75",
  "preferred_username": "anders",
  "roles":              ["Administrator"],
  "iat":                1788282039,
  "exp":                1788325239,
  "iss":                "https://tucan.amlang.net/api",
  "aud":                "amlang-registry"
}

What each code means

codemeaningwhat to do
401 no usable credential: missing, malformed or expired token log in again
403 a real token, but this account is not allowed — you do not own this extension, or, on login, your address is not verified yet check verification in the body: required means enter the code. Otherwise it is not a token problem
404 on an account call, the token names an account that does not exist here; on an extension, it exists but is not yours to see sign up, or check the id
409 on sign-up: the username or address is taken pick another
429 too many wrong codes, resends, or sign-ups from one address ask for a fresh code, or wait
503 login or sign-up is unavailable on this deployment nothing a client can fix; tell whoever runs the registry

The 401/403 split is the useful one: 401 means try again with a better token, 403 means the token is fine, the account is not allowed. An unapproved extension answers 404 to everyone but its owner, so a 404 on something you know exists means it is not yours.

What your account can do

Once you are signed in and verified, the registry is open to you: you can create an extension, and you own what you create.

you canwhere
create an extensionPOST /extensions
edit its name and descriptionPOST /extensions, same localName
publish a versionPOST /extensions/{id}/v
upload the files it is made ofPUT /extensions/{id}/v/{version}/{platform}/r/{name}
delete a version, or its filesDELETE on those same paths
invite others to work on it, and remove themPOST /extensions/{id}/users
answer an invitation to someone else'sPOST /extensions/{id}/users/{username}/accept or /decline
see everything you participate inGET /users/me/extensions
read back your own versions before they are approvedGET /users/me/extensions/{id}, /v, /v/{version}, /v/{version}/{platform}/r/{name}

The one thing you cannot do is make your work public. A new extension, and each new version of it, is reviewed by whoever runs the registry before it appears in the catalog — and until then it is visible to you and to nobody else, so you can build and upload at your own pace without publishing anything half-finished.

Answering an invitation is yours alone: an extension's owner can invite you, but only you can accept. Anyone else trying gets a 403.

Endpoints

endpointwho
POST /users/signuppublic; creates an unverified account and mails a code
POST /users/verifypublic; exchanges the code for a verified account and a token
POST /users/verify/resendpublic; mails a fresh code, replacing the old one
POST /users/loginpublic; returns an access token and a refresh token
POST /users/token/refreshpublic; spends a refresh token for a new pair
POST /users/logoutpublic; ends that refresh token's session
GET /users/me/sessionsyour signed-in machines
DELETE /users/me/sessions/{id}sign one of them out; without an id, all of them
GET /users/meyour account
GET /users/me/extensionswhat you participate in; status tells invitations from memberships
GET /users/me/extensions/{id}one of yours, with every version under /v, approved or not, and their files; 404 unless you are an accepted member