Frequently Asked Questions¶
Getting Started & Access¶
How do I get API credentials? Credentials are issued by FSRI during partner onboarding, after your partner agreement is in place. You receive three values per environment (Client ID, Client Secret, API key) over a secure channel. There is no self-service signup.
What tech stack do I need? Anything that makes HTTPS requests server-side. The API is plain REST + JSON (OAuth2 client_credentials). Ready-made clients exist for PHP, JavaScript, and Python, and a Postman collection covers every endpoint.
Can I call the API from a browser or mobile app? No. Your Client Secret and API key must stay server-side. Browser/mobile frontends should call your backend, which calls the FSRI API. The one browser-adjacent piece is the content URL — you embed it in an iframe/player, but you obtain it server-side.
Which environment do I start in? Sandbox, always. Production credentials are issued by FSRI once your sandbox integration is complete.
Authentication¶
Why do I need two credentials on every request?
The JWT bearer token (from your Client ID/Secret) authenticates you; the x-api-key header maps your traffic to its rate limits. They're validated by different layers, which is why they fail differently: bad/missing JWT → 401, bad/missing key → 403 with an empty body.
How long do tokens last? Is there a refresh token? Access tokens last 1 hour. There are no refresh tokens in the client_credentials flow — "refreshing" means requesting a new token exactly like the first one. Cache the token and re-mint ~5 minutes before expiry.
Can I request a token for every API call? Don't. The token endpoint isn't sized for per-request traffic. One token serves an hour of API calls.
What scopes should I request?
partner-api/read partner-api/write (space-separated) unless FSRI issued you a read-only client.
My token mints fine but every API call fails — why?
Two usual suspects: you're sending the token to the wrong environment (sandbox tokens don't work on production and vice versa), or you're missing the x-api-key header. See Error Handling.
How do I rotate credentials? Contact FSRI. Rotating the Cognito credentials issues a new Client ID and a new Client Secret together — the old pair is deleted and stops working immediately, with no overlap period, so plan a brief cutover. The API key rotates independently of the Cognito pair. Keep all three in configuration your backend can reload, and don't treat the Client ID as a permanent identifier.
User Management¶
Do my learners need FSRI accounts?
No. You register each learner once via POST /users/register and get back a UUID. That UUID — sent as the X-User-ID header — is their entire FSRI identity. They never log into anything FSRI-side.
What if I register the same email twice? You get the existing record back — registration is idempotent per partner. This makes retries after timeouts safe.
Can I see users registered by other partners?
No — and you can't detect them either. Users are partner-isolated; another partner's UUID behaves exactly like a nonexistent one (404).
The same person uses my LMS and another partner's — what happens? Each partner gets an independent registration with its own UUID and profile. Your view of the user is yours alone.
What's the difference between deactivating and deleting a user?
PATCH {"status": "inactive"} is reversible — the learner's data stays intact and you can reactivate them later. DELETE is permanent GDPR erasure: identifying data is removed, while transcripts/certificates/progress are retained but anonymized so already-issued credentials stay verifiable.
A learner invoked their right to be forgotten — what do I call?
DELETE /users/{uuid}. It returns 204 and cannot be undone.
Content & Completion¶
What happens when a learner fails an assessment? How do they retake it?
Retakes happen inside the content: re-open the module (request a fresh content_url as usual) and the package resumes at its results screen, where its own Retake control starts a new attempt. A failed attempt records progress but never completes the module — and it doesn't block anything: a later passing attempt records normally and completes the module then. There is no reset endpoint and none is needed; the package manages its own attempts. Your UI needs nothing special beyond letting the learner open the module again.
How do I display FSRI content in my LMS?
Call GET /modules/{id}/content with the learner's UUID, then embed the returned content_url — an <iframe> for interactive content and documents, a video player for video. FSRI hosts and renders everything, including the interactive package runtime.
Why do content URLs stop working after an hour?
They're signed and time-limited (expires_at, ~60 minutes) — that's the content protection model, not a bug. Fetch at click-time, and for long-form playback call GET /modules/{id}/refresh-url shortly before expiry.
Can I cache or hotlink content URLs?
No. Never persist a content_url — it will expire. Cache the catalog (course/module metadata); fetch content URLs on demand.
When do I call POST /modules/{id}/complete?
When a learner finishes a video, document, or resource module and confirms it in your UI. Never for interactive — the package reports its own completion, and the endpoint returns 409 to protect you from double-reporting.
Is /complete safe to retry?
Yes — it's idempotent. Repeat calls on an already-completed module are 202 no-ops.
Can partners upload their own content? No. The catalog is FSRI-curated and read-only via the API; only published content is visible.
Progress & Transcripts¶
How do I write progress data?
You don't — progress is read-only by design, so there's a single source of truth. Records are created automatically on first content access and updated by FSRI. You just read GET /course-progresses.
How fresh is progress data? Should I poll?
Progress updates as completion events arrive, and it is effectively immediate: events from the content player are processed synchronously, so a poll issued right after one should already reflect it. There is no ingestion queue to design around. Poll proportionately: delta-sync (updatedAt[after]) on a schedule for dashboards, on-demand for a single learner's page.
When does a course show as completed? When FSRI's system of record (the transcript) says so — course-level completion is derived from transcript records.
How do I get certificates?
When a transcript record has certificateAvailable: true, download GET /transcripts/{id}/certificate/pdf. The full transcript PDF is at GET /transcripts/pdf. Both generate on demand (1–3s).
A score seems wrong / a completion is missing — who fixes it? FSRI owns the learning record. Collect the specifics (UUID, module/course ID, timestamps) and raise it with your FSRI contact — don't compensate in your own UI.
Rate Limiting¶
What are my limits? Your API key is subject to rate limits (request rate + burst + daily quota) enforced at the API gateway. FSRI provides your specific limits with your credentials.
What happens when I exceed them?
429 Too Many Requests. Back off exponentially and retry (Error Handling). Sustained 429s mean your sync schedule needs spreading out, not faster retries.
Do content downloads count against my quota?
API calls (including /content and PDF endpoints) count. The media itself is served from FSRI's CDN via the signed URL and doesn't consume API quota.
Sandbox¶
How real is sandbox data? Sandbox carries real (published) production content, refreshed monthly. Users, progress, and transcripts in sandbox are yours to create, and are isolated both from production and from other partners. Catalog content — courses, categories, modules — is the opposite: one shared copy that every partner on sandbox reads.
Why did my sandbox test data change/disappear? The monthly refresh re-syncs content from production — content IDs can shift and test artifacts may be cleaned up. Write sandbox tests that bootstrap their own state instead of hardcoding.
Can I trigger a progress update for a test learner? Yes, entirely on your own — this needs nothing from FSRI. Progress is learner data, so it is created by calls you already make and is isolated to learners you registered.
The quickest self-test:
- Register a test learner (or reuse one you own) and keep their UUID.
GET /modules/{id}/contentwith that UUID inX-User-ID. That alone creates aCourseProgressrow atin_progressfor every published course the module belongs to. Calling it again on an existing row deliberately bumpsupdatedAt, so you can re-fire a delta on demand without building new state.- Poll
GET /course-progresseswith the sameX-User-IDandupdatedAt[strictly_after]=<your last sync>. The row comes back.
To exercise the whole lifecycle, launch the content and play the package through. It reports progress and completion via xAPI as the learner works, advancing completionPercent and completedModuleIds, and on finishing the required modules it writes a /transcripts record — the same path a real learner takes.
Effectively immediate — no queue to wait on
Statements from the player are handled synchronously, so a delta poll issued straight after an event should already see it. (Statements are also forwarded to FSRI's analytics pipeline, but that hand-off is asynchronous and sits outside the data /course-progresses and /transcripts read.) Build normal retry/backoff as you would for any API, but you don't need a deliberate wait for ingestion.
Two traps: POST /modules/{id}/complete will not shortcut this — it returns 409 for interactive content, which is every module in the catalog. And status=not_started always returns empty, because no row exists until the learner touches content; absence is the not-started state.
Can I trigger a course update to test my delta sync? First, check which "update" you mean: if you want a learner's progress to move, see the previous question — you can do that yourself. This answer is about a course's own metadata changing.
No. The catalog is read-only over the API and there are no webhooks. Sandbox catalog content is also a single copy shared by every partner, so we don't make one-off edits to it for testing — an edit for one partner would land in everyone's catalog and fire everyone's delta.
You don't need a synthetic catalog edit to prove your sync, for two reasons:
- The
updatedAtfilter semantics are identical on every collection that supports them. Validate your delta logic against/course-progressesand/transcripts, which you can move yourself (see the previous question), and the same code path covers/courses,/categoriesand/modules. - Real catalog deltas arrive on their own. Sandbox content re-syncs from production monthly, carrying production's timestamps, so genuinely changed courses show up in your
updatedAt[after]query after each refresh — with no hardcoded IDs to break, provided you discover IDs from the list endpoints.
Do sandbox credentials work in production? No — tokens, keys, and data are fully isolated per environment. Production requires its own credentials, issued by FSRI.
Didn't find your answer? Check Error Handling for error-specific help, or contact your FSRI integration contact.