When to use this endpoint

Use the recording catalogue with the shared CRM2 table widget’s start/length controls. This is a focused wrapper over Training_list(on_demand), not a generic mapped getDataTable endpoint; it does not accept arbitrary selected fields or general table filters.

Authentication and permissions

Authenticate with signed API-key fields or a logged-in CRM session. Every Training handler requires login; there is no additional page, role, department, or tenant feature-flag check. CRM2 exposes Training to authenticated users.

Webinars and session IDs belong to a shared global catalogue. Interests and provider registrations are scoped to the authenticated tenant and user even though they are stored globally. Client-supplied tenant/user IDs do not select another person’s bookings or progress.

Parameters

ParameterTypeRequiredDescription
startIntegerNoDefault zero, integer-converted and clamped nonnegative. Converted to page=floor(start/effectiveLength), so an unaligned offset rounds down to its page boundary rather than returning exactly that offset.
lengthIntegerNoDefault 10, integer-converted and clamped to 1–200. Zero/negative/nonnumeric values become 1; -1 does not mean export all.
categoryStringNoSame trimmed exact category IDs as Training_list; blank means all. No title/searchValue/general advanced-filter implementation exists here.
sortStringNoExact newest (default), az or za. Ignored/overridden when orderBy is exactly title.
orderBy / orderDescStringNoOnly orderBy=title changes sort: exact orderDesc=desc selects za, everything else selects az. Other orderBy values do not select arbitrary database fields.
view / page / page_lengthMixedIgnoredWrapper forces on_demand and derives page/page_length from start/length. Do not use these to bypass table pagination.

Example request

$result = FM_api('Training_videosTable', [
	'start' => '0',
	'length' => '10',
	'category' => 'jobs',
	'orderBy' => 'title',
	'orderDesc' => 'asc',
]);

Example response

{
  "data": [
    {
      "id": 1042,
      "webinar_id": 91,
      "title": "Jobs from start to finish",
      "description": "Create and schedule jobs with Fieldmotion.",
      "category": "jobs",
      "category_name": "Jobs",
      "image_url": "https://example.test/training.webp",
      "image_alt": "Fieldmotion Training",
      "presenter": "Fieldmotion trainer",
      "starts_at": 1792569600,
      "ends_at": 1792573200,
      "timezone": "Europe/Dublin",
      "status": "completed",
      "capacity": null,
      "registration_closes_at": 0,
      "registration_open": false,
      "registration_url": "",
      "interested": false,
      "registration_opened_at": 0,
      "join_available": false,
      "join_url": "",
      "registration_state": "none",
      "provider_mapped": false,
      "provider_available": true,
      "recording_provider_mapped": true,
      "recording_registration_state": "confirmed",
      "recording_duration": 3600,
      "watched_seconds": 3240,
      "recording_available": true,
      "recording_url": "",
      "recording_published_at": 1792659600
    }
  ],
  "categories": [
    {
      "id": "general",
      "name": "General"
    },
    {
      "id": "getting_started",
      "name": "Getting Started"
    },
    {
      "id": "jobs",
      "name": "Jobs"
    },
    {
      "id": "assets_stock",
      "name": "Assets & Stock"
    },
    {
      "id": "advanced",
      "name": "Advanced"
    },
    {
      "id": "scheduling",
      "name": "Scheduling"
    },
    {
      "id": "assets",
      "name": "Assets"
    },
    {
      "id": "stock",
      "name": "Stock"
    },
    {
      "id": "finance",
      "name": "Finance"
    },
    {
      "id": "forms",
      "name": "Forms"
    },
    {
      "id": "reports",
      "name": "Reports"
    }
  ],
  "total": 1,
  "stale": false,
  "unavailable": false,
  "recordsFiltered": 1,
  "recordsTotal": 1
}

Each session object includes integer id/webinar_id, Unix-second starts_at/ends_at, stored timezone, title/description/category/category_name, image_url/image_alt/presenter, session status, nullable capacity and integer registration_closes_at. Unknown category values normalize to general. Descriptions are returned as text values without HTML sanitization; render them as text, as CRM2 does.

Action/state fields are registration_open, registration_url, interested, registration_opened_at, join_available, join_url, registration_state, provider_mapped, provider_available, recording_provider_mapped, recording_registration_state, recording_duration, watched_seconds, recording_available, recording_url and recording_published_at. Flags are booleans, durations/times/counts are integers, and URL/state fields are strings. Unavailable URLs are empty strings, not omitted.

A provider-mapped live session requires cached registration_state=confirmed for joining; a legacy unmapped session requires local interested=true. The session must be scheduled, current time must be at least starts_at−900 and no later than ends_at, and the chosen personal/legacy join URL must be valid HTTPS without embedded credentials. Joining is not exposed earlier by get/list.

Recordings require completed session status, published recording status, and ends_at no later than now. Mapped recordings expose no shared recording_url: use Training_watch for personal access. Unmapped recordings expose a valid HTTPS URL. A recording can be available without live interest or a live booking. Images also require valid HTTPS without embedded credentials.

Adds recordsFiltered and recordsTotal to the Training_list envelope; both equal total after the selected category and publication/recording filters. These are not separate filtered/unfiltered catalogue counts. No draw echo or next-page cursor is supplied.

Errors and unusual responses

ResponseCondition
{"error":"not logged in"}No authenticated tenant context is available. Invalid signed credentials can be rejected by the dispatcher before the handler runs.
{"error":"invalid Training category"}Nonblank category is not in the list allowlist.
{"error":"invalid Training sort"}sort is unknown and was not replaced by title ordering.

Legacy application errors may be returned with HTTP 200. Check the decoded response for error, not only the HTTP status.

Behaviour and edge cases

  • Requires a published parent webinar, completed session, ends_at no later than now and published recording status. Mapped recordings or SQL https://-prefixed legacy URLs qualify according to provider-schema readiness. Final public URL validation can still mark a listed legacy record unavailable.
  • Pagination is page-based under the table wrapper. With start=15 and length=10, the effective offset is 10, not 15. start=20/length=10 selects the expected third page. There is no stable snapshot across requests.
  • Newest orders recording publication time descending then session ID descending. Alphabetical title order uses ID ascending as tie-breaker for both az and za. Records count session rows, not deduplicated webinar titles or recording broadcasts.
  • General widget fields/projection, draw, searchValue and arbitrary advanced filters are not implemented; only the explicit parameters above reach the list logic.
  • Provider workflows require the global provider schema (migration 011) and server WebinarGeek credentials. This is deployment configuration, not the account’s HR setting or a request-supplied API key. Without the provider schema, legacy URL-based catalogue/interests still work; without credentials, provider actions fail while cached catalogue data remains readable.
  • Refresh operates on all saved provider registrations for the authenticated user, not only the displayed category/page. Rows synced less than about five minutes ago are reused. Older rows are reconciled against provider subscriptions (up to 20 pages of 100); at most ten extra subscription-ID lookups recover entries omitted from provider lists. Cached statistics are retained when refresh fails. stale reports incomplete refresh and unavailable reports missing provider schema/configuration; these are not booking states or proof of live provider health.

Side effects

  • Like the on-demand list, a table read can reconcile provider registrations and write cached status, personal URLs, viewing counters and sync/edit timestamps. It does not create a registration, mark completion or send an independent notification.

See it in Fieldmotion

CRM1

No direct CRM1 caller for Training_videosTable was found. The implemented screen is CRM2 Training; no direct mobile-app or m-service caller was found for these CRM routes.

CRM2

Open Training → Watch Training Videos. Topic, Sort By, title-header sorting and pagination produce Training_videosTable requests through widgetDisplayStyle. The table is read-only; Watch Video uses Training_watch for mapped recordings or directly opens the returned legacy recording_url.

Related endpoints