Public API

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.

Base URL
https://360ecommerce.grauw.dev/api/v1
Header
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.

Request
curl https://360ecommerce.grauw.dev/api/v1/catalog \
     -H "X-Api-Key: demo-api-key"
Response 200
{
  "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 reference
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.

Request
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
}'
Response 201
{
  "reference": "PO-2026-0005",
  "status": "pending",
  "total": 0,
  "currency": "EUR",
  "message": "Order received — pending review by our team."
}
Errors
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