Examplary
  • Start for free
    Developer docs

    Developer changelog

    Changes to the API, SDKs, embeds, and question type platform, in chronological order.

    1. External IDs and metadata on sessions and source materials

      Assessment sessions can now carry an externalId and a metadata map, like users already do. Use them to link a session to the attempt in your LMS, then find it again with ?externalId= on GET /exams/{id}/sessions. Set them when you create sessions, which now also accepts an array of up to 100 at once. Heads up: that endpoint now always returns an array.

      Source materials get metadata too, next to the externalId they already had. Slices inherit it.

      Metadata values are scalars. A PATCH merges into what's there, and setting a key to null removes it.

    2. Webhooks

      No more polling to find out whether a test has finished generating or a session has been graded: Examplary can now tell you. Register an HTTPS URL with POST /webhooks and pick your events: exam.generation.completed, exam.import.completed, exam.questions.generation.completed, exam.session.completed and exam.session.grading.completed. Generation and import events include the exam's questions, so you don't need a follow-up request.

      Requests are signed following the Standard Webhooks spec, and failed deliveries are retried for about 45 minutes. A webhook covers your own work by default, specific tests through resources, or the whole workspace for admins. The calls need the new webhooks:read and webhooks:write scopes.

      The webhooks guide covers payloads, signature checks and retries.

    3. Review generated questions before the exam generation embed finishes

      The exam generation embed used to complete the moment generation was done, so your user never saw the questions until they were back in your app. Set showPreview: true in the session's presets and they now get a read-only list of the generated questions first.

      From there they can choose Save questions, or Back to settings to tweak the settings and generate again (which clears the generated questions). The session only completes, and examplary:embed-status-update only fires with status: "completed", once they save. It's off by default. See the preview step reference for details.

    4. Bring your own student levels to the exam generation embed

      The student level dropdown in the exam generation embed used to offer only Examplary's built-in school levels. That's a poor fit if your learners are, say, beginner and advanced drivers. Pass availableStudentLevels in the session's presets and the dropdown shows only your levels instead:

      {
        "presets": {
          "availableStudentLevels": [
            { "id": "beginner", "name": "Beginner driver", "instructions": "Use simple language and avoid jargon." }
          ]
        }
      }
      

      The instructions go into the AI prompt, so generated questions match the level your user picked. The chosen level is saved with the exam, so grading and feedback use it later too. Keep instructions to two or three sentences. See custom student levels for the full shape.

    5. Write your own entries to the audit log

      Organizations get a full audit trail of significant actions, and you've been able to read it over the API for a while. Now your app can write to it as well — record an action it took in its own system so it lands in the same timeline as everything Examplary logs itself.

      POST /audit-logs takes an action in resource.action form, the object it acted on, and optional session, reason and before/after snapshots. It needs the new audit-logs:write scope — not granted by default — and only OAuth apps can call it; JWT and API-key callers get a 403. Entries are attributed to the token's own actor and marked as coming from an app, so they're never mistaken for Examplary's own.

    6. Store your own metadata on users

      Every user now carries a metadata field: a free-form map of your own keys and values. Hang an external reference straight off the Examplary user — the id for a person in your SIS, LMS or CRM — instead of keeping a separate mapping table on your side.

      Set it when you create a user or with PATCH /users/{id} (needs users:write), and read it back from GET /users or a single user (users:read). A patch merges into whatever is already there, and setting a key to null clears it. Values are scalars — a string, number or boolean — not nested JSON.

    7. Grade answers that contain files

      Question types that collect a file — an uploaded document, an image, a voice recording — used to hand the grading AI nothing but the file's URL as text. Now the file itself can go into the prompt.

      Add fileInputs to a question type's grading object: a JSONata expression that starts with = and resolves to a file URL or an array of URLs. It has the submitted answer and the question configuration in scope, just like responseProcessing.condition.

      {
        "grading": {
          "enabled": true,
          "fileInputs": "=answer.value"
        }
      }
      

      The files are fetched and attached to the end of the grading prompt, so the AI reads the document, image or audio directly instead of guessing from a link. Only files hosted on the Examplary media CDN are attached; any other URL is skipped. See the grading docs for the details.

    8. Choose where AI processing happens

      Workspaces on Examplary for organizations can now keep AI processing — generating questions, grading, feedback and scanning answer sheets — inside the EU. It's a workspace setting, so you can set it with PATCH /org:

      { "settings": { "aiRegion": "eu" } }
      

      settings.aiRegion is either global (requests go wherever there's capacity) or eu (processing and any files sent along with it stay in the EU). For organizations based in the European Economic Area, eu is the default; for everyone else, it's global. It applies to every request from then on, and each change lands in the audit log as org.ai_region_changed.

      The call needs the orgs:write scope and an Owner or Admin. Changing the region on a plan that doesn't include it returns a 403.

      The same toggle is under Account → Access & domains in the dashboard, and the data residency guide has the details.

    9. Deprecate a question type

      You can now set deprecated: true in a question type's definition to retire it gracefully. Existing questions of that type keep rendering, grading, regenerating and exporting exactly as before — but no new ones can be created, and orgs that haven't already enabled it can no longer discover or enable it.

      It's the clean way to phase out a type you've replaced. Setting public: false looks like it would do the same, but it also stops other orgs from resolving the definitions of questions they already have, so those questions stop rendering. See publishing question types for the details.

    10. POST /exams/{id} and POST /folders/{id} are gone