Integrating your e-commerce site with the 360L E-commerce Connector
Everything an external storefront needs: read the training catalog, and send us purchase orders.
This API has two jobs:
1. Expose the catalog — published training paths (with translations, cover images), their modules, packs (bundles of paths) and categories (with sub-categories). Your site displays them however it wants — and can sell at three levels: a pack, a single path, or a single module.
2. Receive purchase orders — when a customer submits a cart on your site,
POST /orders creates a purchase order on our side (status pending).
Our team reviews and prices it, then deploys it: the company is mapped or created,
360Learning groups are set up, trainings are opened and every learner email is provisioned and invited.
The catalog carries no prices: pricing happens on the quote, per order. Orders always arrive unpriced.
Authentication
Every request must carry the API key header.
https://360ecommerce.grauw.dev/api/v1
X-Api-Key: demo-api-key
Requests without a valid key get a 401. The key is managed in
Settings → Platform API — rotating it takes effect immediately.
All responses are JSON (Content-Type: application/json).
Endpoints
| Method | Path | Description |
|---|---|---|
| GET | /catalog | The full published catalog in one call: categories, packs, and paths with their modules. |
| GET | /packs | Published packs only, each with its included paths. |
| GET | /paths/{id} | A single path with modules and translations. 404 if unpublished or out of sync. |
| POST | /orders | Submit a cart: creates an unpriced purchase order (status pending). |
GET /catalog — read the catalog
Categories (with sub-categories), packs, and multilingual paths.
curl https://360ecommerce.grauw.dev/api/v1/catalog \
-H "X-Api-Key: demo-api-key"
{
"categories": [
{ "id": 1, "name": "Sales", "slug": "sales", "parent_id": null, "parent_slug": null },
{ "id": 5, "name": "Negotiation", "slug": "negotiation", "parent_id": 1, "parent_slug": "sales" }
],
"packs": [
{
"id": 1,
"name": "Manager Pack",
"slug": "manager-pack",
"description": "The complete manager kit: team + project.",
"cover": "https://…/640/360",
"category": "Management",
"paths": [ { "id": 3, "name": "Team Management", "…": "same shape as paths below" } ]
}
],
"paths": [
{
"id": 3,
"name": "Team Management",
"description": "Lead, motivate and grow your team day to day.",
"cover_picture": "https://…/640/360",
"lang": "en",
"translations": [
{ "lang": "en", "name": "Team Management", "description": "Lead, motivate…" },
{ "lang": "fr", "name": "Management d'équipe", "description": "Animer, motiver…" },
{ "lang": "de", "name": "Teamführung", "description": "Führen, motivieren…" }
],
"estimated_duration": "12 hours",
"categories": ["Management", "Sales"],
"modules": [
{ "id": 12, "name": "Manager postures", "type": "native", "duration_minutes": 30 }
]
}
]
}
| Field | Notes |
|---|---|
| categories[].parent_id / parent_slug | null for top-level categories. Use them to rebuild the category tree (one level of sub-categories). |
| packs[] | A pack bundles paths and/or individual modules sold as one (paths[] and modules[]). No price field — pricing happens on the quote. category is a single name. |
| paths[].lang / translations[] | lang is the default language. translations always contains every language line (default included) — pick the one matching your visitor's locale, fall back to lang. |
| paths[].categories | Array of category names — a path can belong to several categories. |
| paths[].modules | The course outline: id, name, type (native, elearning standard, external) and duration in minutes. Modules are sellable on their own: use their id with type: "module" in POST /orders. |
| visibility | Only paths that are published and in sync with 360Learning are exposed. A path removed on the 360L side silently disappears from the API (inside packs too) until it is remapped — cache accordingly and refresh regularly. |
POST /orders — send us a purchase order
The contract your checkout must fulfil.
curl -X POST https://360ecommerce.grauw.dev/api/v1/orders \
-H "X-Api-Key: demo-api-key" \
-H "Content-Type: application/json" \
-d '{
"company": {
"name": "New Corp", // required
"contact_name": "Jane Doe", // optional
"contact_email": "jane@newcorp.com" // required, valid email
},
"items": [ // required, at least 1
{
"type": "pack", // required: "pack", "path" or "module"
"id": 1, // required: id from the catalog
"learner_emails": [ // required, at least 1 valid email
"a@newcorp.com",
"b@newcorp.com"
]
},
{ "type": "path", "id": 3, "learner_emails": ["c@newcorp.com"] },
{ "type": "module", "id": 12, "learner_emails": ["d@newcorp.com"] }
],
"access_duration_months": 12, // optional, default 12 — content access duration for every learner
"notes": "Ordered from the website" // optional
}'
{
"reference": "PO-2026-0005",
"status": "pending",
"total": 0,
"currency": "EUR",
"message": "Order received — pending review by our team."
}
| Status | Meaning |
|---|---|
| 401 | Missing or invalid X-Api-Key. |
| 422 | Validation failed — the response lists the offending fields (standard Laravel errors object). |
| 404 | An item id doesn't match a published pack/path — re-sync your catalog copy. |
What happens next on our side: the company is matched by name (or created as a
provisional record), the order lands as a pending purchase order with one line per
cart item and the learner emails attached. Our team reviews it, prices the quote and
deploys the order (status deployed) — mapping
it to the final company, creating or reusing 360Learning groups, opening the trainings and
provisioning every learner email as a group member (account created + invitation sent).
The reference (PO-…) is your tracking key: quote it in any follow-up.
Integration checklist
What we expect from your e-commerce site.
✅ Pull GET /catalog regularly (or on publish) — don't hard-code training ids or names; paths can appear, change and disappear with the 360Learning sync.
✅ Use translations[] to display each path in your visitor's language, falling back to the default lang.
✅ Rebuild the category tree from parent_id / parent_slug — one level of sub-categories.
✅ Display no prices from the catalog — your checkout collects the request, our quote sets the price.
✅ Sell at the right granularity: type: "pack" with the pack id, type: "path" with the path id, or type: "module" with the module id found in paths[].modules.
✅ Collect one list of learner emails per cart item — these are the licenses we will open. Validate the email format client-side.
✅ Store the returned reference (PO-…) and show it to your customer as the order confirmation number.
Try it now
Paste this in a terminal — it hits the running demo instance:
curl -s https://360ecommerce.grauw.dev/api/v1/catalog -H "X-Api-Key: demo-api-key" | python3 -m json.tool