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.
- Navigate to **Admin → Settings → API Tokens** in the admin shell.
- Click **New token**.
- Name it something that identifies the caller, not the tenant
— e.g.
GitHub Actions – production deploy. - Select only the scopes the pipeline actually needs. A deploy
job that runs migrations + seeds entities typically wants
data:write,workflow:execute, andforms:write. Leaveadmin:*off unless you really mean "everything". - Pick an expiry — 90 days is the recommended default. "Never expires" exists but should be paired with out-of-band rotation.
- 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.
Related
- Authentication — full token shapes + the scope vocabulary.
- Webhook Intake — the inverse direction (external system posts in).