#Configuration & Config Schemas
Apps and workflows read their settings through config.*. A value there comes
from one of two layers: the definition (the author's app spec or workflow)
or the activation (what a tenant stored when it set the definition up). A
config schema is a JSON Schema you register once and name from the
definition, and every write to that scope is then validated against it.
Authenticate with a Personal Access Token (HTTP Basic auth). Enter it once in the Authorize bar above and every Try it below uses it.
#Two layers
| Layer | Where it lives | Who writes it | Behaviour |
|---|---|---|---|
| Definition | config.<scope> in the app spec, config.env in the workflow definition |
The definition's author | Live: an edit reaches every tenant that has not set that key itself. |
| Activation | Stored per tenant, integration or principal, written through the activation endpoints | The activating tenant | Wins over the definition, key by key. Only what the tenant supplied is stored. |
At render and at run time the two are merged per key: definition values underneath, stored values on top. The merge is top-level only, so a stored nested object replaces the definition's nested object whole.
#Scopes
| Scope | Definition key | Written with | Read as |
|---|---|---|---|
app |
config.app |
PUT /apps/{specId}/config |
config.app.* |
integration |
config.integration |
POST /apps/{specId}/integrations, PUT /apps/{specId}/integrations/{integrationId} |
config.integration.* |
company |
config.company |
PUT /apps/{specId}/principals/company/self |
config.company.* |
team |
config.team |
PUT /apps/{specId}/principals/team/{teamId} |
config.team.* |
user |
config.user |
PUT /apps/{specId}/principals/user/self, or a user id |
config.user.* |
env |
config.env |
POST /workflows/{id}/activate, PUT /workflows/{id}/config (workflows only) |
config.env.* |
companyis your own tenant. Its principal id is the calling tenant's id, andselfresolves to it. There is one company config per activation, shared by every integration under it. An external company's id is refused with a403.teamtakes a team id.selfis refused with a400, because a caller may belong to several teams.usertakesself(the caller) or a user id.- The scope names are a closed set. An app spec whose
configorconfigSchemasnames any other key is refused at publish with a422(spec_document.configSchemas.apps: Invalid key in record). A workflow acceptsenvonly.
#Declare a schema
#1. Register the schema
Register it in the tenant that owns the definition:
POST/schema/schemasRegister a config schemaAPI docs ↗Try it
A name may contain letters, digits, _ and -. A definition's reference to it
is matched case-insensitively.
#2. Name it from the definition
An app spec names one schema per scope under configSchemas, next to its
definition values under config:
{
"config": {
"app": { "threshold": 50 },
"company": { "tag": "standard" }
},
"configSchemas": {
"app": "fleet_app_config",
"company": "fleet_company_config",
"user": "fleet_user_config"
}
}
A workflow definition names one schema, for env:
{
"config": { "env": { "REGION": "us" } },
"configSchemas": { "env": "fleet_workflow_env" }
}
- A scope that is omitted, or set to
null, is not validated. An empty string is refused at publish. A workflow that has aconfigSchemasblock must setenvin it, to a name ornull. - The name is not looked up at publish. A definition that names a schema
nobody registered publishes fine, and the first write to that scope fails with
UNKNOWN_CONFIG_SCHEMA. Register the schema first.
Required env keys in a workflow. With configSchemas.env declared, list
required keys in the schema's required. A null value in the definition's
config.env is then refused at publish, so the two ways of marking a key
required cannot disagree. Without a schema, a null value in config.env marks
a key the activating tenant must supply. Activation and config writes fail with
422 and details.missing_env_vars until every such key is present. false,
0 and "" count as present.
#Consumers in other tenants
A tenant that activates a definition you shared needs to read your schema too.
Add every schema the definition names as a json_schema member of the manifest
that shares it (see Share with Another Tenant):
{
"members": [
{ "resource_type": "app_spec", "resource_name": "fleet-app" },
{ "resource_type": "json_schema", "resource_name": "fleet_app_config" },
{ "resource_type": "json_schema", "resource_name": "fleet_company_config" }
]
}
Without that member, every config write the consumer makes to the scope fails
with UNKNOWN_CONFIG_SCHEMA, and so does every run of a shared workflow whose
configSchemas.env names it. A consumer that registers a schema of the same
name in its own tenant is validated against its own copy instead: the caller's
tenant is checked first, then the definition owner's.
#How a write works
Every config write, at every scope, runs the same four steps:
- Merge over the stored row. Keys you send are set, a key sent as
nullis unset, and keys you do not mention keep their stored value. The merge is top-level: a nested object you send replaces the stored one. - Add the definition underneath. The definition's values for the scope go under the merged row. The result is the effective config.
- Validate the effective config against the scope's schema:
- Primitive values are coerced to the declared type.
"75"becomes75for anumber, and5becomes"5"for astring. - Schema
defaults are filled in. - Keys the schema does not declare are dropped. This happens when the schema
sets
additionalProperties: false, or declarespropertieswithout settingadditionalPropertiesat all. SetadditionalProperties: trueto keep them.
- Primitive values are coerced to the declared type.
- Store what the tenant supplied. The validated result is stored, minus the keys that came only from the definition, so a definition value stays live rather than being copied into the row. Filled-in schema defaults are stored.
A key you unset falls back to the definition's value if there is one. If the
schema has a default for it, the default is filled in again.
Concurrent writes to the same row are merged on the server, so two partial writes that touch different keys both succeed and both keep their keys.
#Worked example
With fleet_app_config above and "config": { "app": { "threshold": 50 } } in
the spec, each write below was made in order:
Request config |
Stored config afterwards |
Why |
|---|---|---|
{ "model": "m1" } |
{ "model": "m1", "label": "Default label" } |
threshold comes from the spec. The default is stored. |
{ "threshold": "high" } |
unchanged (422) |
"high" is not a number. |
{ "threshold": "75", "junk": true } |
{ "model": "m1", "label": "Default label", "threshold": 75 } |
Coerced. junk is not declared, so it is dropped. |
{ "model": null } |
{ "label": "Default label", "threshold": 75 } |
null unsets. |
{ "threshold": null } |
{ "label": "Default label" } |
The spec's 50 applies again. |
The first call to PUT /apps/{specId}/config creates the activation, so it must
carry name:
PUT/apps/{specId}/configActivate an app and write its app-scope configAPI docs ↗Try it
#Integration and principal config
An integration's config is validated against configSchemas.integration on
create and on update:
POST/apps/{specId}/integrationsCreate an integration with its configAPI docs ↗Try it
PUT/apps/{specId}/integrations/{integrationId}Change one key of an integration's configAPI docs ↗Try it
Principal config is written by natural key. Use company/self for your tenant's
company config and user/self for your own:
PUT/apps/{specId}/principals/{type}/{principalId}Write your tenant's company config (type company, principalId self)API docs ↗Try it
A principal write needs the app to be activated first; before that it fails with
404 "App not activated for this tenant." A form or a setPrincipalConfig step
inside the app writes through the same path. See
Apps.
#Workflow env
POST /workflows/{id}/activate and PUT /workflows/{id}/config both write the
tenant's env with the same four steps. The config PUT needs an existing
activation. Before one exists it fails with 404 "Workflow is not activated for
this tenant."
POST/workflows/{id}/activateActivate with the env keys the schema requiresAPI docs ↗Try it
PUT/workflows/{id}/configOverride one env key; null would unset itAPI docs ↗Try it
A PUT that carries only job_bindings leaves the env untouched. See
Job Bindings.
#Reading config back
GET /apps/{specId}/config, the integration reads and the principal reads return the stored config. Definition values are merged in at render.GET /workflows/{id}/configreturns the effective env: the definition'sconfig.envwith the stored values on top.PUT /workflows/{id}/configanswers with the same view.
#Reading config at render and run time
Definitions read config with smart values:
- App render:
{{ config.app.threshold }},{{ config.integration.region }},{{ config.company.tag }},{{ config.team.* }},{{ config.user.theme }}. Each scope is the definition'sconfig.<scope>with the stored row on top.config.integrationis the integration the request arrived through,config.companyis the activating tenant's company config, andconfig.useris the caller's. - Workflow:
{{ config.env.REGION }}in triggers, job step inputs, conditions and auth profiles.
Workflow env is validated again on every run, against the current
definition. An edit to the definition can therefore leave an activated tenant
unable to run. For example, the edit might stop supplying a key the schema
requires. The run is then refused with 400, and one
PUT /workflows/{id}/config that supplies the missing keys fixes it.
#Secrets in config
Config is where secret references live. A definition never contains one.
Store the value with the secrets API, wire {{ secrets.<scope>.NAME }} into a
config key, and have the definition read that key. Secrets
covers the scopes and which config may carry which reference.
Give a property that holds a reference "type": "string" in its schema.
Validation runs on the reference text, so a reference in a number property
fails with 422.
#Auth profiles read config
A root auth profile on an app spec or a workflow reads its credentials from
config, for example "token": "{{ config.app.api_token }}" or
"password": "{{ config.env.API_PASS }}". Each tenant's own config, including
the secret references wired into it, supplies the credential. A job bound into
the definition that names the profile uses the same values. See
HTTP Auth & External Data.
#Errors
Errors use the standard envelope. Match on error.code and details, not on
message.
| Situation | Status | error.code |
details |
|---|---|---|---|
| The effective config fails the schema | 422 |
VALIDATION_FAILED |
schema_name, scope, schema_errors[] (plus principal_type on principal writes) |
| The named schema is not visible to your tenant | 422 |
VALIDATION_FAILED |
code: "UNKNOWN_CONFIG_SCHEMA", schema_name, scope |
| A workflow without a schema is missing a required env key | 422 |
VALIDATION_FAILED |
missing_env_vars[] |
First app activation without name |
422 |
VALIDATION_FAILED |
spec_id |
| A secret reference in the wrong config | 422 |
VALIDATION_FAILED |
code: "SECRET_REF_SCOPE_VIOLATION", offending_secret_refs[] |
| A malformed or scope-less secret reference | 422 |
VALIDATION_FAILED |
code: "INVALID_SECRET_REF", token |
| The app spec is not visible to your tenant (for example, its publication was revoked) | 403 |
FORBIDDEN |
spec_id |
| Not activated yet (principal write, workflow config PUT) | 404 |
NOT_FOUND |
spec_id (app) or id (workflow) |
A run whose env fails configSchemas.env |
400 |
BAD_REQUEST |
schema_name, scope: "env", schema_errors[], workflow_id |
A run whose configSchemas.env schema is not visible |
400 |
BAD_REQUEST |
code: "UNKNOWN_CONFIG_SCHEMA", schema_name |
| A run of a workflow without a schema with a required key unset | 400 |
BAD_REQUEST |
missing_env_vars[] |
A 422 schema_errors entry names the path and the rule, for example
/threshold: Instance type "string" is invalid. Expected "number".
#See also
- Apps: specs, activation, integrations, forms that write principal config
- Workflows: activation, triggers, runs
- Secrets: secret references in config
- Job Bindings: the other half of an activation's config
- Share with Another Tenant: manifests and their members