Outcome
Publish a saved action while distinguishing catalog visibility, full app projection and uncertain downstream outcomes.
POST /api/developer-console/apps/{appId}/actions/{actionId}/publish
Before you start
Use the original organization with the published Developers apps:write grant and a separately discovered canonical Developers workspace at read_write access. Discover the exact accessible app record and current action id through supported reads; appId/actionId input is not a new permission.
The route requires an active, non-revoked/non-suspended developer context with onboardingState complete and app.canEdit. Owner/editor app access is required after the workspace match; a viewer or permission string alone does not authorize the mutation. The ordinary same-organization branch verifies the workspace with Auth. Its existing privileged cross-organization branch uses the seeded developer profile workspace, without honoring a requested workspace header; it does not bypass the app-workspace comparison.
Shared context can create a local identity mirror and initial developer profile for an enabled organization after platform workspace resolution, even before a later onboarding/app refusal. No Auth workspace is minted. Keep those possible initialization effects separate from the selected mutation.
Before a real mutation, read the complete declaration and publication state, review the exact before/after metadata and external effects, validate and plan against the current credential-scoped action, and obtain authorization for that concrete change. CLI --confirm or MCP confirm:true does not supply human authority.
Every example below is source-derived synthetic UNRUN. The fictional host, ids and timestamps grant no authority. No app action/declaration, manifest, Auth projection, provider, key or notification was created, updated, published or executed for these docs. The source pin is not proof of serving-runtime adoption.
Request and effects
Action: app_topolo_developers.apps.actions.publish. It is a confirmed mutation; validate and plan the real discovered payload and resource context before authorization/execution.
Success is HTTP200 {success:true,message,data:{action:<complete mapped declaration>}}. The action mapper includes the schemas, agent contract, identity, normalized flags/status, sourceRevision and publication/error/timestamps. Its response is a saved declaration state, not an execution of that declared endpoint.
There is no expectedRevision, conditional hash or request idempotency key in these action inputs. Concurrent editors and lost responses require a fresh same-context read and before/after comparison. Preserve actual uncertainty and partial outcomes rather than blindly repeating a write or using another organization.
The endpoint resolves the original app’s validated canonical appId and its action partition. Permission normalization removes the matching app prefix, lowercases and replaces periods with colons. apiBaseUrl is parsed as HTTP(S) without URL credentials and reduced to its origin; an embedded URL path is not kept. The distinct action path remains the endpoint path.
Changing permission, authorizationMode, principalAccess, allowedServices, accessPolicy, agentAccess, confirmation, schema or target changes the declared execution/access contract. Those flags are not grants to the editor or proof the implementation enforces them. Review the real provider/handler and all current caller-specific authorization separately; never execute the declared action to verify registration.
The publish handler first reloads and validates the stored declaration, then marks a non-archived definition published, updates lastPublishedAt/lastPublishError/updatedAt and changes the searchable action status and permission generation. Missing or archived definitions return404 Action not found or archived. This is not a new action body or a create-if-missing operation.
Only after that partition change does it call the full single-app publisher. That path can notify Auth permissions, synchronize first-party Auth/launcher projection, update the application manifest/catalog and invoke a developer notification. It processes the app’s relevant publication data, not merely a cosmetic field on one action. These operations are not a single distributed transaction and do not deploy the application’s code.
The app publisher can also return false without throwing when the app is missing or excluded by environment/surface/distribution/listing rules. Its exclusion branch may synchronize or retract an Auth projection and delete/purge the application catalog projection. This action handler does not inspect that boolean; it still returns HTTP200 with the action saved as published. Therefore HTTP200 here alone does not prove a public app listing, successful full manifest publication or complete Auth state.
On a thrown publisher failure the route attempts to set lastPublishedAt null and save a bounded error string, then propagates the failure. That compensating metadata update leaves status published and does not retract the already-published searchable action. Its own failure is swallowed. Earlier Auth/catalog/notification effects can also remain, so a failure response does not prove rollback.
The successful response uses the mapped action obtained before the full publisher call. It contains no durable application commit receipt or publisher boolean. sourceRevision remains the saved declaration’s revision marker; lastPublishedAt is a state marker, not a multi-service completion certificate. Repeated publishes can change timestamps and attempt downstream effects again.
Onboarding incomplete returns HTTP403 from this concrete route, although the retained generic catalog example names onboarding_incomplete at409. The raw route supplies error.message rather than that stable code. Verify actual status/body; do not fabricate a409/code pair from metadata. A non-ApiError thrown by the publisher is surfaced by the console catch as HTTP500 with error.message.
Verification needs both the exact saved declaration and the actual applicable application/Auth/catalog projection. Use supported published reads in the original context; do not infer public eligibility or notification delivery from lastPublishedAt, call the declared business endpoint, change app distribution or replay an unknown publication as a probe.
Worked requests and responses
Saved declaration published; eligible app publisher completes
Complete synthetic UNRUN HTTP200, assuming an accessible nonarchived stored declaration and an eligible app whose publisher returns successfully. The body itself has no manifest receipt or notification delivery result; source clock2026-10-21T08:00Z is fictional.
Input:
{
"appId": "app_docs_fixture",
"actionId": "app_docs_fixture.items.list"
}
Response:
{
"success": true,
"message": "App actions published successfully",
"data": {
"action": {
"actionId": "app_docs_fixture.items.list",
"target": "api",
"apiBaseUrl": "https://fixture.invalid",
"appId": "app_docs_fixture",
"developerId": "dev_docs_fixture",
"organizationId": "org_docs_fixture",
"name": "items.list",
"title": "List fixture items",
"description": "Return fixture items for illustration.",
"method": "GET",
"path": "/api/items",
"permissionName": "items:read",
"principalAccess": null,
"authorizationMode": "static",
"allowedServices": [],
"inputSchema": {
"type": "object",
"properties": {},
"additionalProperties": false
},
"outputSchema": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"items"
],
"additionalProperties": false
},
"agentContract": {
"docsUrl": "https://fixture.invalid/docs/items",
"resource": {
"kind": "item",
"idPath": "$.items[]"
},
"effects": [
"Reads fixture item identifiers without changing items."
],
"errors": [
{
"status": 403,
"code": "permission_denied",
"recovery": "Preserve the original scope and ask its legitimate owner to review the actual refusal."
}
],
"verify": [
"Check items belongs to the selected original resource context."
]
},
"readOnly": true,
"destructive": false,
"requiresConfirmation": false,
"agentAccess": "auto",
"status": "published",
"sourceRevision": "rev_app_docs_fixture.items.list_1792569600",
"lastPublishedAt": "2026-10-21T08:00:00.000Z",
"lastPublishError": null,
"createdAt": "2026-10-21T08:00:00.000Z",
"updatedAt": "2026-10-21T08:00:00.000Z"
}
}
}
Verify the actual declaration and applicable app/Auth/catalog projections separately; do not execute the newly declared endpoint.
Same success body after an exclusion returns false
Complete synthetic UNRUN HTTP200 with the same visible action state, assuming the app’s metadata makes it ineligible and the exclusion cleanup succeeds then returns false. The handler ignores false. The identical body cannot distinguish this case from complete eligible publication.
Input:
{
"appId": "app_docs_fixture",
"actionId": "app_docs_fixture.items.list"
}
Response:
{
"success": true,
"message": "App actions published successfully",
"data": {
"action": {
"actionId": "app_docs_fixture.items.list",
"target": "api",
"apiBaseUrl": "https://fixture.invalid",
"appId": "app_docs_fixture",
"developerId": "dev_docs_fixture",
"organizationId": "org_docs_fixture",
"name": "items.list",
"title": "List fixture items",
"description": "Return fixture items for illustration.",
"method": "GET",
"path": "/api/items",
"permissionName": "items:read",
"principalAccess": null,
"authorizationMode": "static",
"allowedServices": [],
"inputSchema": {
"type": "object",
"properties": {},
"additionalProperties": false
},
"outputSchema": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"items"
],
"additionalProperties": false
},
"agentContract": {
"docsUrl": "https://fixture.invalid/docs/items",
"resource": {
"kind": "item",
"idPath": "$.items[]"
},
"effects": [
"Reads fixture item identifiers without changing items."
],
"errors": [
{
"status": 403,
"code": "permission_denied",
"recovery": "Preserve the original scope and ask its legitimate owner to review the actual refusal."
}
],
"verify": [
"Check items belongs to the selected original resource context."
]
},
"readOnly": true,
"destructive": false,
"requiresConfirmation": false,
"agentAccess": "auto",
"status": "published",
"sourceRevision": "rev_app_docs_fixture.items.list_1792569600",
"lastPublishedAt": "2026-10-21T08:00:00.000Z",
"lastPublishError": null,
"createdAt": "2026-10-21T08:00:00.000Z",
"updatedAt": "2026-10-21T08:00:00.000Z"
}
}
}
Do not report public listing or full application publication from this response. Preserve the intended visibility and inspect supported current projections.
No publishable saved definition
Complete synthetic UNRUN HTTP404 after app/context checks, with a missing or archived stored action. No definition is published by this case. The source may validate an archived existing declaration first and refuse invalid metadata before this branch.
Input:
{
"appId": "app_docs_fixture",
"actionId": "app_docs_fixture.items.list"
}
Response:
{
"success": false,
"error": {
"message": "Action not found or archived"
}
}
Read exact current definitions and reconcile the actual missing/archive state; do not fabricate a new id, unarchive or recreate it as recovery.
Verify and recover
-
Read the exact saved declaration and its current publication/error state; a null timestamp or failure message does not imply its catalog status was retracted. Preserve the original action/app identity and current source marker.
-
Independently inspect the supported current application and credential-scoped action/Auth projections appropriate to the intended visibility. A successful action response cannot prove public listing or every downstream projection, and no business execution is needed to inspect registration.
-
For a failure/lost response preserve the actual request id, sanitized error and known/unknown partition, Auth, catalog and notification outcomes for the owner. A previously committed action is not rolled back by an error-metadata update; do not reset distribution, fabricate a receipt or blindly publish again.
There is no automatic inverse or atomic cross-service rollback. An intentional corrective declaration or publication requires a fresh reviewed plan and authority for its actual effects. Never restore old flags/content or delete a declaration merely to conceal uncertain outcomes.
Change log
- 2026-10-21: Added current-source mutation semantics, complete synthetic request/response examples, visibility and access distinctions, nontransactional outcome/recovery guidance and source-qualified status/error qualifications. Examples remain unrun.