> 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/authentication/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). > Getting a token, using it, and what it grants you