> 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.

# 9 October 2026

> **Warning**
>
> **Three fields on the credit outcome have been renamed.** If you have already built against
> `uoc`, `assessedUoc` or `totalCreditUoc`, those names are gone. See
> [Credit points replace `uoc`](#credit-points-replace-uoc) below — it is a rename only, and nothing about
> the values has changed.

Everything else is additive: an application type you can set and read back, two new application
statuses, two filters on the change feed, more detail on the credit outcome, a set of changes to
supporting documents — including a fix for entries that went missing after a failed upload —
and a list on each application of what it is waiting on.

This page also covers the early-October work, which had no release note of its own.

## Institution API

### Telling us whether an application is domestic or international

> **Warning**
>
> **This field was briefly called `applicantType`.** If you saw that name on a test
> environment, it is now `applicationType` — same values, same meaning. The old name
> said the wrong thing: it belongs to the application, as below.

```json
{ "applicationType": "INTERNATIONAL" }
```

Settable on `POST /institution/applications` and on `PATCH /institution/applications/{id}`,
and returned by every endpoint that reports an application — including the outcome and the
change feed.

Optional everywhere. **"Not stated" is a real answer**, and `null` clears it back to that,
because an intake that recorded a type it should not have has to be able to take it back.

Checked against **your own configured list** rather than a fixed pair, so a value the
management portal offers is a value this API accepts. Case is forgiven. An unknown value is a
`400` naming what is on offer:

```json
{
  "status": 400,
  "error": "'OVERSEAS' is not one of this institution's application_type values: DOMESTIC, INTERNATIONAL"
}
```

> **Note**
>
> **It belongs to the APPLICATION, not the applicant** — which is what the name now says. The
> same person can be domestic for one application and international for the next: a visa granted
> in between, a citizenship that came through. A single value on the person would make their
> older application claim something untrue about the day it was lodged. There is no
> applicant-level equivalent, deliberately.

`applicationType` is the only field validated this way. `stream` and `sourceName` have configured
lists too, but those drive the portal's pickers — this API accepts any value for them, so a
typo is stored rather than refused.

### Credit points replace `uoc`

The outcome endpoint spoke in "UOC", which is one institution's word for it. The fields now say
what they hold:

| Was              | Now                    | Where       |
| ---------------- | ---------------------- | ----------- |
| `uoc`            | `creditPoints`         | each credit |
| `assessedUoc`    | `assessedCreditPoints` | each credit |
| `totalCreditUoc` | `totalCreditPoints`    | the outcome |

Nothing else about them changed — same values, same meaning, same rules about what counts.
`totalCreditPoints` is still a plain sum of the credits below it, and `assessedCreditPoints`
still appears only when an officer changed the number.

The rename reaches both `GET /institution/applications/{id}/outcome` and the outcome carried on
`/institution/applications/changes`.

### Two more application statuses

`ON_HOLD` and `FINALISED` join the five you already have:

| Status      | Meaning                                              |
| ----------- | ---------------------------------------------------- |
| `ON_HOLD`   | Paused, usually waiting on something outside Advance |
| `FINALISED` | You are done with it                                 |

Two things worth knowing before you use them.

**`ON_HOLD` does not pause an assessment.** If one is running it carries on — the status is a
signal to the people working on the application, exactly as `READY` is. Nothing in Advance stops
on it.

**`FINALISED` is not terminal.** An application can still be withdrawn afterwards, which is what
happens when an applicant pulls out after a decision. `WITHDRAWN` remains the only terminal
status.

### Setting a status, and saying why

```
POST /institution/applications/{id}/status
```

```json
{ "status": "ON_HOLD", "reason": "awaiting transcript from Ngee Ann Polytechnic" }
```

Takes any status except `WITHDRAWN`, with an optional `reason`. The reason is kept on the
application and on its status history, so "why was this held, and when" has an answer later.

Setting a status the application already has succeeds and changes nothing, so a retry is safe.

`WITHDRAWN` keeps its own endpoint because withdrawing also closes the assessment — setting the
status alone would leave an assessment that still believes it is running.
`/institution/applications/{id}/ready` also stays, and now accepts an optional `reason` as well.
It still works with no body at all, so an existing integration needs no change.

### Filtering the change feed by status

```
GET /institution/applications/changes?updatedSince=…&status=ON_HOLD&status=FAILED
```

Repeat the parameter, or send one comma-separated value. Omit it for every status. Case is
forgiven.

The filter applies before paging, so a page is a full page of matches and `hasMore` means what
it says.

> **Note**
>
> An unrecognised status is a `400` naming the seven that exist, rather than an empty page. On a
> change feed an empty page means "nothing has changed", so a typo would otherwise look like a
> working, quiet feed.

### More on the credit outcome

The outcome now reports the application it belongs to, so storing outcomes no longer needs a
second call to know what the credit was awarded against:

* `programCode`, `specialisationCodes`, `intakeCode`, `applicationType`
* `applicantSources` and `applicationSources` — your own references, kept apart, because an
  application number is not a student number
* `organisationName` and `organisationCode` on each credit source — the institution the prior
  study was done at

The organisation is joined from the agreement behind the credit rather than stored with it, so
it reflects that organisation's current name. Both are `null` where there is no agreement: an
exchange mapping, or credit an officer awarded outright.

All of it appears on the change feed too.

## What an application is waiting on

`ON_HOLD` tells you an application is paused. It does not tell you what we are waiting for, and
the `reason` beside it is written by an assessor for their own records — not something you can
put in front of an applicant.

Each application now carries `pendingActions`: the individual things we need, written to be
read by whoever has to supply them.

```json
{
  "status": "ON_HOLD",
  "pendingActions": [
    {
      "id": "6f1c…",
      "actionType": "DOCUMENT_REQUIRED",
      "description": "Certified transcript for the Diploma of Business",
      "state": "OPEN",
      "qualificationId": "829787ff…",
      "raisedAt": "2026-10-09T11:40:22+11:00",
      "resolvedAt": null
    }
  ]
}
```

| Field              |                                                                               |
| ------------------ | ----------------------------------------------------------------------------- |
| `actionType`       | `DOCUMENT_REQUIRED`, `CLARIFICATION`, `VERIFICATION` or `OTHER`               |
| `description`      | What is needed, in the assessor's words                                       |
| `state`            | `OPEN`, `RESOLVED`, or `CANCELLED` if it turned out not to be needed          |
| `qualificationId`  | Which qualification it is about, where it is about one in particular          |
| `resolvedBySource` | `institution_api` if your integration closed it, `officer` if an assessor did |

It appears on `GET /institution/applications/{id}` and on every item in the change feed.

To poll for just the applications that are waiting on something:

```
GET /institution/applications/changes?updatedSince=…&pendingActions=open
```

`open` returns applications with at least one outstanding action, `none` returns those with
none, and omitting it returns both. Case is forgiven, and an unrecognised value is a `400`
rather than an unfiltered feed.

> **Note**
>
> **Use this rather than `?status=ON_HOLD`.** An assessor can raise an action without putting
> the application on hold, so the status filter would miss it — the two are independent, as
> below.

> **Note**
>
> **An empty list is the normal case, not an error.** Most applications are waiting on nothing.

> **Warning**
>
> **`pendingActions` and `status` are independent.** An application can have open actions without
> being `ON_HOLD`, and can be `ON_HOLD` with none — whether an application is workable is a
> judgement an assessor makes, not something that follows from a list being empty. Do not infer
> one from the other, and filter on `pendingActions` rather than on `status` when what you want
> is "waiting on something".

### Supplying what was asked for

Attach documents as you would normally — the [one-call upload](#uploading-in-one-call) below is
usually the simplest — and send any new qualifications or corrections the usual way.

Then tell us you have responded:

```
POST /institution/applications/{id}/pending-actions/{actionId}/resolve
```

```json
{ "note": "Certified transcript uploaded 9 October" }
```

The `note` is optional and is kept against the action for the assessor to read. A bodyless
`POST` works too.

Send it once you have actually supplied the thing. **Resolving an action does not move any
information** — it is the signal, not the delivery.

### Adding a qualification after an assessment has started

```
POST /institution/applications/{id}/qualifications
```

This now returns a **`409` once an application has been assessed**, where it previously returned
`200`.

It was never doing anything useful: an assessment works from a snapshot of the qualifications
taken when it began, so one added afterwards was stored and never read. The credit could not
change, and the `200` said otherwise.

If late evidence changes what an applicant is entitled to, submit a new application — the same
answer as for a programme corrected after assessment. See [Correcting records](/corrections).

Documents are unaffected: you can attach them at any time, and an assessor reads them directly.

Safe to retry: resolving an action that is already closed returns it unchanged rather than
failing, and leaves the original resolution in place, so a call you are unsure landed can be
repeated.

An assessor can also close an action from their side, which is why `resolvedBySource` exists —
`institution_api` means your integration closed it, `officer` means an assessor did.

> **Warning**
>
> **Supplying information does not restart an assessment**, and no endpoint does. In practice
> none is needed: asking for more information does not stop an assessment, so there is nothing
> waiting to be restarted — the assessor picks the application back up when the evidence
> arrives. What does need care is an application whose assessment has already FINISHED. New
> information then cannot change the outcome, and the answer is the same as for a wrong
> programme: a new application. See [Correcting records](/corrections).

## Course topics, in the management portal

Some courses are assessed in parts rather than as a whole — COMM1100 as Economics, Law
and Management — and the institution's assessors can now record those parts, with the
guidance for deciding whether a piece of prior study covers each one.

There is **nothing to call and nothing you send changes**: this is the institution's own
configuration, and the application and outcome shapes are untouched. It is noted here
because it explains a vocabulary you will hear in conversation about RPL, and because
one external course commonly covers topics belonging to several different courses at
once.

See [The catalogue](/management-portal/catalogue) for what a topic holds.

## Supporting documents

### Entries whose upload failed are no longer hidden

Creating a document entry and uploading its bytes are two separate calls, and the second can
fail on its own — a timeout, a dropped connection, an expired URL. Until now, an entry whose
bytes never arrived was left out of `GET .../documents` entirely, so there was no way to find
it and retry. Worse, it was deleted half an hour later.

Those entries are now listed, and every document carries a `status`:

| `status`   | Meaning                                                           |
| ---------- | ----------------------------------------------------------------- |
| `UPLOADED` | The bytes are stored. `sizeBytes` is set and it can be downloaded |
| `PENDING`  | The entry exists, the file does not. `sizeBytes` is `null`        |

**Nothing is deleted on a timer any more.** A `PENDING` entry stays until you upload its bytes
or delete it, however long that takes — your retry may be on tomorrow's run, not within the hour.

> **Note**
>
> `uploadedAt` on a `PENDING` document is when the ENTRY was created. Nothing has been uploaded,
> so there is no upload time to report.

### Retrying a failed upload

```
POST /institution/applications/{id}/documents/{documentId}/upload-url
```

Returns a fresh `uploadUrl` and `requiredHeaders` for an entry whose bytes never arrived, against
the **same document id** — a retry does not create a duplicate. Use it whenever the original URL
has expired or its `PUT` failed. The `PUT` itself is unchanged.

A document that has already been uploaded gets a `409`: replacing a stored file means deleting
the entry and creating a new one, rather than having a retry quietly overwrite it.

### Deleting a document

```
DELETE /institution/applications/{id}/documents/{documentId}
```

Removes the entry and its stored file, and works on a `PENDING` entry as well as an `UPLOADED`
one. This is how an abandoned entry gets cleared now that nothing expires on its own.

### Uploading in one call

```
POST /institution/applications/{id}/documents/content
```

Send the file as a `multipart/form-data` part named `file` and it is stored in a single call —
no entry to create first, no presigned URL, no second request. The response is the document
itself, already `UPLOADED`.

```bash
curl -X POST "$API/institution/applications/$ID/documents/content" \
  -H "Authorization: Bearer $TOKEN" \
  -F "file=@transcript.pdf"
```

The filename and content type are taken from the part.

**Limited to 10MB.** Above that you get a `413`, and the two-step flow is the answer — it sends
the bytes straight to storage without passing through this API, so it has no such limit. Both
flows produce the same document: nothing downstream can tell which one was used.

> **Note**
>
> All four changes apply to qualification documents too, at
> `/institution/qualifications/{qualificationId}/documents/...`.

## Nothing to do

Beyond the rename, every change is additive. An integration that ignores the new fields and
statuses behaves exactly as it does today.

Two behaviours to be aware of rather than act on:

`GET .../documents` now returns `PENDING` entries it previously omitted. If you treat everything
in that list as downloadable, check `status` first — a `PENDING` document has no bytes and its
download returns a `404`.

`pendingActions` is present on every application, as an empty list where there is nothing
outstanding. An integration that ignores the field is unaffected.

The one change that is **not** additive is the `409` on adding a qualification to an assessed
application. If you do that today you are getting a `200` and no effect, so nothing that works
will stop working — but a call that silently did nothing now fails loudly.