Configuration API
The Configuration API lets an administrator create, read, update, and delete DIAL Core configuration entities while Core runs. Core stores each entity as a blob in the platform bucket of its blob storage, with secrets encrypted, and merges these entities with the config files. This reference is for platform engineers who automate configuration; it assumes you know how DIAL Core is configured.
The Configuration API is in preview: its endpoints are marked x-preview: true in the DIAL Core OpenAPI specification and can change without backward compatibility. POST /v1/ops/config/reload and the metadata listing are not marked preview.
Endpoints
| Endpoint | Purpose |
|---|---|
GET, PUT, DELETE /v1/{type}/platform/{name} | Read, create or replace, and delete one entity |
GET /v1/metadata/{type}/platform/{path} | List the stored entities of a type |
POST /v1/admin/validate | Check a batch of entities without writing anything |
POST /v1/admin/apply | Write a batch of entities |
GET /v1/admin/config/file/{type}[/{name}] | Read the entities that come from config files |
POST /v1/admin/config/file/migrate | Copy file entities into the platform bucket |
GET /v1/admin/health/config | Report entities that failed to load |
POST /v1/ops/config/reload | Re-read the config files and rebuild the configuration |
POST /v1/admin/system-export | Download the public and platform buckets as a ZIP archive |
{type} is one of models, interceptors, translators, roles, keys, routes, schemas, catalog_schemas, applications, toolsets, settings, policies, or policy_assignments. Every type is bound only to the platform bucket; any other bucket answers 404 with no body. For the full request and response schemas, see the DIAL Core API reference.
Who can call it
Every endpoint above requires the admin role: the caller must match access.admin.rules and must not use a per-request key. A denied call answers 403. See Access.
An empty access.admin.rules ([]) denies everyone the Configuration API. POST /v1/ops/config/reload is the exception: it uses the resource admin rule, so with empty rules every caller without a per-request key can reload. Leave access.admin.rules out to keep the built-in default instead.
Entity names
A name must match ^[A-Za-z0-9._%:@\[\]()-]+$ and must not end with /. Toolset names must match ^[A-Za-z0-9-_]+$. Global settings have one entity only, settings/platform/global.
Read an entity
GET /v1/{type}/platform/{name} answers from the stored blob only:
200with the entity JSON, itsname,status: "valid", and anETagheader.304whenIf-None-Matchmatches the stored ETag.404when no blob exists. An entity defined only in a config file also answers404; read it through/v1/admin/config/file/{type}/{name}.
An entity that failed to load answers 200 with status: "invalid" and a validationWarnings array of {"field", "message"} objects. The record clears on the first rebuild that accepts the entity.
Secrets are never returned: a key's key, an upstream or interface key, and secretExtraData are omitted. For applications and toolsets, auth_settings.client_secret and code_verifier are removed, and an admin gets client_secret_hint instead.
Create or replace an entity
PUT /v1/{type}/platform/{name} creates the entity or replaces it, and answers 200 with {"name": "<name>"} and an ETag header. The change applies before the answer on the instance that served it.
curl -X PUT "https://<DIAL_CORE_HOST>/v1/models/platform/gpt-4o" \
-H "Authorization: Bearer <ADMIN_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"type": "chat", "endpoint": "http://model-provider:5000/compat/openai/openai/deployments/gpt-4o/chat/completions"}'
Conditional headers protect concurrent edits:
| Header | Effect |
|---|---|
If-None-Match: * | Create only. Answers 412 when the entity already exists. |
If-Match: <etag> | Compare-and-swap. Answers 412 when the stored ETag differs. |
A 412 writes nothing.
Secrets on update
When a PUT replaces a model, key, or route, a secret field that is absent or null keeps the stored value; any string becomes the new secret. Changing a key's secret revokes the old secret, and deleting a key revokes its secret before Core answers.
Errors
| Status | When |
|---|---|
400 | The body is empty, is not JSON, is not a JSON object, or does not match the entity type, or the name is invalid. A key without a key, a project, or a role. A schema without a $id. An invalid global settings value. |
409 | Another model, application, toolset, or interceptor already uses the deployment ID. Another key already uses the secret; the secret is never echoed. A schema PUT changes the $id of an existing schema, or reuses another schema's $id. |
412 | A conditional header does not match. |
422 | The entity breaks a configuration rule, such as pricing, interfaces, translator references, or catalog properties, or it references an interceptor that does not exist. The body lists validationWarnings. |
With config.write.softValidation: true, a model whose only problem is a reference to a missing interceptor is written and answers 200. Rule violations still answer 422. See Config loading.
Delete an entity
DELETE /v1/{type}/platform/{name} answers 204 after it removes the blob, 404 when no blob exists, and 412 on an If-Match mismatch. Deleting settings/platform/global answers 204 whether or not it exists, and restores the global settings from the config files. POST on an entity URL answers 405.
List entities
GET /v1/metadata/{type}/platform/{path} lists the stored blobs of a type, in the same folder format as the resource API. It supports token, limit (default 100, from 0 to 1000), and recursive (default false). Entities that exist only in config files are not listed.
Validate and apply a batch
POST /v1/admin/validate and POST /v1/admin/apply take a batch of manifests:
{
"precheck": true,
"manifests": [
{
"kind": "Interceptor",
"name": "interceptors/platform/pii-redaction",
"spec": { "endpoint": "http://pii-interceptor:4088/api/v1/interceptor/handle" }
},
{
"kind": "Model",
"name": "models/platform/gpt-4o",
"spec": {
"type": "chat",
"endpoint": "http://model-provider:5000/compat/openai/openai/deployments/gpt-4o/chat/completions",
"interceptors": ["pii-redaction"]
}
}
]
}
kindis one ofSettings,Schema,CatalogSchema,Translator,Interceptor,Role,Key,Route,Model,ToolSet,Application,Policy, orPolicyAssignment.nameis<type>/<bucket>/<name>with the segment that matches the kind. The bucket isplatform, orpublicforApplicationandToolSet.Settingsis alwayssettings/platform/global.- Order. Core processes a batch by kind in the order listed above, so an entity can reference another one later in the same batch.
| Endpoint | precheck: true (default) | precheck: false |
|---|---|---|
/v1/admin/validate | Writes nothing. Answers 422 if any entry fails, with the valid entries marked SKIPPED. | Writes nothing. Answers 200 with each entry VALID or FAILED. |
/v1/admin/apply | Writes nothing if any entry fails, and answers 422. Otherwise writes all entries and answers 200. | Writes every valid entry, skips each failing one, and answers 200. |
Results report APPLIED, APPLIED_INVALID, FAILED, or SKIPPED for each entry. APPLIED_INVALID is a model written under soft validation with an unresolved interceptor reference.
Move file configuration to the platform bucket
GET /v1/admin/config/file/{type} lists the entities that come from config files, and GET /v1/admin/config/file/{type}/{name} reads one. File keys are hidden: keys answers 403 for every caller.
POST /v1/admin/config/file/migrate copies each file entity that is missing from the platform bucket into a blob, through the same path as apply. The config files are not changed.
{ "types": ["models", "keys"], "dryRun": true }
typeslists entity types, or["all"](default).- With
dryRun: true, nothing is written, and results reportwould_migrate,would_skip, orwould_failinstead ofmigrated,skipped, orfailed. - An entity already in the bucket is skipped with the reason
already in blob. - Keys are named
<project in lowercase>-<first 12 hex characters of the SHA-256 of the secret>. The secret keeps working, and results never include it. - Schemas are named after the last path segment of their
$id. A name that is not valid, or that collides with another schema, fails the migration; Core does not rename it.
After migration, a blob replaces the file entity of the same name. Remove migrated entities from the config files so the two sources do not drift.
Check configuration health
GET /v1/admin/health/config answers 200 with:
{
"status": "degraded",
"skipped": [
{ "id": "models/platform/broken-model", "reason": "<why the entity was skipped>" }
]
}
status is ok when no invalid entity is recorded. id is always the canonical ID, also for an entity from a config file.
Export the configuration
POST /v1/admin/system-export streams the public and platform buckets as one ZIP archive, for backups and migrations:
- Blobs are exported as stored, so secrets stay encrypted. The
encryption_keys/andcredentials/folders are never exported; keep the key material separately if you need to restore the archive. - The export holds the locks of both buckets while it runs, so writes wait and a second export answers
409. The locks expire after 5 minutes, and an export that takes longer fails. - A resource written within
resources.syncDelaymay not be in the archive yet. - Once streaming starts, a failure resets the connection and leaves an invalid ZIP. Check that the download completed before you use it.
- Entry names are blob paths and can contain characters that Windows tools reject, such as
:. Extract the archive on Linux or read it with a ZIP library.
The archive holds the full content of both buckets; treat it as confidential. adminSystemExport.stallTimeout is described in Background jobs.
Further reading
- Config loading — reload interval, invalid entity policy, and soft validation
- Access — admin rules, API-key IP checks, and sharing lifetimes
- Background jobs — the jobs Core runs beside request handling
- Backup and restore — protect blob storage and Redis
- Back up, restore, and set global properties — export, import, and global interceptors from the Admin home page
- Keys — API key entries, their roles, and IP limits