scopes, extensions,
dashboardExtensions and workflow, nothing else; listing fields (name,
icons, URLs) stay with the console.
GET /api/v1/app/declarations with the same credentials reads the current
declarations back, so a startup script can diff before it writes.
Authentication
Send yourclientId and clientSecret in the body, or as a Basic auth
header (clientId:clientSecret). The secret is required, so a leaked client id
alone does nothing. This is the same pair your server already holds for token
requests.
What is validated
Exactly what the console would validate, because both paths run the same code:scopesmust come from the grantable list; unknown entries are dropped.extensionsembeds and blocks: https URLs, valid keys, per-list caps.dashboardExtensions: one of the twelve targets, label 1 to 40 characters, a path inside your app, heights within the kind’s limits.workflowactions and triggers: full schema validation, and actions need a webhook URL set before they can be declared.
400 with the field that failed, the same
message the console would show.
When to call it
- On startup or deploy, so the console always matches the code (idempotent: sending the same declaration again changes nothing).
- After you add a scope, extension kind or workflow step to your code.

