Skip to content

Core API ​

Base URL: /api/v1/core/

System reference data including lookup values, currencies, institution settings, countries, email templates, credit conversions, and health checks.

Endpoints Overview ​

Lookup Values ​

MethodEndpointAuthDescription
GET/lookup-typesYesList lookup types
GET/lookup-types/by-code/{code}YesGet lookup type by code
POST/lookup-valuesYesCreate lookup value
GET/lookup-values/by-type/{typeCode}YesGet values for a type
PATCH/lookup-values/{typeCode}/{id}YesUpdate lookup value
DELETE/lookup-values/{typeCode}/{id}YesDelete lookup value
GET/lookupsNoGet all lookups (frontend alias)
GET/lookups/by-type/{typeCode}NoGet values by type (frontend alias)

Currencies ​

MethodEndpointAuthDescription
GET/currenciesYesList currencies
POST/currenciesYesCreate currency
GET/currencies/{id}YesGet currency details
PATCH/currencies/{id}YesUpdate currency
DELETE/currencies/{id}YesDelete currency

Institution Settings ​

MethodEndpointAuthDescription
GET/settingsYesList all settings
POST/settingsYesCreate setting
GET/settings/{id}YesGet setting by ID
PATCH/settings/{id}YesUpdate setting by ID
GET/settings/brandingNoGet branding settings
GET/settings/by_keyYesGet setting by key
PATCH/settings/by_keyYesUpdate setting by key
PUT/settings/{key}YesUpsert setting by key
POST/settings/bulk_updateYesUpdate multiple settings
PATCH/settings/bulk_updateYesUpdate multiple settings (alternative)
GET/settings/by_categoryYesGet settings by category

Countries ​

MethodEndpointAuthDescription
GET/countriesNoList countries
GET/countries/dropdownNoMinimal country list for dropdowns
GET/countries/{id}NoGet country details
POST/countriesYesCreate country
PATCH/countries/{id}YesUpdate country
DELETE/countries/{id}YesDelete country

Email Templates ​

MethodEndpointAuthDescription
GET/email-templatesYesList email templates
POST/email-templatesYesCreate email template
GET/email-templates/by-code/{code}YesGet template by code
GET/email-templates/{id}YesGet template by ID
PATCH/email-templates/{id}YesUpdate email template
DELETE/email-templates/{id}YesDelete email template
GET/email-templates/attachments/{id}YesA template's fixed attachments, its allowance and the limits that apply
POST/email-templates/attachments/{id}YesAdd files to a template (multipart)
PUT/email-templates/attachments/{id}/settingsYesSet the template's attachment allowance
GET/email-templates/attachments/{id}/{attachmentID}YesDownload one attached file
DELETE/email-templates/attachments/{id}/{attachmentID}YesRemove one attached file

Credit Conversions ​

MethodEndpointAuthDescription
GET/credit-conversionsYesList credit system conversions
POST/credit-conversionsYesCreate conversion rule
POST/credit-conversions/convertYesConvert credits between systems
PUT/credit-conversions/{conversionID}YesUpdate conversion rule
DELETE/credit-conversions/{conversionID}YesDelete conversion rule

Health ​

MethodEndpointAuthDescription
GET/healthYesHealth check

Lookup Values ​

The lookup system provides reference data for dropdowns, statuses, and types throughout the application.

Get All Lookups ​

GET /api/v1/core/lookups

Get all lookup types with their active values. Publicly accessible.

Query Parameters ​

ParameterTypeDescription
type_codesstringComma-separated list of type codes to filter

Example Request ​

bash
# Get all lookups
curl -X GET http://localhost:8000/api/v1/core/lookups

# Get specific types
curl -X GET "http://localhost:8000/api/v1/core/lookups?type_codes=TERM_TYPE,COURSE_STATUS,NAME_TYPE"

Success Response (200 OK) ​

json
{
  "success": true,
  "data": [
    {
      "lookup_type_id": 1,
      "type_code": "TERM_TYPE",
      "description": "Academic term types",
      "values": [
        {
          "lookup_value_id": 1,
          "code": "FALL",
          "display_value": "Fall",
          "sort_order": 1
        },
        {
          "lookup_value_id": 2,
          "code": "SPRING",
          "display_value": "Spring",
          "sort_order": 2
        },
        {
          "lookup_value_id": 3,
          "code": "SUMMER",
          "display_value": "Summer",
          "sort_order": 3
        }
      ]
    },
    {
      "lookup_type_id": 2,
      "type_code": "COURSE_STATUS",
      "description": "Course statuses",
      "values": [
        {
          "lookup_value_id": 10,
          "code": "ACTIVE",
          "display_value": "Active",
          "sort_order": 1
        },
        {
          "lookup_value_id": 11,
          "code": "INACTIVE",
          "display_value": "Inactive",
          "sort_order": 2
        }
      ]
    }
  ]
}

TypeScript Types ​

typescript
interface LookupValue {
  lookup_value_id: number
  code: string
  display_value: string
  sort_order: number
}

interface LookupType {
  lookup_type_id: number
  type_code: string
  description: string | null
  values: LookupValue[]
}

Get Values by Type Code ​

GET /api/v1/core/lookups/by-type/{typeCode}

Get all active values for a specific lookup type. Publicly accessible.

Path Parameters ​

ParameterTypeDescription
typeCodestringThe lookup type code

Example Request ​

bash
curl -X GET http://localhost:8000/api/v1/core/lookups/by-type/GENDER

Success Response (200 OK) ​

json
{
  "success": true,
  "data": [
    {
      "lookup_value_id": 20,
      "lookup_type_id": 5,
      "type_code": "GENDER",
      "code": "M",
      "display_value": "Male",
      "is_active": true,
      "sort_order": 1
    },
    {
      "lookup_value_id": 21,
      "lookup_type_id": 5,
      "type_code": "GENDER",
      "code": "F",
      "display_value": "Female",
      "is_active": true,
      "sort_order": 2
    },
    {
      "lookup_value_id": 22,
      "lookup_type_id": 5,
      "type_code": "GENDER",
      "code": "NB",
      "display_value": "Non-Binary",
      "is_active": true,
      "sort_order": 3
    }
  ]
}

Error Response (404 Not Found) ​

json
{
  "success": false,
  "error": {
    "code": "lookup_type_not_found",
    "message": "Lookup type \"INVALID_TYPE\" not found"
  }
}

List Lookup Types ​

GET /api/v1/core/lookup-types

List all lookup types with value counts.

Query Parameters ​

ParameterTypeDescription
searchstringSearch in type_code, description
orderingstringSort by: type_code

Example Request ​

bash
curl -X GET http://localhost:8000/api/v1/core/lookup-types \
  -b cookies.txt

Success Response (200 OK) ​

json
{
  "count": 25,
  "next": null,
  "previous": null,
  "results": [
    {
      "lookup_type_id": 1,
      "type_code": "TERM_TYPE",
      "description": "Academic term types",
      "is_system": true,
      "value_count": 3,
      "created_at": "2024-01-01T00:00:00Z",
      "updated_at": "2024-01-01T00:00:00Z"
    }
  ]
}

Get Lookup Type by Code ​

GET /api/v1/core/lookup-types/by-code/{code}

Get a specific lookup type by its code, including its values.

Path Parameters ​

ParameterTypeDescription
codestringThe lookup type code

Example Request ​

bash
curl -X GET http://localhost:8000/api/v1/core/lookup-types/by-code/TERM_TYPE \
  -b cookies.txt

Get Values for a Type ​

GET /api/v1/core/lookup-values/by-type/{typeCode}

Get all values for a specific lookup type.

Path Parameters ​

ParameterTypeDescription
typeCodestringThe lookup type code

Example Request ​

bash
curl -X GET http://localhost:8000/api/v1/core/lookup-values/by-type/ENROLLMENT_STATUS \
  -b cookies.txt

Success Response (200 OK) ​

json
{
  "count": 5,
  "next": null,
  "previous": null,
  "results": [
    {
      "lookup_value_id": 100,
      "lookup_type_id": 15,
      "type_code": "ENROLLMENT_STATUS",
      "code": "REGISTERED",
      "display_value": "Registered",
      "is_active": true,
      "sort_order": 1,
      "created_at": "2024-01-01T00:00:00Z",
      "updated_at": "2024-01-01T00:00:00Z"
    }
  ]
}

Create Lookup Value ​

POST /api/v1/core/lookup-values

Create a new lookup value.

Example Request ​

bash
curl -X POST http://localhost:8000/api/v1/core/lookup-values \
  -b cookies.txt \
  -H "Content-Type: application/json" \
  -d '{
    "type_code": "ENROLLMENT_STATUS",
    "code": "WITHDRAWN",
    "display_value": "Withdrawn",
    "sort_order": 6
  }'

Update Lookup Value ​

PATCH /api/v1/core/lookup-values/{typeCode}/{id}

Update an existing lookup value.

Path Parameters ​

ParameterTypeDescription
typeCodestringThe lookup type code
idintegerThe lookup value ID

Example Request ​

bash
curl -X PATCH http://localhost:8000/api/v1/core/lookup-values/ENROLLMENT_STATUS/100 \
  -b cookies.txt \
  -H "Content-Type: application/json" \
  -d '{
    "display_value": "Registered (Active)",
    "sort_order": 1
  }'

Delete Lookup Value ​

DELETE /api/v1/core/lookup-values/{typeCode}/{id}

Delete a lookup value.

Path Parameters ​

ParameterTypeDescription
typeCodestringThe lookup type code
idintegerThe lookup value ID

Example Request ​

bash
curl -X DELETE http://localhost:8000/api/v1/core/lookup-values/ENROLLMENT_STATUS/100 \
  -b cookies.txt

Common Lookup Types ​

Type CodeDescription
TERM_TYPEAcademic term types (Fall, Spring, Summer)
COURSE_STATUSCourse statuses (Active, Inactive)
PROGRAM_STATUSProgram statuses
BLOCK_TYPERequirement block types
REQUISITE_TYPECourse requisite types (Prerequisite, Corequisite)
NAME_TYPEPerson name types (Legal, Preferred, Maiden)
ADDRESS_TYPEAddress types (Home, Mailing, Permanent)
PHONE_TYPEPhone types (Mobile, Home, Work)
EMAIL_TYPEEmail types (Personal, Work, Institutional)
GENDERGender options
ETHNICITYEthnicity categories
RACERace categories
ENROLLMENT_STATUSEnrollment statuses
GRADEGrade values
INSTRUCTION_MODEInstruction modes (In Person, Online, Hybrid)
SECTION_STATUSSection statuses (Open, Closed, Waitlist)

Currencies ​

List Currencies ​

GET /api/v1/core/currencies

List all currencies. By default returns only active currencies.

Query Parameters ​

ParameterTypeDescription
is_activebooleanFilter by active status (default: true)
searchstringSearch in iso_code, name
orderingstringSort by: iso_code, name

Example Request ​

bash
curl -X GET http://localhost:8000/api/v1/core/currencies \
  -b cookies.txt

Success Response (200 OK) ​

json
{
  "count": 3,
  "next": null,
  "previous": null,
  "results": [
    {
      "currency_id": 1,
      "iso_code": "USD",
      "name": "US Dollar",
      "symbol": "$",
      "decimal_places": 2,
      "is_active": true,
      "created_at": "2024-01-01T00:00:00Z",
      "updated_at": "2024-01-01T00:00:00Z"
    },
    {
      "currency_id": 2,
      "iso_code": "EUR",
      "name": "Euro",
      "symbol": "\u20ac",
      "decimal_places": 2,
      "is_active": true,
      "created_at": "2024-01-01T00:00:00Z",
      "updated_at": "2024-01-01T00:00:00Z"
    }
  ]
}

TypeScript Types ​

typescript
interface Currency {
  currency_id: number
  iso_code: string
  name: string
  symbol: string
  decimal_places: number
  is_active: boolean
}

Institution Settings ​

Get Branding Settings ​

GET /api/v1/core/settings/branding

Get all branding-related settings. Publicly accessible.

Example Request ​

bash
curl -X GET http://localhost:8000/api/v1/core/settings/branding

Success Response (200 OK) ​

json
{
  "success": true,
  "data": {
    "institution_name": "University of Example",
    "institution_short_name": "UOE",
    "system_name": "Student Information System",
    "logo_url": "/static/images/logo.png",
    "logo_dark_url": "/static/images/logo-dark.png",
    "primary_color": "#1E40AF",
    "secondary_color": "#7C3AED",
    "accent_color": "#10B981",
    "institution_address_line1": "123 University Ave",
    "institution_city": "Example City",
    "institution_state": "EX",
    "institution_postal_code": "12345",
    "institution_phone": "(555) 123-4567",
    "institution_email": "info@university.edu",
    "institution_website": "https://university.edu"
  }
}

Get Setting by Key ​

GET /api/v1/core/settings/by_key?key={setting_key}

Get a specific setting by its key.

Query Parameters ​

ParameterTypeRequiredDescription
keystringYesThe setting key

Example Request ​

bash
curl -X GET "http://localhost:8000/api/v1/core/settings/by_key?key=GPA_DECIMAL_PLACES" \
  -b cookies.txt

Success Response (200 OK) ​

json
{
  "success": true,
  "data": {
    "setting_id": 15,
    "setting_key": "GPA_DECIMAL_PLACES",
    "setting_value": "2",
    "typed_value": 2,
    "setting_type": "number",
    "description": "Number of decimal places for GPA display",
    "created_at": "2024-01-01T00:00:00Z",
    "updated_at": "2024-03-15T10:30:00Z"
  }
}

TypeScript Types ​

typescript
interface InstitutionSetting {
  setting_id: number
  setting_key: string
  setting_value: string | null
  typed_value: any
  setting_type: 'string' | 'number' | 'boolean' | 'json'
  description: string | null
}

EMAIL_NOREPLY_ADDRESS is validated

The no-reply sender (migration 268) is the From of every system notification, and staff whose role allows it may send from it. Every write path — create, update by id or key, upsert, bulk update — refuses a value that is blank, is not a plain email address, or is not at an active, protected institution domain, with 400 and field: setting_value. A bulk update validates every key before writing any, so a bad value does not leave the batch half applied. See Communications.


Get Setting by ID ​

GET /api/v1/core/settings/{id}

Get a specific setting by its ID.

Path Parameters ​

ParameterTypeDescription
idintegerThe setting ID

Example Request ​

bash
curl -X GET http://localhost:8000/api/v1/core/settings/15 \
  -b cookies.txt

Create Setting ​

POST /api/v1/core/settings

Create a new institution setting.

Request Body ​

FieldTypeRequiredDescription
setting_keystringYesUnique setting key
setting_valuestringYesThe setting value
setting_typestringNoType: string, number, boolean, json
descriptionstringNoDescription of the setting

Example Request ​

bash
curl -X POST http://localhost:8000/api/v1/core/settings \
  -b cookies.txt \
  -H "Content-Type: application/json" \
  -d '{
    "setting_key": "MAX_WAITLIST_SIZE",
    "setting_value": "25",
    "setting_type": "number",
    "description": "Maximum number of students on a section waitlist"
  }'

Update Setting by ID ​

PATCH /api/v1/core/settings/{id}

Update a setting by its ID.

Path Parameters ​

ParameterTypeDescription
idintegerThe setting ID

Example Request ​

bash
curl -X PATCH http://localhost:8000/api/v1/core/settings/15 \
  -b cookies.txt \
  -H "Content-Type: application/json" \
  -d '{
    "setting_value": "3"
  }'

Upsert Setting by Key ​

PUT /api/v1/core/settings/{key}

Create or update a setting by its key. If the setting exists, it is updated; otherwise, it is created.

Path Parameters ​

ParameterTypeDescription
keystringThe setting key

Example Request ​

bash
curl -X PUT http://localhost:8000/api/v1/core/settings/GPA_DECIMAL_PLACES \
  -b cookies.txt \
  -H "Content-Type: application/json" \
  -d '{
    "setting_value": "3",
    "setting_type": "number",
    "description": "Number of decimal places for GPA display"
  }'

Update Setting by Key ​

PATCH /api/v1/core/settings/by_key

Update a setting's value by its key.

Request Body ​

FieldTypeRequiredDescription
keystringYesThe setting key
setting_valuestringYesThe new value

Example Request ​

bash
curl -X PATCH http://localhost:8000/api/v1/core/settings/by_key \
  -b cookies.txt \
  -H "Content-Type: application/json" \
  -d '{
    "key": "GPA_DECIMAL_PLACES",
    "setting_value": "3"
  }'

Bulk Update Settings ​

POST /api/v1/core/settings/bulk_update

Update multiple settings at once.

Request Body ​

FieldTypeRequiredDescription
settingsobjectYesKey-value pairs to update

Example Request ​

bash
curl -X POST http://localhost:8000/api/v1/core/settings/bulk_update \
  -b cookies.txt \
  -H "Content-Type: application/json" \
  -d '{
    "settings": {
      "primary_color": "#1E40AF",
      "secondary_color": "#7C3AED",
      "institution_name": "New University Name"
    }
  }'

Success Response (200 OK) ​

json
{
  "success": true,
  "data": {
    "updated_keys": ["primary_color", "secondary_color", "institution_name"]
  }
}

Get Settings by Category ​

GET /api/v1/core/settings/by_category?category={category}

Get settings grouped by category.

Query Parameters ​

ParameterTypeRequiredDescription
categorystringYesCategory: system or academic

System Settings:

  • SESSION_TIMEOUT_MINUTES
  • PAPER_SIZE

Academic Settings:

  • GPA_REPEAT_POLICY
  • GPA_DECIMAL_PLACES
  • MAX_CREDITS_UNDERGRAD
  • MAX_CREDITS_UNDERGRAD_OVERLOAD
  • MAX_CREDITS_GRADUATE
  • MAX_CREDITS_GRADUATE_OVERLOAD

Countries ​

List Countries ​

GET /api/v1/core/countries

List all countries. Publicly accessible.

Query Parameters ​

ParameterTypeDescription
is_activebooleanFilter by active status (default: true)
visa_requiredbooleanFilter by visa requirement
searchstringSearch in iso_code, country_name
orderingstringSort by: sort_order, country_name, iso_code

Example Request ​

bash
curl -X GET http://localhost:8000/api/v1/core/countries

Success Response (200 OK) ​

json
{
  "count": 195,
  "next": "http://localhost:8000/api/v1/core/countries?page=2",
  "previous": null,
  "results": [
    {
      "country_id": 1,
      "iso_code": "US",
      "iso_code_3": "USA",
      "country_name": "United States",
      "visa_required": false,
      "sort_order": 1,
      "is_active": true,
      "created_at": "2024-01-01T00:00:00Z",
      "updated_at": "2024-01-01T00:00:00Z"
    }
  ]
}

Country Dropdown ​

GET /api/v1/core/countries/dropdown

Get minimal country list for dropdowns. Publicly accessible.

Example Request ​

bash
curl -X GET http://localhost:8000/api/v1/core/countries/dropdown

Success Response (200 OK) ​

json
{
  "success": true,
  "data": [
    {
      "country_id": 1,
      "iso_code": "US",
      "country_name": "United States",
      "visa_required": false
    },
    {
      "country_id": 2,
      "iso_code": "CA",
      "country_name": "Canada",
      "visa_required": false
    }
  ]
}

TypeScript Types ​

typescript
interface Country {
  country_id: number
  iso_code: string
  iso_code_3: string | null
  country_name: string
  visa_required: boolean
  sort_order: number
  is_active: boolean
}

interface CountryMinimal {
  country_id: number
  iso_code: string
  country_name: string
  visa_required: boolean
}

Email Templates ​

List Email Templates ​

GET /api/v1/core/email-templates

List all email templates.

Example Request ​

bash
curl -X GET http://localhost:8000/api/v1/core/email-templates \
  -b cookies.txt

Success Response (200 OK) ​

json
{
  "count": 10,
  "next": null,
  "previous": null,
  "results": [
    {
      "template_id": 1,
      "template_code": "ADMISSION_ACCEPTED",
      "name": "Admission Acceptance Letter",
      "subject": "Congratulations! You have been accepted",
      "body_html": "<p>Dear {{student_name}},</p>...",
      "body_text": "Dear {{student_name}},\n...",
      "is_active": true,
      "created_at": "2024-01-01T00:00:00Z",
      "updated_at": "2024-01-01T00:00:00Z"
    }
  ]
}

TypeScript Types ​

typescript
interface EmailTemplate {
  template_id: number
  template_code: string
  name: string
  subject: string
  body_html: string | null
  body_text: string | null
  is_active: boolean
  created_at: string
  updated_at: string
}

Create Email Template ​

POST /api/v1/core/email-templates

Create a new email template.

Request Body ​

FieldTypeRequiredDescription
template_codestringYesUnique template code
namestringYesTemplate display name
subjectstringYesEmail subject line
body_htmlstringNoHTML email body
body_textstringNoPlain text email body
is_activebooleanNoActive status (default: true)

Example Request ​

bash
curl -X POST http://localhost:8000/api/v1/core/email-templates \
  -b cookies.txt \
  -H "Content-Type: application/json" \
  -d '{
    "template_code": "REGISTRATION_CONFIRMATION",
    "name": "Registration Confirmation",
    "subject": "Registration Confirmed for {{term_name}}",
    "body_html": "<p>Dear {{student_name}},</p><p>Your registration has been confirmed.</p>",
    "body_text": "Dear {{student_name}},\nYour registration has been confirmed."
  }'

Get Email Template by Code ​

GET /api/v1/core/email-templates/by-code/{code}

Get an email template by its unique code.

Path Parameters ​

ParameterTypeDescription
codestringThe template code

Example Request ​

bash
curl -X GET http://localhost:8000/api/v1/core/email-templates/by-code/ADMISSION_ACCEPTED \
  -b cookies.txt

Get Email Template by ID ​

GET /api/v1/core/email-templates/{id}

Get an email template by its ID.

Path Parameters ​

ParameterTypeDescription
idintegerThe template ID

Example Request ​

bash
curl -X GET http://localhost:8000/api/v1/core/email-templates/1 \
  -b cookies.txt

Update Email Template ​

PATCH /api/v1/core/email-templates/{id}

Update an existing email template.

Path Parameters ​

ParameterTypeDescription
idintegerThe template ID

Example Request ​

bash
curl -X PATCH http://localhost:8000/api/v1/core/email-templates/1 \
  -b cookies.txt \
  -H "Content-Type: application/json" \
  -d '{
    "subject": "Updated Subject Line",
    "body_html": "<p>Updated HTML content</p>"
  }'

Delete Email Template ​

DELETE /api/v1/core/email-templates/{id}

Delete an email template.

Path Parameters ​

ParameterTypeDescription
idintegerThe template ID

Example Request ​

bash
curl -X DELETE http://localhost:8000/api/v1/core/email-templates/1 \
  -b cookies.txt

Template Bodies: Tables ​

The template editor has a table toolbar. When a template is used, its body is given fixed inline borders and padding on every table, th and td (email.InlineTableStyles), because mail clients ignore <style> blocks. A style the template already has on those tags is kept after the defaults, so it still wins.


Email Template Attachments ​

Fixed files sent with every use of a template — a form a drop notice refers to, a policy PDF with the probation alert (migration 305). All five routes are gated on ADMIN.EMAIL_TEMPLATES, like the rest of the template editor; no superuser is needed.

Why not /email-templates/{id}/attachments

Go's ServeMux refuses that pattern next to /email-templates/by-code/{code}: neither is more specific than the other. The template id therefore comes after attachments.

What a template may carry is three layers, intersected (types every layer allows, the smallest of each number):

  1. the organizational ceiling, core.email_attachment_limits;
  2. the TEMPLATE surface policy, core.email_attachment_policies — the most any template may allow, and the only source of allowed file types. Both are edited superuser-only through the Communications API;
  3. the template's own allowance, core.email_template_attachment_settings — on/off, file count, per-file and total MB. No row means no attachments.

Files are stored as bytea in core.email_template_attachments, which opts out of row auditing. They are checked when added and again at every send (email.FetchTemplateAttachments) against the limits as they stand then; files that no longer pass are left off that email with a logged warning, and the email still goes.

Get a Template's Attachments ​

GET /api/v1/core/email-templates/attachments/{id}
json
{
  "attachments": [
    { "attachment_id": 4, "filename": "drop-form.pdf", "content_type": "application/pdf", "size_bytes": 92160, "created_at": "2026-10-29T16:40:00Z" }
  ],
  "settings":  { "is_enabled": true, "max_files": 2, "max_file_mb": 2, "max_total_mb": 4 },
  "available": { "is_enabled": true, "allowed_extensions": ["pdf"], "max_file_mb": 2, "max_files": 2, "max_total_mb": 4 },
  "limits":    { "is_enabled": true, "allowed_extensions": ["pdf"], "max_file_mb": 2, "max_files": 2, "max_total_mb": 4 }
}
FieldDescription
settingsThe template's own allowance as saved. For a template never set up it is off, with the numbers prefilled to available
availableThe TEMPLATE surface inside the ceiling: the most this template may be set to. is_enabled: false means no template may take attachments
limitsWhat actually applies: settings inside available

An unknown template is 404.

Set the Allowance ​

PUT /api/v1/core/email-templates/attachments/{id}/settings
json
{ "is_enabled": true, "max_files": 2, "max_file_mb": 2, "max_total_mb": 4 }

Returns the same object as the GET. Turning attachments off is always accepted, and keeps the numbers already saved so that turning them back on restores them. Turning them on needs the TEMPLATE surface on (400 errors.attachmentsNotAllowed otherwise), and each number must fit inside available (per-field validation errors). Files already attached are kept even if they no longer fit; they are left off each email until the allowance covers them again.

Add Files ​

POST /api/v1/core/email-templates/attachments/{id}
Content-Type: multipart/form-data

Each file in a part named files. Files already on the template count toward the number and total size allowed. Checks are those of every attachment: allowed type, never a denied type (executables, macro Office files, HTML/SVG/XML, archives), bytes matching the extension, not empty, within the per-file and total limits. Errors are 400 with message_key errors.attachmentsNotAllowed, errors.tooManyAttachments, errors.fileTypeNotAllowed, errors.fileTooLarge, errors.attachmentsTooLarge, errors.attachmentEmpty or errors.attachmentContentMismatch. Returns 201 with the same object as the GET.

bash
curl -X POST http://localhost:8000/api/v1/core/email-templates/attachments/12 \
  -b cookies.txt \
  -H "X-CSRF-Token: <csrf_token_from_cookie>" \
  -F 'files=@drop-form.pdf'

Download or Remove a File ​

GET    /api/v1/core/email-templates/attachments/{id}/{attachmentID}
DELETE /api/v1/core/email-templates/attachments/{id}/{attachmentID}

The download is sent with Content-Disposition: attachment, X-Content-Type-Options: nosniff and Cache-Control: private, no-store. The delete returns 204, and takes effect on the next email sent from the template. Both answer 404 when the file is not on that template.


Credit Conversions ​

Manage credit system conversion rules (e.g., semester-to-quarter, ECTS-to-semester) and perform credit conversions.

List Conversions ​

GET /api/v1/core/credit-conversions

Get all credit system conversion rules.

Example Request ​

bash
curl -X GET http://localhost:8000/api/v1/core/credit-conversions \
  -b cookies.txt

Success Response (200 OK) ​

json
[
  {
    "conversion_id": 1,
    "from_system_id": 1,
    "from_system_code": "SEMESTER",
    "from_system_display": "Semester Credits",
    "to_system_id": 2,
    "to_system_code": "QUARTER",
    "to_system_display": "Quarter Credits",
    "conversion_factor": "1.5",
    "is_active": true
  },
  {
    "conversion_id": 2,
    "from_system_id": 3,
    "from_system_code": "ECTS",
    "from_system_display": "ECTS Credits",
    "to_system_id": 1,
    "to_system_code": "SEMESTER",
    "to_system_display": "Semester Credits",
    "conversion_factor": "0.5",
    "is_active": true
  }
]

TypeScript Types ​

typescript
interface CreditConversion {
  conversion_id: number
  from_system_id: number
  from_system_code: string
  from_system_display: string
  to_system_id: number
  to_system_code: string
  to_system_display: string
  conversion_factor: string
  is_active: boolean
}

Create Conversion ​

POST /api/v1/core/credit-conversions

Create a new credit conversion rule.

Request Body ​

FieldTypeRequiredDescription
from_system_idintegerYesSource credit system lookup ID
to_system_idintegerYesTarget credit system lookup ID
conversion_factordecimalYesMultiplication factor
is_activebooleanNoActive status (default: true)

Example Request ​

bash
curl -X POST http://localhost:8000/api/v1/core/credit-conversions \
  -b cookies.txt \
  -H "Content-Type: application/json" \
  -d '{
    "from_system_id": 2,
    "to_system_id": 1,
    "conversion_factor": "0.667"
  }'

Success Response (201 Created) ​

Returns the created conversion DTO.


Update Conversion ​

PUT /api/v1/core/credit-conversions/{conversionID}

Update a credit conversion rule.

Path Parameters ​

ParameterTypeDescription
conversionIDintegerThe conversion rule ID

Request Body ​

Same fields as Create.

Example Request ​

bash
curl -X PUT http://localhost:8000/api/v1/core/credit-conversions/1 \
  -b cookies.txt \
  -H "Content-Type: application/json" \
  -d '{
    "from_system_id": 1,
    "to_system_id": 2,
    "conversion_factor": "1.5",
    "is_active": true
  }'

Delete Conversion ​

DELETE /api/v1/core/credit-conversions/{conversionID}

Delete a credit conversion rule.

Path Parameters ​

ParameterTypeDescription
conversionIDintegerThe conversion rule ID

Example Request ​

bash
curl -X DELETE http://localhost:8000/api/v1/core/credit-conversions/1 \
  -b cookies.txt

Success Response (204 No Content) ​


Convert Credits ​

POST /api/v1/core/credit-conversions/convert

Convert a credit value from one system to another.

Request Body ​

FieldTypeRequiredDescription
creditsdecimalYesNumber of credits to convert
from_systemstringYesSource system code (e.g., SEMESTER)
to_systemstringYesTarget system code (e.g., QUARTER)

Example Request ​

bash
curl -X POST http://localhost:8000/api/v1/core/credit-conversions/convert \
  -b cookies.txt \
  -H "Content-Type: application/json" \
  -d '{
    "credits": "3.0",
    "from_system": "SEMESTER",
    "to_system": "QUARTER"
  }'

Success Response (200 OK) ​

json
{
  "original_credits": "3.0",
  "converted_credits": "4.5",
  "from_system": "SEMESTER",
  "to_system": "QUARTER",
  "conversion_factor": "1.5"
}

TypeScript Types ​

typescript
interface CreditConvertRequest {
  credits: string
  from_system: string
  to_system: string
}

interface CreditConvertResponse {
  original_credits: string
  converted_credits: string
  from_system: string
  to_system: string
  conversion_factor: string
}

Audit Trail ​

The federated audit trail behind Administration → Audit Logs, gated on ADMIN.AUDIT. It merges three independent logs into one list: core.audit_log (DB_CHANGE, generic row changes on every table auditing is enabled for), core.permission_audit_log (PERMISSION) and records.grade_change_log (GRADE_CHANGE).

List Entries ​

GET /api/v1/core/audit-trail

Query Parameters ​

ParameterDescription
page, page_sizePagination
log_sourceDB_CHANGE, PERMISSION or GRADE_CHANGE
table_nameReal schema.table for DB_CHANGE; entity type for PERMISSION. GET /audit-trail/categories lists the values that have rows
field_nameOnly rows where this column's value changed. Excludes GRADE_CHANGE rows
actionINSERT, UPDATE or DELETE (INSERT matches CREATE in the PERMISSION source)
person_idThe person who made the change
actor_sourceWEB, AUTH, SYSTEM, PUBLIC_FORM, COLLEAGUE_SYNC, ... PERMISSION rows have none, so setting it excludes them
searchPartial match on table/entity name and record id
date_from, date_toDate range

Success Response (200 OK) ​

json
{
  "entries": [
    {
      "log_source": "DB_CHANGE",
      "log_id": 918273,
      "table_name": "core.auth_users",
      "record_id": "412",
      "action": "UPDATE",
      "changed_fields": ["last_login"],
      "changed_by_person_id": "550e8400-e29b-41d4-a716-446655440000",
      "changed_by_name": "Jane Doe",
      "changed_at": "2026-10-26T14:03:11Z",
      "ip_address": "10.0.50.17",
      "client_hostname": "lab-14.example.edu",
      "user_agent": "Mozilla/5.0 ...",
      "actor_source": "AUTH"
    }
  ],
  "total_count": 1,
  "page": 1,
  "page_size": 25
}

log_id is the log row's own key in its source table, not the changed record's id (record_id). ip_address and user_agent are recorded on every audited web write; client_hostname (migration 264) is resolved by reverse DNS on the pre-session AUTH routes only, so a successful sign-in — recorded as the UPDATE of core.auth_users.last_login — carries one. client_hostname and user_agent exist only on DB_CHANGE rows. Rows written by background jobs and CLI tools have no client fields. Failed sign-ins write no row.

Delete Entries ​

DELETE /audit-trail takes { "entries": [{ "log_source", "log_id" }, ...] }, at most 500 per call; DELETE /audit-trail/bulk deletes everything matching the same filters as the list. Each deletion is itself recorded, without the original payload, in core.audit_trail_deletion_log, which no endpoint can delete from.


Health Check ​

GET /api/v1/core/health

Returns the health status of the core service.

Example Request ​

bash
curl -X GET http://localhost:8000/api/v1/core/health \
  -b cookies.txt

Success Response (200 OK) ​

json
{
  "status": "healthy",
  "service": "core"
}



Complete endpoint reference ​

Every route this domain registers, with the permission node that gates it. Generated from the route table, so it is complete by construction — the sections above explain behaviour, this one guarantees nothing is missing.

Paths are relative to /api/v1/core. A node in italics is an exemption rather than a grant: selfservice acts only on the caller's own record, public needs no session at all, and custom carries its own wrapper.

MethodEndpointPermission nodeDescription
GET/audit-configADMIN.AUDIT_CONFIGGet audit config
POST/audit-config/applyADMIN.AUDIT_CONFIGApply
PUT/audit-config/masterADMIN.AUDIT_CONFIGReplace master
PUT/audit-config/schema-defaultsADMIN.AUDIT_CONFIGReplace schema defaults
PUT/audit-config/tablesADMIN.AUDIT_CONFIGReplace tables
DELETE/audit-trailADMIN.AUDITDelete audit trail
GET/audit-trailADMIN.AUDITGet audit trail
DELETE/audit-trail/bulkADMIN.AUDITDelete bulk
GET/audit-trail/categoriesADMIN.AUDITList categories
GET/countriesselfserviceList countries
POST/countriesADMIN.COUNTRIESCreate a country
GET/countries/dropdownselfserviceList options for a picker
DELETE/countries/{id}ADMIN.COUNTRIESDelete a country
GET/countries/{id}selfserviceGet one country
PATCH/countries/{id}ADMIN.COUNTRIESUpdate a country
GET/credit-conversionsACADEMIC.CREDIT_CONVERSIONSList credit conversions
POST/credit-conversionsACADEMIC.CREDIT_CONVERSIONSCreate a credit conversion
POST/credit-conversions/convertACADEMIC.CREDIT_CONVERSIONSCreate convert
DELETE/credit-conversions/{conversionID}ACADEMIC.CREDIT_CONVERSIONSDelete a credit conversion
PUT/credit-conversions/{conversionID}ACADEMIC.CREDIT_CONVERSIONSReplace a credit conversion
GET/currenciesselfserviceList currencies
POST/currenciesADMIN.CURRENCIESCreate a currency
DELETE/currencies/{id}ADMIN.CURRENCIESDelete a currency
GET/currencies/{id}selfserviceGet one currency
PATCH/currencies/{id}ADMIN.CURRENCIESUpdate a currency
GET/demo-accountspublicThe demo's published logins; an empty list unless DEMO_MODE=true
GET/email-templatesADMIN.EMAIL_TEMPLATESList email templates
POST/email-templatesADMIN.EMAIL_TEMPLATESCreate a email template
GET/email-templates/by-code/{code}ADMIN.EMAIL_TEMPLATESGet one by code
DELETE/email-templates/{id}ADMIN.EMAIL_TEMPLATESDelete a email template
GET/email-templates/{id}ADMIN.EMAIL_TEMPLATESGet one email template
PATCH/email-templates/{id}ADMIN.EMAIL_TEMPLATESUpdate a email template
GET/email-templates/attachments/{id}ADMIN.EMAIL_TEMPLATESGet a template's attachments and limits
POST/email-templates/attachments/{id}ADMIN.EMAIL_TEMPLATESAdd files to a template
PUT/email-templates/attachments/{id}/settingsADMIN.EMAIL_TEMPLATESSet a template's attachment allowance
DELETE/email-templates/attachments/{id}/{attachmentID}ADMIN.EMAIL_TEMPLATESRemove a template attachment
GET/email-templates/attachments/{id}/{attachmentID}ADMIN.EMAIL_TEMPLATESDownload a template attachment
GET/filter-presetsselfserviceList filter presets
POST/filter-presetsselfserviceCreate a filter preset
DELETE/filter-presets/{presetID}selfserviceDelete a filter preset
PATCH/filter-presets/{presetID}selfserviceUpdate a filter preset
GET/healthpublicGet health
GET/instancepublicWrite mode, environment and whether demo logins are published
GET/institution-configADMIN.BRANDINGGet institution config
PUT/institution-configADMIN.BRANDINGReplace institution config
GET/lookup-typesselfserviceList lookup types
GET/lookup-types/by-code/{code}selfserviceGet one by code
POST/lookup-valuesADMIN.LOOKUPSCreate a lookup value
GET/lookup-values/by-type/{typeCode}selfserviceGet one by type
DELETE/lookup-values/{typeCode}/{id}ADMIN.LOOKUPSDelete a
PATCH/lookup-values/{typeCode}/{id}ADMIN.LOOKUPSUpdate a
GET/lookupsselfserviceList lookups
GET/lookups/by-type/{typeCode}selfserviceGet one by type
GET/settingsselfserviceList settings
POST/settingsADMIN.SYSTEM_SETTINGSCreate a setting
GET/settings/brandingpublicGet branding
PUT/settings/brandingADMIN.BRANDINGReplace branding
GET/settings/branding/fullselfserviceGet full
PATCH/settings/bulk_updateADMIN.SYSTEM_SETTINGSBulk update
POST/settings/bulk_updateADMIN.SYSTEM_SETTINGSBulk update
GET/settings/by_categoryselfserviceGet by category
GET/settings/by_keyselfserviceGet by key
PATCH/settings/by_keyADMIN.SYSTEM_SETTINGSUpdate by key
POST/settings/recompute-statusesADMIN.SYSTEM_SETTINGSRecompute statuses
GET/settings/{id}selfserviceGet one setting
PATCH/settings/{id}ADMIN.SYSTEM_SETTINGSUpdate a setting
PUT/settings/{key}ADMIN.SYSTEM_SETTINGSReplace a setting
GET/state-provinces/dropdownselfserviceList state/province options for a picker
GET/ui-text/catalogADMIN.UI_TEXTThe shipped UI strings, per locale
GET/ui-text/draftADMIN.UI_TEXTThe unpublished overrides
PUT/ui-text/draftADMIN.UI_TEXTSave overrides to the draft
POST/ui-text/draft/discardADMIN.UI_TEXTDiscard the draft
GET/ui-text/exportADMIN.UI_TEXTExport the overrides
GET/ui-text/livepublicThe published overrides (the login page renders them before any session)
POST/ui-text/publishADMIN.UI_TEXTPublish the draft

Student Information System Documentation