# Make your first API request

> Get an access token, list your services and refresh the token when it expires, with curl commands you can copy.

Source: https://docs.coritan.com/get-started/first-steps-with-the-api/

This tutorial takes you from your email and password to a working API session in a terminal. You sign in, read your account, list your services and renew the access token when it runs out. Every request goes to `https://api.coritan.com/api/v1`.

## Before you begin

- A Coritan account with a password. [Create an account](/get-started/create-an-account/) if you have none.
- `curl`, and `jq` to read values out of the JSON answers.
- Your authenticator app, if you turned on [two-factor authentication](/account/two-factor-authentication/).

> [!IMPORTANT]
> The API accepts the access token that signing in returns. It does not accept the keys from **Settings**, **API keys** (they start with `ct_`): a request that sends one answers `401`. [Manage API keys](/account/api-keys/) explains what the keys are for today.

## 1. Check whether sign-in needs a challenge

The sign-in page can ask for a bot check. Ask the API whether it is on:

```bash
curl https://api.coritan.com/api/v1/auth/turnstile
```

```json
{"enabled": false, "site_key": ""}
```

When `enabled` is `false`, an email and a password are enough to sign in, and you can go on to the next section. When it is `true`, `POST /auth/login` also needs a `turnstile_token` that only the check on the [sign-in page](https://www.coritan.com/login) produces. Without one it answers `403` with `{"detail": {"error": "turnstile_failed", "message": "Verification required"}}`, so a script cannot sign in on its own.

You now know whether your password is enough to sign in from a terminal.

## 2. Sign in and keep the tokens

1. Read your password into a variable, so it stays out of your shell history:

   ```bash
   read -rs -p "Password: " CORITAN_PASSWORD; echo
   ```

2. Sign in with [`POST /auth/login`](/api/reference/client/authentication/#op-post-api-v1-auth-login). `jq` builds the JSON body, so a password with quotes or other special characters is sent intact:

   ```bash
   curl -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.json
   ```

   Without two-factor authentication, `login.json` holds your tokens:

   ```json
   {
     "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
     "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
     "token_type": "bearer",
     "expires_in": 1800
   }
   ```

   `expires_in` is how many seconds the access token lasts.

3. 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/verify`](/api/reference/client/authentication-mfa/#op-post-api-v1-auth-mfa-verify) within ten minutes, with `mfa_token` as the bearer token. A recovery code works too, once. The answer holds the same tokens as a sign-in without a second factor:

   ```bash
   MFA_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.json
   ```

4. Keep both tokens in variables:

   ```bash
   export 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 with `eyJ`. Keep `login.json` private or delete it: anyone with these tokens can use your account.

## 3. Read your account

Send the access token in the `Authorization` header of every request. [`GET /auth/me`](/api/reference/client/authentication/#op-get-api-v1-auth-me) returns the account the token belongs to:

```bash
curl -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 `email` is yours, which shows the token works. [Update your profile](/account/profile/#with-the-api) describes each field.

## 4. List your services

[`GET /services/`](/api/reference/client/services/#op-get-api-v1-services) returns a JSON array of the services on your account, newest first. This prints one line for each of the first five:

```bash
curl -s "https://api.coritan.com/api/v1/services/?limit=5" \
  -H "Authorization: Bearer $CORITAN_TOKEN" \
  | jq -c '.[] | {id, hostname, status, product_name}'
```

```json
{"id":1042,"hostname":"web-1.example.com","status":"active","product_name":"Cloud Compute 2 GB"}
```

An account with no services prints nothing. You see the same services as on the [Services page](https://www.coritan.com/dashboard/services). [Manage your services](/get-started/services/#with-the-api) lists the filters and fields.

## 5. Renew the access token

When 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`](/api/reference/client/authentication/#op-post-api-v1-auth-refresh). It needs no password, no bot check and no second factor:

```bash
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, while the one you sent keeps its old expiry. When a refresh token has expired too, sign in again as in section 2.

Run the `GET /auth/me` request from section 3 again. It answers with your account, using the new token.

## Troubleshooting

`401` `Invalid email or password`
: The email or the password is wrong. The answer is the same for both.

`403` `Account is suspended or closed`
: The account cannot sign in. [Contact support](/support/).

`401` `That 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.

`401` `Invalid or expired token` from `/auth/mfa/verify`
: The `mfa_token` is more than ten minutes old. Sign in again.

`401` with `"error": "mfa_required"`
: You sent the `mfa_token` to another endpoint. Finish section 2 first.

`401` `Token invalidated by password change`
: Someone changed the account's password after the token was issued. Sign in again with the new password.

`403` `Not authenticated`
: The request has no `Authorization` header. Check that `$CORITAN_TOKEN` is set in the shell you are using.

`429` `Too many authentication attempts. Please try again later.`
: Too many sign-in or refresh attempts failed from your address. Wait a minute, as the `Retry-After` header says.

## Related

- [Authentication](/api/authentication/) covers the token rules and the other kinds of credential.
- [Errors](/api/errors/) and [Rate limits](/api/rate-limits/) describe what the API answers when a request fails.
- [Sign out and end sessions](/account/sessions/) explains how to stop a token working.
- The [Client API reference](/api/reference/client/services/) lists every endpoint. [api.coritan.com/docs](https://api.coritan.com/docs) lets you try them in the browser.

## API

- `GET /api/v1/auth/turnstile`: Turnstile config (https://docs.coritan.com/api/reference/client/authentication/#op-get-api-v1-auth-turnstile)
- `POST /api/v1/auth/login`: Login (https://docs.coritan.com/api/reference/client/authentication/#op-post-api-v1-auth-login)
- `POST /api/v1/auth/mfa/verify`: Second step of signing in (https://docs.coritan.com/api/reference/client/authentication-mfa/#op-post-api-v1-auth-mfa-verify)
- `GET /api/v1/auth/me`: Get me (https://docs.coritan.com/api/reference/client/authentication/#op-get-api-v1-auth-me)
- `GET /api/v1/services/`: List services (https://docs.coritan.com/api/reference/client/services/#op-get-api-v1-services)
- `POST /api/v1/auth/refresh`: Refresh (https://docs.coritan.com/api/reference/client/authentication/#op-post-api-v1-auth-refresh)
