> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs-unsw-v6.advance-uac.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs-unsw-v6.advance-uac.com/_mcp/server.

# Authentication

Every endpoint in this API requires an access token. Tokens come from UAC's Keycloak using
the OAuth 2.0 client credentials grant. It's a machine-to-machine flow, so there's no user to
redirect and no browser involved.

## 1. Get a token

**`cURL`**

```bash title="cURL"
curl -X POST \
  https://keycloak-dev.uac.edu.au:8443/realms/advance_reload/protocol/openid-connect/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "client_id=advance_inst_api_unsw" \
  -d "client_secret=$CLIENT_SECRET"
```

**`Python`**

```python title="Python"
import requests

resp = requests.post(
    "https://keycloak-dev.uac.edu.au:8443/realms/advance_reload/protocol/openid-connect/token",
    data={
        "grant_type": "client_credentials",
        "client_id": "advance_inst_api_unsw",
        "client_secret": CLIENT_SECRET,
    },
)
resp.raise_for_status()
token = resp.json()["access_token"]
```

The response:

```json
{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6...",
  "expires_in": 1800,
  "token_type": "Bearer"
}
```

## 2. Use it

Send the `access_token` as a bearer token on every request:

```bash
curl https://unsw-v6-test.advance-uac.com/institution-api/institution/catalogue/subjects \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

## 3. Reuse it until it expires

`expires_in` is the lifetime in seconds, currently 1800 (30 minutes).

You should NOT:

* Request a new token for every API call as it hammers Keycloak during bulk loads.
* Cache a token forever and ignore its expiry.

Instead, hold the token in memory, note when it expires, and fetch a new one shortly before it does.

> **Note**
>
> Read `expires_in` rather than hardcoding 1800. The realm's token lifetime can
> change without the API changing.

## What the token carries

Your identity travels in the token itself, which has two practical consequences:

* You never pass an `institutionId` in a request. There is no parameter for it.
* Writes are attributed automatically, so audit fields such as a document's `uploadedBy` are
  filled in for you.

## Credentials

UAC issues your `client_id` and `client_secret`. You can't create them yourself, so contact
UAC if you don't have them or a secret needs rotating.

> **Warning**
>
> The client secret has full access to your institution's data, and anything
> holding it can act as your institution. Keep it server-side. It should never
> appear in a browser, a mobile app, a public repository or a CI log.

Use an environment variable or a secrets manager, not a literal in source. In the examples
above, `$CLIENT_SECRET` is read from the environment for exactly this reason.

## Environments

Each environment has its own API host. The Keycloak realm is shared between them, so the same
credentials work against both, and a token minted for one will be accepted by the other. Be
deliberate about which host you point at.

| Environment | API base URL                                           | Swagger UI                                                                 |
| ----------- | ------------------------------------------------------ | -------------------------------------------------------------------------- |
| Test        | `https://unsw-v6-test.advance-uac.com/institution-api` | [open](https://unsw-v6-test.advance-uac.com/institution-api/q/swagger-ui/) |
| Dev         | `https://unsw-v6-dev.advance-uac.com/institution-api`  | [open](https://unsw-v6-dev.advance-uac.com/institution-api/q/swagger-ui/)  |

Each environment also serves its raw OpenAPI document at `/q/openapi.json` under the same
base URL, if you'd rather generate a client than read pages.

## When it goes wrong

| Symptom                                 | Likely cause                                                                                    |
| --------------------------------------- | ----------------------------------------------------------------------------------------------- |
| `401` on every call                     | Token missing, malformed, or expired                                                            |
| `401` immediately after a token request | The `Bearer ` prefix is missing, or the whole JSON body was sent instead of just `access_token` |
| `invalid_client` from Keycloak          | Wrong `client_id`/`client_secret`, or the secret has been rotated                               |
| `404` on a record you believe exists    | The `code` and `year` don't match a record, or it hasn't been created yet                       |

For the endpoint reference, see
[Get access token](/unsw-advance-institution-api/get-access-token).