Every API here is available over the APIs.io API and to AI agents over MCP.
{
"openapi": "3.1.0",
"info": {
"title": "Get Mandatory Training course catalogue API",
"version": "1.0",
"summary": "Read-only course information for agents helping a person choose an online CPR or first aid course.",
"description": "Lists the online courses offered for a country, with prices, what each course covers, and what we say about whether an employer will accept the certificate. Give people the employer_acceptance wording as it is, without paraphrasing it. Enrolment happens on the site, where the person enters and confirms their own details. Documentation: /agent/docs. It's free: no paid tier, no key and no sign-up. Conventions: this is version 1.0; a breaking change comes with a new version, and additive changes don't. Reads are limited per client address; every read response carries RateLimit-Policy and RateLimit (draft-ietf-httpapi-ratelimit-headers), and a 429 carries Retry-After. Pagination, batch requests, async jobs and idempotency keys don't apply: every documented operation is a read-only GET returning a complete, short list or one item."
},
"servers": [{ "url": "https://getmandatorytraining.com" }],
"security": [],
"externalDocs": {
"url": "https://getmandatorytraining.com/sandbox",
"description": "No operation needs authentication: reads change nothing, so call the live API. The calls with side effects (agent registration and enrolment) have a test mode; the sandbox page shows how to rehearse them."
},
"paths": {
"/agent/courses": {
"get": {
"operationId": "listCourses",
"summary": "List the courses for a country",
"description": "The courses offered for the country, one version per course, with that country's employer-acceptance wording. A country without its own versions gets the US versions and a note that says so.",
"parameters": [{ "$ref": "#/components/parameters/country" }],
"responses": {
"200": {
"description": "The courses for the country.",
"headers": { "RateLimit-Policy": { "$ref": "#/components/headers/RateLimit-Policy" }, "RateLimit": { "$ref": "#/components/headers/RateLimit" } },
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/CourseList" } } }
},
"405": { "$ref": "#/components/responses/MethodNotAllowed" },
"429": { "$ref": "#/components/responses/TooMany" },
"500": { "$ref": "#/components/responses/Failed" },
"503": { "$ref": "#/components/responses/Unavailable" },
"default": { "$ref": "#/components/responses/Error" }
}
}
},
"/agent/courses/{slug}": {
"get": {
"operationId": "getCourse",
"summary": "Get one course",
"description": "One course, with its description, its lesson titles, the size and pass mark of its test, and its versions for other countries.",
"parameters": [
{
"name": "slug",
"in": "path",
"required": true,
"description": "The course's slug, from a course list.",
"schema": { "type": "string", "pattern": "^[a-z0-9-]+$" },
"example": "first-aid-nz"
}
],
"responses": {
"200": {
"description": "The course.",
"headers": { "RateLimit-Policy": { "$ref": "#/components/headers/RateLimit-Policy" }, "RateLimit": { "$ref": "#/components/headers/RateLimit" } },
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/Course" } } }
},
"404": { "$ref": "#/components/responses/NotFound" },
"405": { "$ref": "#/components/responses/MethodNotAllowed" },
"429": { "$ref": "#/components/responses/TooMany" },
"500": { "$ref": "#/components/responses/Failed" },
"503": { "$ref": "#/components/responses/Unavailable" },
"default": { "$ref": "#/components/responses/Error" }
}
}
},
"/agent/acceptance": {
"get": {
"operationId": "employerAcceptance",
"summary": "What we say about employer acceptance",
"description": "Whether an employer will accept the certificate is up to the employer. This is our wording on it for the country, to give the person as it is.",
"parameters": [{ "$ref": "#/components/parameters/country" }],
"responses": {
"200": {
"description": "The wording for the country.",
"headers": { "RateLimit-Policy": { "$ref": "#/components/headers/RateLimit-Policy" }, "RateLimit": { "$ref": "#/components/headers/RateLimit" } },
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/Acceptance" } } }
},
"405": { "$ref": "#/components/responses/MethodNotAllowed" },
"429": { "$ref": "#/components/responses/TooMany" },
"500": { "$ref": "#/components/responses/Failed" },
"default": { "$ref": "#/components/responses/Error" }
}
}
}
},
"components": {
"parameters": {
"country": {
"name": "country",
"in": "query",
"required": false,
"description": "A two-letter country code (US, CA, GB, NZ and others; UK is accepted for GB). Without it, or with a code we don't recognise, the answer is for the requester's own country as the site estimates it, else the United States. Asking for a country never changes anyone's settings.",
"schema": { "type": "string", "pattern": "^[A-Za-z]{2}$" },
"example": "NZ"
}
},
"responses": {
"NotFound": {
"description": "No course with this slug is open to visitors.",
"headers": { "RateLimit-Policy": { "$ref": "#/components/headers/RateLimit-Policy" }, "RateLimit": { "$ref": "#/components/headers/RateLimit" } },
"content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" }, "example": { "type": "https://getmandatorytraining.com/agent/docs#errors", "title": "Not found", "status": 404, "code": "not_found", "detail": "No course with that slug is offered.", "hint": "List the courses offered with GET https://getmandatorytraining.com/agent/courses, and use a slug from that list.", "result": "not_found" } } }
},
"Failed": {
"description": "Something went wrong reading the catalogue. Try again shortly.",
"headers": { "RateLimit-Policy": { "$ref": "#/components/headers/RateLimit-Policy" }, "RateLimit": { "$ref": "#/components/headers/RateLimit" } },
"content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" }, "example": { "type": "https://getmandatorytraining.com/agent/docs#errors", "title": "Temporarily unavailable", "status": 500, "code": "unavailable", "detail": "The course catalogue isn't available right now.", "hint": "Try again in a minute. If it keeps failing, see https://getmandatorytraining.com/agent/docs.", "result": "unavailable" } } }
},
"Unavailable": {
"description": "The course catalogue is loading or can't be read. Try again shortly.",
"headers": { "RateLimit-Policy": { "$ref": "#/components/headers/RateLimit-Policy" }, "RateLimit": { "$ref": "#/components/headers/RateLimit" } },
"content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" }, "example": { "type": "https://getmandatorytraining.com/agent/docs#errors", "title": "Temporarily unavailable", "status": 503, "code": "unavailable", "detail": "The course catalogue isn't loaded yet.", "hint": "Try again in a minute. If it keeps failing, see https://getmandatorytraining.com/agent/docs.", "result": "unavailable" } } }
},
"MethodNotAllowed": {
"description": "This path doesn't accept the method used: the read API's paths accept GET and HEAD only. The Allow header names the methods to use. A wrong method isn't a read, so it isn't counted against the reads quota and carries no RateLimit fields.",
"headers": {
"Allow": { "description": "The methods this path accepts, comma-separated.", "schema": { "type": "string" }, "example": "GET, HEAD" }
},
"content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" }, "example": { "type": "https://getmandatorytraining.com/agent/docs#errors", "title": "Method not allowed", "status": 405, "code": "method_not_allowed", "detail": "This resource doesn't accept that method.", "hint": "Use GET or HEAD; see https://getmandatorytraining.com/agent/docs." } } }
},
"Error": {
"description": "Any other error, as a Problem: read code and hint to decide what to do next. Every error the API sends is a Problem.",
"content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" }, "example": { "type": "https://getmandatorytraining.com/agent/docs#errors", "title": "Temporarily unavailable", "status": 500, "code": "unavailable", "detail": "The course catalogue isn't available right now.", "hint": "Try again in a minute. If it keeps failing, see https://getmandatorytraining.com/agent/docs." } } }
},
"TooMany": {
"description": "Too many reads from this address in the current window. Wait the Retry-After seconds, which is when the window ends, then try again.",
"headers": {
"RateLimit-Policy": { "$ref": "#/components/headers/RateLimit-Policy" },
"RateLimit": { "$ref": "#/components/headers/RateLimit" },
"Retry-After": { "description": "Seconds until the window ends and reads are accepted again: the RateLimit field's t.", "schema": { "type": "integer", "minimum": 1 }, "example": 43 }
},
"content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" }, "example": { "type": "https://getmandatorytraining.com/agent/docs#errors", "title": "Too many requests", "status": 429, "code": "too_many", "detail": "Too many reads from this address for now.", "hint": "Wait before trying again; the limits are described in https://getmandatorytraining.com/agent/docs.", "result": "too_many" } } }
}
},
"headers": {
"RateLimit-Policy": {
"description": "The reads quota per client address, as a structured-field list (draft-ietf-httpapi-ratelimit-headers): the policy name \"reads\", q requests per w seconds, counted on each server. For example \"reads\";q=120;w=60.",
"schema": { "type": "string" },
"example": "\"reads\";q=120;w=60"
},
"RateLimit": {
"description": "What's left of the reads quota for this address, as a structured-field list (draft-ietf-httpapi-ratelimit-headers): r requests remaining and t seconds until the window ends. For example \"reads\";r=119;t=43.",
"schema": { "type": "string" },
"example": "\"reads\";r=119;t=43"
}
},
"schemas": {
"CourseList": {
"type": "object",
"description": "The courses offered for one country, with that country's employer-acceptance wording.",
"required": ["country", "employer_acceptance", "courses"],
"additionalProperties": false,
"properties": {
"country": { "type": "string", "description": "The country answered for, by name." },
"note": { "type": "string", "description": "When some or all of the courses are US versions: says they follow US guidelines and refer to US emergency numbers and laws." },
"employer_acceptance": { "$ref": "#/components/schemas/AcceptanceText" },
"courses": { "type": "array", "items": { "$ref": "#/components/schemas/Course" }, "description": "The courses, one version each." }
}
},
"Course": {
"type": "object",
"description": "One course, as offered in a country. Get a course adds its description, lessons, test and other versions.",
"required": ["slug", "title", "url", "country", "price", "currency", "minutes", "certificate_valid_for", "summary", "audience", "enrolment", "employer_acceptance"],
"additionalProperties": false,
"properties": {
"slug": { "type": "string", "description": "The course's identifier, for Get a course." },
"title": { "type": "string", "description": "The course's name." },
"url": { "type": "string", "description": "The course's page, where the person can enrol." },
"country": { "type": "string", "description": "The country this version of the course is written for." },
"price": { "type": "string", "description": "The certificate's price in the course's own currency, e.g. \"NZ$19.95\"." },
"currency": { "type": "string", "description": "ISO 4217 code." },
"minutes": { "type": "integer", "description": "About how long the course takes." },
"certificate_valid_for": { "type": "string", "description": "How long the certificate of completion is valid for, e.g. \"2 years\"." },
"summary": { "type": "string", "description": "What the course covers, in a sentence or two." },
"audience": { "type": "string", "description": "Who the course is written for." },
"enrolment": { "type": "string", "description": "How enrolling works." },
"employer_acceptance": { "$ref": "#/components/schemas/AcceptanceText" },
"description": { "type": "string", "description": "Get a course only." },
"lessons": { "type": "array", "items": { "type": "string" }, "description": "Get a course only: the lesson titles." },
"test_questions": { "type": "integer", "description": "Get a course only: how many questions the test has." },
"test_pass_mark": { "type": "integer", "description": "Get a course only: how many correct answers pass." },
"other_versions": { "type": "array", "items": { "$ref": "#/components/schemas/Version" }, "description": "Get a course only: the same course written for other countries." }
}
},
"Version": {
"type": "object",
"description": "The same course written for another country.",
"required": ["country", "slug", "url"],
"additionalProperties": false,
"properties": {
"country": { "type": "string", "description": "The country this version is written for, by name." },
"slug": { "type": "string", "description": "This version's identifier, for Get a course." },
"url": { "type": "string", "description": "This version's page." }
}
},
"Acceptance": {
"type": "object",
"description": "Our wording on employer acceptance for one country.",
"required": ["country", "employer_acceptance"],
"additionalProperties": false,
"properties": {
"country": { "type": "string", "description": "The country answered for, by name." },
"employer_acceptance": { "$ref": "#/components/schemas/AcceptanceText" }
}
},
"AcceptanceText": {
"type": "array",
"items": { "type": "string" },
"description": "Paragraphs on whether an employer will accept the certificate. Give them to the person as they are, without paraphrasing or shortening them into a yes."
},
"Problem": {
"type": "object",
"description": "An error, as RFC 9457 problem details, with a stable code and a hint saying what to do next.",
"required": ["type", "title", "status", "code", "detail", "hint"],
"additionalProperties": false,
"properties": {
"type": { "type": "string", "description": "A link to the errors section of the API docs.", "example": "https://getmandatorytraining.com/agent/docs#errors" },
"title": { "type": "string", "description": "A short, fixed summary of the kind of error.", "example": "Not found" },
"status": { "type": "integer", "minimum": 400, "maximum": 599, "description": "The HTTP status code, repeated.", "example": 404 },
"code": { "type": "string", "enum": ["not_found", "invalid", "forbidden", "unavailable", "too_many", "method_not_allowed"], "description": "A stable, machine-readable error code.", "example": "not_found" },
"detail": { "type": "string", "description": "What went wrong with this request, in fixed text.", "example": "No course with that slug is offered." },
"hint": { "type": "string", "description": "What to do next, with a link to the docs or a resource.", "example": "List the courses offered with GET https://getmandatorytraining.com/agent/courses, and use a slug from that list." },
"result": { "type": "string", "enum": ["not_found", "unavailable", "too_many"], "description": "The same code, kept for clients written before problem details.", "example": "not_found" }
}
}
}
}
}