> ## Documentation Index
> Fetch the complete documentation index at: https://university.triggr-app.com/llms.txt
> Use this file to discover all available pages before exploring further.

# The Core config schema

> JSON-Schema-style description of formConfig / execConfig and every enum the validator enforces — render your editor from it.



## OpenAPI

````yaml /api-reference/openapi.json get /api/v1/core/schema
openapi: 3.0.3
info:
  description: >-
    The API an MSP's own portal (or a partner acting for it) uses to wrap
    Triggr: list clients, forms, workflows, executions and Core requests; embed
    forms as a signed-in user; run Core forms natively; act on approvals and
    manual steps; configure Core workflows; and receive events by webhook.


    Authentication: every request carries an organisation API key as
    `Authorization: Bearer tpk_…` (issued on the API Keys settings page; shown
    once). Every key can read the catalogue and mint form sessions; `core:run`,
    `core:configure` and `executions:manage` are opt-in scopes chosen when the
    key is created — see `x-triggr-scopes` and each operation's
    `x-triggr-scope`.


    Rate limits: 600 requests/min per key (bursts to 200) for reads and 60/min
    (bursts to 30) for writes; form-session mints have their own 120/min (bursts
    to 120). Authenticated responses carry `X-RateLimit-Limit` /
    `X-RateLimit-Remaining` (exposed to browsers); a 429 carries `Retry-After`.


    Paging: lists take `limit` (default 50, max 100) and `offset` and return
    `total` + `hasMore`; `since` (RFC 3339) is accepted where an operation
    declares it (executions, Core requests).


    Idempotency is OPTIONAL and opt-in: send nothing and every endpoint behaves
    exactly as before — existing integrations need change nothing. The
    operations that start or decide something (run a Core form, approve /
    reject, complete a manual step, cancel / retry an execution, register a
    webhook) additionally accept an `Idempotency-Key` header (1–255 characters;
    use a UUID per request). Retry a timed-out call with the same key and the
    same method, path and body within 24 hours and you get the first response
    back — status and body — flagged `Idempotent-Replayed: true`, instead of a
    second run. The same key with a different request is refused with 422; a
    repeat while the first is still in flight is a 409; a 5xx is never stored,
    so that retry runs for real. Keys are scoped to the API key that sent them.


    One-time values are the exception, and deliberately so: a webhook signing
    `secret` and a Core run's `secret` (the new starter's temporary password)
    are never written to our store, so a REPLAYED response carries `"secret":
    null` and an `idempotentReplayNote` saying where to get a fresh one. Record
    those from the original response — a replay cannot give them back.


    Webhooks: a delivery is a POST carrying `X-Triggr-Signature`,
    `X-Triggr-Event`, `X-Triggr-Delivery-Id` and `User-Agent:
    Triggr-Webhooks/1.0`, with a `WebhookEnvelope` body. Mind the two ids: the
    envelope's `id` names the EVENT and is unchanged across retries and
    redeliveries, so it is your dedupe key; `X-Triggr-Delivery-Id` names the
    DELIVERY ROW — the same on every retry of that delivery, new on a redelivery
    — and matches a row in the Deliveries list. Verify the signature by
    accepting the header if any `v1` matches your secret — there are two for 24
    hours after a rotation.


    ## Embedding


    **Which forms can be embedded.** A form can be embedded in your portal
    exactly when its `embeddable` field is true (every form in `GET
    /api/v1/forms`, `GET /api/v1/forms/{formId}` and the Core forms list and
    detail carries it). By auth type: `microsoft` and `google` — yes, as the
    signed-in user, through a form session; `none` (public) — yes, through a
    form session that attributes the submission to your portal user and client,
    or plainly from `shareUrl` (anonymous); `password` and `internal` — no: a
    session request is refused with `form_session_unsupported`, and neither can
    sign a visitor in inside a third-party frame. Core forms are embeddable when
    `public` (Microsoft sign-in). Branch on `embeddable`, not on your own list
    of auth types.


    **When a session ends.** Inside your frame, the form page tells your page
    when its embedded session can't be used, with `window.parent.postMessage({
    type: "triggr:form-session", state }, "*")` — exactly those two fields, no
    token. `state` is `"error"` when the handle couldn't be redeemed (already
    used, expired, or rate-limited past its retry) and `"expired"` when the
    signed-in session lapsed (noticed on reload or submit — and, on a Core form,
    on a lookup) — at most once per lapse. Nothing is posted when the page isn't
    framed. Listen for it, check `event.origin` equals your stored embed origin
    (`formsOrigin` / `coreFormsOrigin`), then mint a fresh session and replace
    the iframe's `src` with its `embedUrl`; without that, the visitor is left on
    a "reopen the form" message.


    **The embed host.** Every `embedUrl` and `shareUrl` begins with the
    organisation's canonical embed origin: `scheme://host[:port]` — `https`,
    lower-case host, no path, no trailing slash, no default port. It is the same
    for every form and session and does not change between releases — only if
    you change your forms domain — and `GET /api/v1/me` returns it as
    `formsOrigin` (Studio) and `coreFormsOrigin` (Core). Store that value as
    your embed host, and before framing a session compare it byte-for-byte with
    the ORIGIN of the returned `embedUrl` (`new URL(embedUrl).origin`; in .NET
    `new Uri(embedUrl).GetLeftPart(UriPartial.Authority)`). Worked example —
    stored embed host `https://forms.acme-msp.com`; `embedUrl`
    `https://forms.acme-msp.com/forms/org_2Ab3/9c8d…?formSession=3f9a…` → origin
    `https://forms.acme-msp.com` → match. With no custom forms domain the origin
    is `https://app.triggr-app.com` (Studio) or `https://core.triggr-app.com`
    (Core). If you store a bare host instead (`forms.acme-msp.com`), compare it
    with the URL's host component, lower-cased.


    Errors are `{ "error": "…" }`: 400 your request, 401 the key, 403 scope or
    not your client, 404 not found, 412 a stale If-Match, 429 rate limit, 503 a
    dependency such as the on-prem agent. The two form-session endpoints instead
    answer `{ "error": { "code", "message", "retryable" } }` (SessionError).
    Timestamps are RFC 3339 UTC.
  title: Triggr Public API
  version: 1.4.0
servers:
  - url: https://api.triggr-app.com
security:
  - ApiKeyAuth: []
tags:
  - description: Who this key is, and the clients every other endpoint scopes by.
    name: Organisation
  - description: >-
      Studio forms with shareable embed URLs, and form sessions that embed a
      `microsoft` or `google` form as an already-signed-in user. Branch on each
      form's `embeddable` (see Embedding): `microsoft` and `google` forms embed
      as the signed-in user, `none` forms embed with a session attributing the
      submission to your portal user (or plainly from `shareUrl`), and
      `password` / `internal` forms cannot be embedded.
    name: Studio forms
  - description: >-
      The Studio workflow catalogue and a status feed of runs: what ran, for
      which client, how it ended. Outcome only — never parameters, step context
      or logs.
    name: Workflows & executions
  - description: >-
      Core forms (onboarding, offboarding, …) with their configuration status,
      embedded sessions for the public ones, and every request submitted through
      them with live status.
    name: Core forms
  - description: >-
      Render and submit a Core form in your own UI as a technician. Scope
      `core:run`.
    name: Core run
  - description: >-
      Read and write Core configuration. A form has one base config and optional
      per-client overrides; writes merge (omitted keeps, formConfig/execConfig
      replace as units, null clears, `inherit` clears a client's availability);
      read the `etag` and send If-Match to write atomically. Scope
      `core:configure`.
    name: Core configuration
  - description: >-
      The work waiting for a person. Read with any key; decide with scope
      `executions:manage`, naming the actor.
    name: Approvals & manual steps
  - description: >-
      Signed event delivery to your endpoint, so you don't poll. Register a URL,
      verify `X-Triggr-Signature` (accept the header if any `v1` matches — there
      are two for 24 hours after a secret rotation), inspect and re-send
      deliveries.
    name: Webhooks
  - description: This document.
    name: Meta
paths:
  /api/v1/core/schema:
    get:
      tags:
        - Core configuration
      summary: The Core config schema
      description: >-
        JSON-Schema-style description of formConfig / execConfig and every enum
        the validator enforces — render your editor from it.
      responses:
        '200':
          content:
            application/json:
              example:
                execConfig:
                  approval:
                    mode:
                      enum:
                        - none
                        - internal
                        - external
                formConfig:
                  sections:
                    items:
                      …: …
                    type: array
              schema:
                type: object
          description: OK
        '401':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: Missing, invalid, revoked, or expired API key
      security:
        - ApiKeyAuth: []
      x-codeSamples:
        - label: curl
          lang: curl
          source: |-
            curl -X GET "https://api.triggr-app.com/api/v1/core/schema" \
              -H "Authorization: Bearer $TRIGGR_API_KEY"
        - label: Node
          lang: node
          source: >-
            const res = await
            fetch("https://api.triggr-app.com/api/v1/core/schema", {
              method: "GET",
              headers: {
                "Authorization": `Bearer ${process.env.TRIGGR_API_KEY}`,
              },
            });

            if (!res.ok) throw new Error(`Triggr ${res.status}: ${await
            res.text()}`);

            const data = await res.json();
        - label: PowerShell
          lang: powershell
          source: >-
            $headers = @{
              Authorization = "Bearer $env:TRIGGR_API_KEY"
            }

            Invoke-RestMethod -Method Get -Uri
            'https://api.triggr-app.com/api/v1/core/schema' -Headers $headers
components:
  schemas:
    Error:
      properties:
        error:
          type: string
      required:
        - error
      type: object
  securitySchemes:
    ApiKeyAuth:
      bearerFormat: tpk_…
      description: >-
        Organisation API key. Scoped operations carry x-triggr-scope; the scopes
        themselves are described in x-triggr-scopes.
      scheme: bearer
      type: http

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.