The public site API exposes one institution and its publicly listed programmes. It is anonymous and read-only, and has two endpoints.
Base URL
https://api.lernmark.app
Every path below is relative to this origin and starts with /api/v1. In the
paths, {school} is your school slug and {institution} is your institution
ID. See Before you start.
Authentication
None. Do not send API keys, tokens, passwords, or cookies. Send
Accept: application/json with every request.
Get the institution
Confirms that your school slug and school ID point to an institution that is listed publicly. Use it as a health check when you set up or change your configuration.
/api/v1/public/site/{school}/institutions/{institution}Response (200 OK):
{
"data": {
"institution": {
"id": "3f2b6c1e-8a4d-4f7b-9c2e-5d1a0b7e4c93",
"name": "Institut Siantou Supérieur",
"code": "ISS"
}
},
"meta": { "message": "OK" },
"errors": {}
}
| Field | Type | Meaning |
|---|---|---|
institution.id | UUID | Stable public school ID. Matches the {institution} you sent. |
institution.name | string | Public institution name. |
institution.code | string | Short institution code. |
List programmes
Returns the institution's publicly listed programmes, one page at a time.
/api/v1/public/site/{school}/institutions/{institution}/programmesGET /api/v1/public/site/siantou/institutions/3f2b6c1e-8a4d-4f7b-9c2e-5d1a0b7e4c93/programmes?per_page=20&sort=name HTTP/1.1
Host: api.lernmark.app
Accept: application/json
Query parameters
All parameters are optional.
| Parameter | Default | Rules |
|---|---|---|
page | 1 | The page to return. |
per_page | 25 | Programmes per page, from 1 to 100. Larger values are capped at 100. |
search | none | Matches any part of the programme title or code. |
sort | name | One of name, code, or award. Other values fall back to name. |
direction | asc | asc or desc. |
Response (200 OK):
{
"data": {
"programmes": [
{
"id": "8c4e2a17-5b3f-4d9a-a1c6-7e0f2b9d3a54",
"title": "BTS Accounting and Management",
"award": "BTS",
"academic_unit": "School of Management",
"duration": "2 years full-time",
"summary": "Practical accounting, tax, and business management for the Cameroonian market.",
"application_available": true,
"slug": "bts-accounting-and-management",
"last_updated": "2026-09-14T08:30:00+00:00"
}
]
},
"meta": {
"message": "OK",
"pagination": {
"current_page": 1,
"from": 1,
"last_page": 2,
"per_page": 20,
"to": 20,
"total": 27
}
},
"errors": {}
}
Programme fields
| Field | Type | Meaning |
|---|---|---|
id | UUID | Stable public programme ID. |
title | string | Programme title. |
award | string or null | Award or qualification, such as BTS or Licence. |
academic_unit | string or null | Department or faculty that runs the programme. |
duration | string or null | Human-readable duration. |
summary | string or null | Short public description. |
application_available | boolean | true while the programme takes applications. See Apply links. |
slug | string | URL-friendly name, unique within the institution. Useful for your own programme page URLs. |
last_updated | ISO 8601 string or null | When the programme was last changed. |
Render nullable fields defensively: leave out a missing value, and never print
the word null.
Pagination
meta.pagination carries current_page, from, last_page, per_page,
to, and total. While current_page is less than last_page, request the
next page. On an empty result, from and to are null.
Response envelope
Every response, success or error, has the same three keys:
{
"data": {},
"meta": { "message": "OK" },
"errors": {}
}
meta.pagination appears only on the programme list. On an error, data is
null and meta.message names the problem.
Response headers
| Header | Meaning |
|---|---|
Cache-Control: no-store, max-age=0 | Lernmark always returns fresh data. Do your caching on your server. |
X-RateLimit-Limit | Requests allowed in the current one-minute window. |
X-RateLimit-Remaining | Requests left in that window. |
Retry-After | On a 429, seconds to wait before trying again. |
X-Request-Id | A unique ID for the request. Include it when you contact support. |
Rate limits
Limits apply per school slug and per calling IP address, and every request counts, including those that fail:
- 60 requests per minute
- 1,000 requests per day
A server-side integration makes every request from your server's IP, so all your visitors share one budget. A 10-minute cache keeps a typical site far below both limits. Avoid passing each visitor's search text straight to the API: every new search term is a cache miss.
Errors
| Status | When | What to do |
|---|---|---|
404 Not Found | The school slug or school ID is wrong, or listing is not turned on for the institution. | Recheck both values. Show the unavailable message. |
429 Too Many Requests | A rate limit was reached. | Stop retrying, wait for Retry-After, and fix your caching. Show the unavailable message. |
5xx or timeout | A temporary problem. | Show the unavailable message and try again on a later request. |
All 404 cases return the same response on purpose, so the API never reveals
whether a school or institution exists:
{
"data": null,
"meta": { "message": "Not Found" },
"errors": {}
}
Treat anything except 200 as unavailable
Set a 5-second timeout. On any non-200 response or timeout, show
“Programme information is temporarily unavailable.” and do not fall back to
expired cached data.
A listed institution with no listed programmes returns 200 OK with an empty
programmes array, not a 404.