Make your first API request
Create an API key, list your services with it, and sign in for the few routes a key cannot reach, with curl commands you can copy.
In the dashboard
This tutorial takes you from the dashboard to a working API request in a terminal. You create an API key, list your services with it and see what a key cannot do. For those few routes, you sign in with your password and renew the access token when it runs out. Every request goes to https://api.coritan.com/api/v1.
Before you begin
Section titled Before you begin- A Coritan account. Create an account if you have none.
curl, andjqto read values out of the JSON answers.- For sections 4 and 5 only: your password, and your authenticator app if you turned on two-factor authentication.
1. Create an API key
Section titled 1. Create an API keyA script sends an API key to prove it acts for you. The key works until you revoke it, so the script never needs your password.
Sign in to the dashboard. In the sidebar, select Settings, then the API keys tab.
Select Create API key…. On the Create an API key page, type a Label, such as
First script.Keep the Preset the page starts with. It is Read only, so the key can read your account but cannot change it.
Select Create API key, then copy the key from Copy this key now. Coritan shows it only this once. Select I have saved it when you have copied it.
In your terminal, read the key into a variable, so it stays out of your shell history:
Shellread -rs -p "API key: " CORITAN_TOKEN; echo; export CORITAN_TOKEN
echo "${CORITAN_TOKEN:0:3}" now prints ct_. The examples across these docs send $CORITAN_TOKEN, so they work with your key as they are. Manage API keys covers the permissions and the IP allow-list in full.
2. List your services
Section titled 2. List your servicesSend the key in the Authorization header of every request. GET /services/ returns a JSON array of the services on your account, newest first. This prints one line for each of the first five:
curl -s "https://api.coritan.com/api/v1/services/?limit=5" \
-H "Authorization: Bearer $CORITAN_TOKEN" \
| jq -c '.[] | {id, hostname, status, product_name}'
{"id":1042,"hostname":"web-1.example.com","status":"active","product_name":"Cloud Compute 2 GB"}
An account with no services prints nothing. Manage your subscriptions lists the filters and fields.
You now have a script that reads your account with a key.
3. See what a key cannot do
Section titled 3. See what a key cannot doYour key can only read, so a request that would change something answers 403:
{"detail": {"code": "api_key_read_only", "message": "This API key can only read. Create a key with a write permission to make changes."}}
A key with a Write permission can make every change you can. Whatever its permissions, a key cannot reach your profile, password, sessions, two-factor authentication, API keys or billing profile, your team, webmail or your organizations. Ask for your profile with the key to see the answer:
curl -s https://api.coritan.com/api/v1/auth/me \
-H "Authorization: Bearer $CORITAN_TOKEN"
{"detail": {"code": "api_key_route_refused", "message": "An API key cannot read or change your profile, password, sessions, two-factor authentication, email address, notifications, API keys or billing profile, and it cannot close your account. Sign in to the dashboard for that."}}
A key also cannot work in another account you joined with the X-Coritan-Account header. What no key can do lists every route a key cannot reach. For those, use the dashboard, or sign in from the script as the next section shows.
4. Sign in for what a key cannot reach
Section titled 4. Sign in for what a key cannot reachSigning in returns an access token. It works on every route a key works on and on the routes a key cannot reach. It expires after expires_in seconds and signing in needs your password, so use it only for what a key cannot do.
The sign-in page can ask for a bot check. Ask the API whether it is on:
Shellcurl https://api.coritan.com/api/v1/auth/turnstileJSON{"enabled": false, "site_key": ""}When
enabledisfalse, an email and a password are enough to sign in. When it istrue,POST /auth/loginalso needs aturnstile_tokenthat only the check on the sign-in page produces. Without one it answers403with{"detail": {"error": "turnstile_failed", "message": "Verification required"}}, so a script cannot sign in on its own.Read your password into a variable, so it stays out of your shell history:
Shellread -rs -p "Password: " CORITAN_PASSWORD; echoSign in with
POST /auth/login.jqbuilds the JSON body, so a password with quotes or other special characters is sent intact:Shellcurl -s -X POST https://api.coritan.com/api/v1/auth/login \ -H "Content-Type: application/json" \ -d "$(jq -n --arg email alex@example.com --arg password "$CORITAN_PASSWORD" '{email: $email, password: $password}')" \ > login.jsonWithout two-factor authentication,
login.jsonholds your tokens:JSON{ "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "token_type": "bearer", "expires_in": 1800 }expires_inis how many seconds the access token lasts.If you use two-factor authentication, the answer asks for your code instead:
JSON{"mfa_required": true, "mfa_setup_required": false, "mfa_token": "eyJhbGciOiJIUzI1NiIs...", "expires_in": 600, "token_type": "bearer"}Send the code from your app with
POST /auth/mfa/verifywithin ten minutes, withmfa_tokenas the bearer token. A recovery code works too, once. The answer holds the same tokens as a sign-in without a second factor:ShellMFA_TOKEN=$(jq -r .mfa_token login.json) curl -s -X POST https://api.coritan.com/api/v1/auth/mfa/verify \ -H "Authorization: Bearer $MFA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"code": "123456"}' \ > login.jsonKeep both tokens in variables. The access token takes the key's place in
CORITAN_TOKEN:Shellexport CORITAN_TOKEN=$(jq -r .access_token login.json) CORITAN_REFRESH=$(jq -r .refresh_token login.json)echo "$CORITAN_TOKEN"now prints a long string that starts witheyJ. Keeplogin.jsonprivate or delete it: anyone with these tokens can use your account.Ask for your profile again.
GET /auth/menow returns the account the token belongs to:Shellcurl -s https://api.coritan.com/api/v1/auth/me \ -H "Authorization: Bearer $CORITAN_TOKEN"JSON{ "id": 4821, "email": "alex@example.com", "first_name": "Alex", "last_name": null, "company": null, "status": "active", "billing_mode": "prepaid", "credit_balance": 25.0, "currency": "USD", "country_code": "GB", "currency_source": "default", "created_at": "2026-09-01T10:15:00" }The
emailis yours, which shows the token works. Update your profile describes each field.
5. Renew the access token
Section titled 5. Renew the access tokenWhen the access token runs out, every request answers 401 with Invalid or expired token. Exchange the refresh token for a new pair with POST /auth/refresh. It needs no password, no bot check and no second factor:
curl -s -X POST https://api.coritan.com/api/v1/auth/refresh \
-H "Content-Type: application/json" \
-d "$(jq -n --arg token "$CORITAN_REFRESH" '{refresh_token: $token}')" \
> login.json
export CORITAN_TOKEN=$(jq -r .access_token login.json)
CORITAN_REFRESH=$(jq -r .refresh_token login.json)
The answer has the same fields as a sign-in. Keep the new refresh_token each time: it starts a full lifetime of its own. The one you sent works once. Sending it again more than a minute later signs the session out (Sessions). When a refresh token has expired too, sign in again as in section 4.
Run the GET /auth/me request from section 4 again. It answers with your account, using the new token.
Troubleshooting
Section titled Troubleshooting401Invalid or expired tokenwith act_key- The key is wrong, or it was revoked. Check that
$CORITAN_TOKENholds the whole key, or create a new one. 403with"code": "api_key_read_only"- The key has only Read permissions. Create a key with a Write permission for a script that makes changes.
403with"code": "api_key_route_refused"- No key can reach that route. Sign in as in section 4, or use the dashboard.
401Invalid email or password- The email or the password is wrong. The answer is the same for both.
403Account is suspended or closed- The account cannot sign in or use a key. Contact support.
401That code is not right- The code is wrong, or you used it already: each code from the app works once. Wait for the next one.
401Invalid or expired tokenfrom/auth/mfa/verify- The
mfa_tokenis more than ten minutes old. Sign in again. 401with"error": "mfa_required"- You sent the
mfa_tokento another endpoint. Finish section 4 first. 401Token invalidated by password change- Someone changed the account's password after the token was issued. Sign in again with the new password.
403Not authenticated- The request has no
Authorizationheader. Check that$CORITAN_TOKENis set in the shell you are using. 429Too many authentication attempts. Please try again later.- Too many sign-in or refresh attempts failed from your address. Wait a minute, as the
Retry-Afterheader says.
Related
Section titled Related- Manage API keys covers what a key can and cannot do.
- Authentication covers the token rules and the other kinds of credential.
- Errors and Rate limits describe what the API answers when a request fails.
- Sign out and end sessions explains how to stop a token working.
- The Client API reference lists every endpoint. api.coritan.com/docs lets you try them in the browser.
API operations on this page
| Method | Path | What it does |
|---|---|---|
GET | /api/v1/auth/turnstile | Turnstile config |
POST | /api/v1/auth/login | Login |
POST | /api/v1/auth/mfa/verify | Second step of signing in |
GET | /api/v1/auth/me | The signed-in account |
GET | /api/v1/services/ | List services |
POST | /api/v1/auth/refresh | A new token pair for a refresh token, which is then used up (app/domains/auth/sessions.py) |