Partner API
Sell a store's courses on your own platform, under your own branding. List the catalog, send a learner to a course with one call, and read their progress back — the course itself plays in Aero's hosted player, so content never leaves the store.
How it works
Your integration is three calls:
- List courses you can sell and show them in your own catalog, storefront or LMS.
- When one of your learners should start a course, call access-course with your own user ID for them. You get back a one-time URL — redirect the learner to it (or open it in an iframe). Aero hosts the entire learning experience.
- Poll learner-reports for progress and completions, on your schedule.
Learners are identified by your user_id — Aero keeps the mapping. No signup, no password: the launch URL carries the identity.
Authentication
Every request carries a partner token as a bearer token. You create and revoke your own tokens from your team dashboard on the store's site (Team dashboard → API access) — each is shown once at creation, so store it like a password. Keep one per environment or integration; revoking a leaked token never breaks the others.
curl https://courseplayer.aeroapp.org/partner/courses \ -H "Authorization: Bearer aero_rk_YOUR_TOKEN"
| Base URL | https://courseplayer.aeroapp.org/partner — or the store's own player domain if they gave you one (e.g. courseplayer.theirstore.com/partner) |
|---|---|
| Token | aero_rk_… — self-service from your team dashboard (multiple tokens supported) |
| Errors | 401 bad/revoked token · 402 plan or pricing issue · 404 not found · 422 missing parameters · 429 rate limited |
Seats & pay-as-you-go
Two ways to pay the store, freely mixed:
- Prepaid seats — bought in advance (usually at tiered volume prices). Each new learner enrollment consumes one seat for that course.
- Pay-as-you-go — no seats needed. Each enrollment is metered at the store's per-enrollment price and invoiced monthly.
Seats are always consumed first. A learner re-opening a course they're already enrolled in is free — you're only ever charged when a new enrollment is created. Your monthly statement shows exactly what was consumed.
Courses
GET /partner/courses
By default: the courses you hold prepaid seats for. Add ?courses=all for the store's whole published catalog (any of it can be sold pay-as-you-go). Query params: count (per page, max 100), page, sort_by (title | id | post_date), sort (ASC | DESC).
{ "status": "success",
"message": "Successfully retrieved courses",
"max_num_pages": 1, "current_page": 1, "total_count": "2",
"data": [ {
"id": 15,
"title": "Food Hygiene Level 2",
"thumbnail": "https://…/food-hygiene.jpg",
"description": "…",
"course_duration": "01:36:00"
} ] }
Access a course
POST /partner/access-course
Enrolls the learner if needed (consuming a seat or metering PAYG), and returns a one-time URL into the hosted player. Call it every time the learner opens the course — repeat calls for an existing enrollment are free and just mint a fresh URL.
{ "email": "emma@client.com",
"first_name": "Emma", "last_name": "Wright",
"user_id": "your-internal-id-8812",
"course_id": 15,
"redirect_url": "https://your-platform.com/dashboard" }
Response:
{ "status": 200, "data": {
"user_id": 4021,
"url": "https://courseplayer.aeroapp.org/launch?t=64-hex-chars"
} }
| user_id | Your identifier for the learner — any string. Aero recognizes them by it on every later call; email/name changes just update the profile. |
|---|---|
| redirect_url | Where "My courses" and back-links send the learner — your dashboard, not ours. |
| language (optional) | Two-letter code ("fr", "ar"). Sets the learner's player and email language — applies when the store has translated that language. |
| 402 partner_pricing | No seats remain for this course and the store hasn't set a pay-as-you-go price. Ask the store to configure pricing or grant seats. |
Learner reports
GET /partner/learner-reports
Progress for your learners. Query params: email (one learner), date_from, date_to (enrollment date window; defaults to the last month).
{ "status": 200, "data": [ {
"email": "emma@client.com",
"courses": [ {
"course_id": 15,
"title": "Food Hygiene Level 2",
"progress": "58.33",
"course_status": "2",
"unit_reports": [ {
"title": "Welcome",
"completion_time": "12 August 2026 09:14:02"
} ]
} ]
} ] }
course_status: 1 not started · 2 in progress · 4 completed. unit_reports lists completed units in order, or is null when none.
Monthly statement
GET /partner/statement?month=2026-08
Your usage for a month — the exact numbers the store invoices from. Defaults to the current month.
{ "status": 200, "data": {
"month": "2026-08",
"lines": [ {
"course_id": 15, "title": "Food Hygiene Level 2",
"seat_count": 40, "payg_count": 12, "payg_amount": 150.00
} ],
"totals": { "enrollments": 52, "seat_count": 40,
"payg_count": 12, "payg_amount": 150.00 }
} }
Webhooks
Instead of polling learner-reports, register a webhook URL with the store (they set it on your reseller profile and give you a signing secret). Events are POSTed as JSON the moment they happen, with two retries (5 and 10 minutes) on non-2xx responses:
| Event | When |
|---|---|
enrollment.created | A new enrollment was charged (seat or PAYG) via access-course |
course.completed | A learner finished a course — includes completed_at |
certificate.issued | Only if the store issues certificates for you — includes certificate_code |
{ "event": "course.completed",
"created_at": "2026-08-15 14:02:11",
"data": {
"user_id": "your-internal-id-8812",
"email": "emma@client.com",
"course_id": 15,
"title": "Food Hygiene Level 2",
"completed_at": "2026-08-15 14:02:11"
} }
Every request carries X-Partner-Signature — the hex HMAC-SHA256 of the raw body with your signing secret. Verify it before trusting the payload, and respond 2xx quickly (do your processing async).
Launch URLs & sessions
- Launch URLs are single-use and expire after 10 minutes — redirect the learner immediately, never store or email them.
- Opening one starts an 8-hour session in the player. When it ends, the learner just returns through your platform (another access-course call) — progress is always saved.
- Inside the player, "My courses" and back-links lead to your
redirect_url. Your learners never see the store.
Embedding (iframe)
The player can run inside an iframe on your platform. Ask the store to register your domains on your reseller profile — once set, framing is locked to those domains. Open the launch URL as the iframe's src; the session cookie is scoped to the player domain and works in the embedded context.
<iframe src="https://courseplayer.aeroapp.org/launch?t=…"
style="width:100%;height:100vh;border:0" allowfullscreen></iframe>
Certificates
By default Aero does not issue certificates for partner-enrolled learners — completions arrive in your learner reports and you certify in your own system, under your own brand. If you'd rather Aero issue them, the store can enable it on your reseller profile; those certificates then carry an "In partnership with" line with your logo.