Topolo Docs Topolo Platform
Open workspace

Replace an app’s API key resource declarations

Replace the complete saved workspace-resource configuration while preserving precise deletion, validation and partial-failure semantics.

Outcome

Replace the complete saved workspace-resource configuration while preserving precise deletion, validation and partial-failure semantics.

PUT /api/developer-console/apps/{appId}/api-key-resources

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 entire existing resource-declaration collection and publication/error metadata, review the complete intended replacement or publication and its Auth effects, validate/plan exact input plus resource context, and obtain authority for that concrete change. CLI --confirm/MCP confirm:true is not human approval.

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.api_key_resources.update. This is a confirmed mutation. The examples are fictional; discover the actual app/workspace and validate/plan the full intended change before its concrete authorization.

These routes require Developers apps:write, selected workspace read_write, completed onboarding, a non-suspended/non-revoked profile and app.canEdit. They describe resource-type/provider configuration; they do not mint an API key or grant the caller a real workspace/resource. Key eligibility remains the supported caller-specific Auth api_keys.options contract.

The complete success envelope is {success:true,message,data:{apiKeyResources:[...]}} for replacement, and adds data.sync for publication. Each record is the saved19-field resource declaration projection, not an actual bindable resource instance. resourceId identifies the declaration and must not automatically become a key’s resource binding id.

No expectedRevision/conditional digest or explicit idempotency key is present in these inputs. Read exact current state before a retry after interruption; no cross-database/Auth transaction or automatic inverse is promised. Preserve original ownership/scope and any known/unknown downstream effects.

This PUT replaces the entire saved resource-declaration set for the app. The handler accepts apiKeyResources or resources arrays (the native published object input requires appId); sending apiKeyResources:[] intentionally deletes every local declaration. It is not a merge or patch of one row. A missing/nonarray collection is rawHTTP422 Invalid request body with details.apiKeyResources:true.

Before deletion, the helper reads existing rows, associates defaults by raw requested resourceType, and normalizes every supplied entry. At this source revision each normalized entry must be resourceType workspace, pattern workspace:*, concept/canonicalType workspace, defaultEntitlement enterprise and exceptionPolicy app_or_organization. resourceLabel is nonblank at most120 and resourceEmptyLabel nonblank at most180. Status is draft/published/archived. Other resource types, labels and policy values are not supported simply because the generic action schema has permissive nested fields.

syncProvider must have kind http and a nonblank url string, or a url object containing both nonblank staging and production values. The local check is presence/shape, not URL-origin safety, provider authentication, reachability or actual resource enumeration. Never put real credentials in authored configuration or execute an illustrative provider URL as validation.

After all normalizations and a verified app workspace id, the helper DELETEs all app rows and INSERTs each supplied record sequentially, then lists the result. There is no transaction around deletion and insertion. An insertion failure can leave an empty or partial set; duplicate resource ids are not pre-deduplicated by this helper. Do not describe every failed replacement as unchanged.

resourceId uses supplied id, matching existing type’s id or defaults to <appId>.workspace. Source aliases are not automatically admitted native input fields; use the actual published schema. Reinserted rows get generated rev_<resourceId>_<seconds>, fresh createdAt/updatedAt and null lastPublishedAt/lastPublishError, even when an earlier id is retained. This marker is not a Git revision or compare-and-swap token.

The replacement handler does not synchronize Auth. Saving an explicit published label does not perform the separate publish action or prove Auth has this new set; old Auth registration can remain while local rows are changed or deleted. A real intended registration update needs its own reviewed publication and readback.

Worked requests and responses

Save one complete draft resource declaration

Complete source-derived synthetic UNRUN HTTP200, assuming the full replacement is authorized and normalization/delete/insert/read all succeed at the fictional clock. New default id is app_docs_fixture.workspace. No actual provider or Auth registration is contacted.

Input:

{
  "appId": "app_docs_fixture",
  "apiKeyResources": [
    {
      "resourceType": "workspace",
      "resourcePattern": "workspace:*",
      "resourceConcept": "workspace",
      "canonicalResourceType": "workspace",
      "resourceLabel": "Workspace",
      "resourceEmptyLabel": "No workspaces",
      "defaultEntitlement": "enterprise",
      "exceptionPolicy": "app_or_organization",
      "syncProvider": {
        "kind": "http",
        "url": "https://fixture.invalid/api/workspaces"
      },
      "status": "draft"
    }
  ]
}

Response:

{
  "success": true,
  "message": "API key resources saved successfully",
  "data": {
    "apiKeyResources": [
      {
        "resourceId": "app_docs_fixture.workspace",
        "appId": "app_docs_fixture",
        "developerId": "dev_docs_fixture",
        "organizationId": "org_docs_fixture",
        "resourceType": "workspace",
        "resourcePattern": "workspace:*",
        "resourceConcept": "workspace",
        "canonicalResourceType": "workspace",
        "resourceLabel": "Workspace",
        "resourceEmptyLabel": "No workspaces",
        "defaultEntitlement": "enterprise",
        "exceptionPolicy": "app_or_organization",
        "syncProvider": {
          "kind": "http",
          "url": "https://fixture.invalid/api/workspaces"
        },
        "status": "draft",
        "sourceRevision": "rev_app_docs_fixture.workspace_1792569600",
        "lastPublishedAt": null,
        "lastPublishError": null,
        "createdAt": "2026-10-21T08:00:00.000Z",
        "updatedAt": "2026-10-21T08:00:00.000Z"
      }
    ]
  }
}

Read back this complete saved set; the local draft is not an Auth grant or a provider execution.

Intentionally replace the local set with no declarations

Complete synthetic UNRUN HTTP200 for an explicitly reviewed destructive empty replacement. It deletes the local set but does not perform Auth sync or certify that prior Auth registration disappeared. No live deletion occurred.

Input:

{
  "appId": "app_docs_fixture",
  "apiKeyResources": []
}

Response:

{
  "success": true,
  "message": "API key resources saved successfully",
  "data": {
    "apiKeyResources": []
  }
}

Verify the empty local set and retain any separate Auth uncertainty; this example is not a harmless health probe.

Onboarding blocks replacement

Complete synthetic UNRUN HTTP403 at the onboarding check before selected replacement. Shared context initialization can already have occurred.

Input:

{
  "appId": "app_docs_fixture",
  "apiKeyResources": [
    {
      "resourceType": "workspace",
      "resourcePattern": "workspace:*",
      "resourceConcept": "workspace",
      "canonicalResourceType": "workspace",
      "resourceLabel": "Workspace",
      "resourceEmptyLabel": "No workspaces",
      "defaultEntitlement": "enterprise",
      "exceptionPolicy": "app_or_organization",
      "syncProvider": {
        "kind": "http",
        "url": "https://fixture.invalid/api/workspaces"
      },
      "status": "draft"
    }
  ]
}

Response:

{
  "success": false,
  "error": {
    "message": "Complete onboarding before managing API key resources."
  }
}

Preserve actual refusal and original context; no grant/role change or alternate organization as bypass.

Verify and recover

  1. Read the entire actual saved collection in the same app/workspace and compare every requested row, ids/types/provider settings/status, count, generated revisions and reset publication metadata. Saving a row is not proof Auth was changed.

  2. After failure or interruption compare the exact old and intended sets against current saved rows. Preserve a possible empty/partial replacement and concurrent changes for the owner; do not assume repeating a PUT is lossless or reconstruct missing values from guesses.

  3. If publication is intended, separately review/authorize the current complete nonarchived set and Auth effect before calling the discovered publish action. No key mint, provider call or grant expansion is needed to verify the saved configuration.

There is no automatic inverse across local deletion/insertion and Auth synchronization. Any intentional corrective replacement or publication needs a fresh complete review and concrete authority; retain history/effects rather than masking uncertainty.

Change log

  • 2026-10-21: Added current source-qualified complete-set/resource-type validation, saved-versus-Auth distinctions, actual success/error carriers and nontransactional recovery, with synthetic unrun examples. No key, provider or production business operation was performed.