Skip to content

Course settings JSON API

Hanami exposes a versioned, teacher-only JSON interface for configuration automation. Version 1 has one resource:

/api/v1/courses/{courseId}/settings

Authenticate with an A+ API token belonging to a teacher of the configured course. Keep the token outside scripts and shell history:

Terminal window
export HANAMI_URL=https://hanami.example.org
export APLUS_TOKEN='...'
curl --fail-with-body \
-H "Authorization: Token $APLUS_TOKEN" \
"$HANAMI_URL/api/v1/courses/123/settings"

GET returns the writable settings together with revision, courseId, and the server-owned tags and ltiLoginUrl fields. Its ETag header contains the same revision.

PUT replaces every writable field. Start from a GET response, remove revision, courseId, tags, and ltiLoginUrl, edit the remaining JSON, and send the revision as a strong If-Match header:

Terminal window
curl --fail-with-body \
-X PUT \
-H "Authorization: Token $APLUS_TOKEN" \
-H 'Content-Type: application/json' \
-H 'If-Match: "4"' \
--data-binary @course-settings.json \
"$HANAMI_URL/api/v1/courses/123/settings"

The request object has these fields:

Field Type Notes
schemaVersion integer Currently 1.
slug string Current lowercase URL slug. It must be sent unchanged.
name string Non-empty course name.
languages string array At least one unique content language.
highlightLanguages string array Unique supported syntax highlighters.
courseConfigUrl string or null HTTPS or HTTP course configuration URL.
templates object automaticExtensions, extensions, and excludedPathSegments.
listed boolean Whether students can discover the course.
visibleTagSlugs string array Every slug must exist in the read-only tags list.
graders object array Each entry has userId, languages, feedbackTags, and gradingTags.
manualGradingExercises integer array Unique positive A+ exercise ids.

Unknown fields are rejected. Hanami also rejects grader language or tag references that are not configured, and verifies that every grader is current A+ course staff. Refresh A+ tags and LTI metadata in the Hanami settings UI before using newly created tags through this API.

The request body limit is 2 MB. A successful PUT returns the updated resource and its new ETag. If another writer changed the course, the request returns 412; fetch the current document, reconcile the changes, and retry. Omitting If-Match returns 428.

Version 1 errors always use one envelope. Validation errors include stable JSON paths and machine-readable issue codes:

{
"error": {
"code": "validation_failed",
"message": "Request validation failed",
"requestId": "9cb32531-bab4-4e8e-8887-30dd0d170945",
"issues": [
{
"path": "$.visibleTagSlugs",
"code": "invalid_value",
"message": "visible tag is unknown"
}
]
}
}

Clients should branch on error.code and issues[].code, not the human-readable messages. Relevant status codes are 400 for malformed or invalid input, 401 for an invalid token, 403 when the caller does not teach the requested course, 404 when a taught course has no Hanami configuration, 412 for a stale revision, 413 for an oversized body, 415 for a non-JSON body, and 428 when If-Match is missing.