Docs

Automate tenant-scoped operations from CI using a long-lived FastYoke API token + curl.

CI Scripting

FastYoke does not ship a general-purpose API CLI (no fy schemas list / fy jobs transition — see the CLI reference for the full story on why). That sounds like a gap but rarely matters: the tenant API is REST/JSON, a FastYoke API token is a single-line bearer credential, and curl is the lingua franca of CI.

This recipe shows the three automations that come up most often: minting a CI token, provisioning schemas from a checked-in JSON, and bulk-seeding entity records.

For the API-token lifecycle reference (format, scopes, hard refusals, rotation pattern), see API tokens. This recipe is the walkthrough; that page is the reference.

Mint a tenant API token for CI

Admin-minted API tokens are long-lived, scope-gated, and revocable without touching a user password. This is the credential shape you want for pipelines.

  1. Navigate to **Admin → Settings → API Tokens** in the admin shell.
  2. Click **New token**.
  3. Name it something that identifies the caller, not the tenant — e.g. GitHub Actions – production deploy.
  4. Select only the scopes the pipeline actually needs. A deploy job that runs migrations + seeds entities typically wants data:write, workflow:execute, and forms:write. Leave admin:* off unless you really mean "everything".
  5. Pick an expiry — 90 days is the recommended default. "Never expires" exists but should be paired with out-of-band rotation.
  6. Click **Mint token**, then immediately copy the secret. It starts with fy_pat_ and is shown exactly once — FastYoke stores only the SHA-256 hash.

Store the token in your CI provider's encrypted secrets store (GitHub Actions / GitLab CI / CircleCI all ship one). Never commit the raw secret to a repo — revoke + re-mint if you do.

env:
  FASTYOKE_TOKEN: ${{ secrets.FASTYOKE_TOKEN }}
  FASTYOKE_TENANT_ID: acme
  FASTYOKE_BASE: https://fastyoke.example/api/v1

Provision schemas from a JSON spec

Version-control your FSM schemas as JSON in the same repo as your code. A simple CI job then creates (or updates) them on every merge to main:

#!/bin/bash
set -euo pipefail

AUTH="authorization: Bearer $FASTYOKE_TOKEN"

for file in schemas/*.json; do
  name=$(jq -r .name "$file")
  existing_id=$(curl -fsS "$FASTYOKE_BASE/tenant/schemas?tenant_id=$FASTYOKE_TENANT_ID" \
    -H "$AUTH" \
    | jq -r ".items[] | select(.name == \"$name\") | .id" \
    | head -1)

  if [[ -n "$existing_id" ]]; then
    echo "Updating schema $name ($existing_id)"
    curl -fsS -X PUT "$FASTYOKE_BASE/tenant/schemas/$existing_id" \
      -H "$AUTH" \
      -H "content-type: application/json" \
      -d "$(jq '{tenant_id: env.FASTYOKE_TENANT_ID, schema_json: .schema_json, name: .name}' "$file")"
  else
    echo "Creating schema $name"
    curl -fsS -X POST "$FASTYOKE_BASE/tenant/schemas" \
      -H "$AUTH" \
      -H "content-type: application/json" \
      -d "$(jq '{tenant_id: env.FASTYOKE_TENANT_ID, name: .name, schema_json: .schema_json}' "$file")"
  fi
done

Versioning: each PUT creates a new version row — the old version stays active on in-flight jobs until they finish. Your git history is the audit log for schema changes; the backend's fsm_schemas append-only table is the audit log for what was active when.

Bulk-seed entity records

Useful when migrating from another system, or when setting up ephemeral tenants for integration tests:

#!/bin/bash
set -euo pipefail

AUTH="authorization: Bearer $FASTYOKE_TOKEN"

jq -c '.[]' seed/vehicles.json | while read -r record; do
  curl -fsS -X POST "$FASTYOKE_BASE/tenant/entities/vehicle" \
    -H "$AUTH" \
    -H "content-type: application/json" \
    -d "{\"tenant_id\":\"$FASTYOKE_TENANT_ID\",\"data_payload\":$record}"
done

Transition jobs programmatically

For end-to-end tests that spin up a tenant, run a workflow, and assert on the terminal state:

# Fire a named transition
curl -fsS -X POST "$FASTYOKE_BASE/tenant/jobs/$JOB_ID/transition" \
  -H "$AUTH" \
  -H "content-type: application/json" \
  -d '{"tenant_id":"'"$FASTYOKE_TENANT_ID"'","event_type":"approve"}'

The response includes the new current_state. Guards run server-side via sandboxed JSONLogic (see Workflows) — failing a guard returns 409 with the guard's context so your test can assert on the rejection reason.

Error-handling discipline

A tiny helper saves you fifty if blocks:

fy_call() {
  local method=$1 path=$2 body=${3:-}
  local code
  code=$(curl -sS -o /tmp/fy-out -w "%{http_code}" \
    -X "$method" "$FASTYOKE_BASE$path" \
    -H "authorization: Bearer $FASTYOKE_TOKEN" \
    ${body:+-H "content-type: application/json" -d "$body"})
  if [[ $code -lt 200 || $code -ge 300 ]]; then
    echo "fastyoke $method $path$code" >&2
    cat /tmp/fy-out >&2
    return 1
  fi
  cat /tmp/fy-out
}

Revoking and rotating tokens

Revoke from Admin → Settings → API Tokens — click the token's Revoke row button, type the token's name to confirm, and any in-flight CI job starts getting 401s on its next request. There is no un-revoke; mint a replacement.

For periodic rotation, the cleanest pattern is double-rolling: mint the new token, push it to the CI secret store, let the next pipeline run pick it up, then revoke the old one. That avoids a window where the pipeline has neither credential.

Scope denials

When an API token hits an endpoint its scope grant doesn't cover, the backend returns 403 Forbidden with a body like:

{ "error": "api token scope 'workflow:admin' required but not granted" }

This is a feature, not a config error — add the missing scope to the token's grant (requires re-minting, since scopes are fixed at mint time) or narrow the pipeline to stay within the token's original permissions.

Rate limits + retries

Tenant-scoped endpoints aren't rate-limited today (modulo common- sense platform protections). Public-form endpoints ARE rate-limited per invite token — see the webhook intake recipe for the 429-specific error shape if you hit it.