Skip to main content

Agent Download URL Endpoint Redesign Plan

Goal

Replace the V1 agent download URL surface with a documented V2 design that:

  • follows API Design Guidelines
  • removes the bespoke GET /internal/v1/agent/downloads/links response
  • supports the current deployment-instructions UI in apps/amm-web
  • leaves room for redirect-based downloads and future signed URLs

This plan addresses the missing agent release/download surface between the existing V1 API and the documented V2 design.


Current Problem

V1 exposes six endpoints under a global /internal/v1/agent namespace:

  • GET /internal/v1/agent/releases/latest
  • GET /internal/v1/agent/downloads/links
  • GET /internal/v1/agent/releases/{version}
  • GET /internal/v1/agent/releases
  • GET /internal/v1/agent/downloads/latest
  • GET /internal/v1/agent/downloads/{version}

The main design problem is GET /internal/v1/agent/downloads/links.

It returns a synthetic payload that mixes two concepts:

  • release metadata for the current stable channel (stable.version)
  • convenience download URLs that point at "latest" alias paths (latest.windows, latest.macos.arm64, etc.)

That shape does not match the V2 resource style used elsewhere in the docs. It is not a resource collection, not a detail resource, and not a summary endpoint.


Frontend Usage Today

The current deployment instructions flow is split across two concerns:

  • DeploymentInstructionsDrawerContent.tsx loads the enrollment token
  • WindowsInstructions.tsx and MacOSInstructions.tsx load installer URLs through agentReleaseLatestQueryOptions

The important detail is what the UI actually needs from the download API:

  • one display version label for the current stable release
  • one Windows download action
  • two macOS download actions (arm64, x86_64)

The UI does not need:

  • a combined stable + latest wrapper object
  • a separate "download links" domain
  • platform-specific filtering on the metadata request

The UI can render correctly from a single "current stable release" resource as long as that resource includes artifact download actions for Windows and macOS architectures.


Design Constraints

From Design Guidelines:

  • agent download data is not client-specific, so it should not live under /clients/{client_id}
  • this is shared reference-style data, so a root-scoped domain is appropriate, similar to catalog domains
  • platform artifacts are a small bounded relationship and are always needed when viewing a release, so they should be embedded in the release DTO
  • custom composite payloads should be avoided when a normal resource object can represent the use case
  • path names should use explicit resource nouns and resource-specific parameter names

This leads to a simple rule:

Model releases as the resource. Model downloads as actions on release artifacts, not as a separate "links" payload.


Create a new root-scoped domain:

  • /agent-releases

Reasoning:

  • the data is global, not tenant-scoped
  • "release" is the stable business resource already present in V1
  • it aligns better with existing V2 docs than a verb-like /downloads domain

Do not embed download URLs into Client.

Reasoning:

  • enrollment token is client-specific and already embedded there
  • agent installer artifacts are not always needed on every client fetch
  • mixing client identity with global release metadata violates the embedding rule

Proposed Resource Model

AgentRelease

Primary resource returned by detail and collection endpoints.

Suggested fields:

{
"channel": "stable",
"version": "1.8.3",
"published_at": "2026-02-24T21:23:53.082Z",
"notes_url": "https://github.com/Scalepad/scalepad-agent/releases/tag/v1.8.3",
"artifacts": {
"windows": {
"filename": "ScalepadAgent-1.8.3.msi",
"content_type": "application/octet-stream",
"size_bytes": 12345678,
"sha256": "abcdef...",
"download_url": "/v2/agent-releases/latest/download?channel=stable&platform=windows"
},
"macos": {
"arm64": {
"filename": "ScalepadAgent-1.8.3.pkg",
"content_type": "application/octet-stream",
"size_bytes": 23456789,
"sha256": "123456...",
"download_url": "/v2/agent-releases/latest/download?channel=stable&platform=macos&arch=arm64"
},
"x86_64": {
"filename": "ScalepadAgent-1.8.3.pkg",
"content_type": "application/octet-stream",
"size_bytes": 23456780,
"sha256": "654321...",
"download_url": "/v2/agent-releases/latest/download?channel=stable&platform=macos&arch=x86_64"
}
}
}
}

Notes:

  • artifacts should replace V1's platforms for clearer naming
  • download_url should be the API-owned download endpoint, not a raw CDN URL
  • the server may still redirect to CDN/S3/CloudFront internally

AgentArtifact

Embedded object. No separate top-level endpoint is required initially.

Do not use a Ref suffix.

Reasoning:

  • there is no fuller standalone artifact resource in this plan
  • the embedded artifact shape is the full shape consumers need

Proposed Endpoint Surface

1. Get current release for a channel

GET /agent-releases/latest?channel=stable

Purpose:

  • powers the deployment instructions drawer
  • returns one AgentRelease
  • includes bounded embedded artifacts with download actions

Why this replaces /downloads/links:

  • the UI gets the version and all installer actions from one canonical resource
  • no extra wrapper object is needed

2. Get release by version

GET /agent-releases/{version}

Purpose:

  • direct lookup for audit, support, or release-history views
  • returns one AgentRelease

3. List releases

GET /agent-releases

Query parameters:

  • channel optional
  • page_size
  • cursor
  • sort_by=version|published_at
  • sort_order=asc|desc

Purpose:

  • supports future admin/release history UI
  • follows standard paginated collection rules

4. Download current release artifact

GET /agent-releases/latest/download?channel=stable&platform=windows

GET /agent-releases/latest/download?channel=stable&platform=macos&arch=arm64

Behavior:

  • responds with 302 to the real artifact location
  • keeps CDN/storage layout private behind an API contract
  • preserves room for signed URLs later

5. Download versioned release artifact

GET /agent-releases/{version}/download?platform=windows

GET /agent-releases/{version}/download?platform=macos&arch=x86_64

Behavior:

  • same redirect behavior as above
  • useful for support, rollback, and release-history workflows

Endpoints To Remove Or Deprecate

Deprecate these V1 shapes once V2 consumers are migrated:

  • GET /internal/v1/agent/downloads/links
  • GET /internal/v1/agent/downloads/latest
  • GET /internal/v1/agent/downloads/{version}

Keep the V1 endpoints temporarily as compatibility shims if needed, but document them as legacy-only.

GET /internal/v1/agent/releases/latest, GET /internal/v1/agent/releases/{version}, and GET /internal/v1/agent/releases should map conceptually to the new V2 /agent-releases domain and can be retired after clients move.


Why This Fits The Frontend Better

Windows

Current UI behavior:

  • show Latest (x.y.z)
  • open one Windows download URL

With the proposed design:

  • call GET /v2/agent-releases/latest?channel=stable
  • read version
  • read artifacts.windows.download_url

macOS

Current UI behavior:

  • show Latest (x.y.z)
  • open one arm64 URL and one x86_64 URL

With the proposed design:

  • same single request as Windows
  • read artifacts.macos.arm64.download_url
  • read artifacts.macos.x86_64.download_url

Query simplification

The current frontend query API accepts platform and channel, but the UI does not need per-platform metadata requests.

After redesign:

  • keep only channel on the metadata endpoint
  • remove platform from the main query key
  • fetch one release object and render all cards from it

Once the V2 endpoint exists, update apps/amm-web as follows:

  1. Replace GET /internal/v1/agent/downloads/links with GET /v2/agent-releases/latest?channel=stable.
  2. Read Windows/macOS download actions from the returned AgentRelease.artifacts.
  3. Use version directly for the label instead of combining stable.version with latest.*.
  4. Rename agentReleaseLatestQueryOptions to reflect the real resource, for example agentLatestReleaseQueryOptions.

Related but separate cleanup:

  • the drawer currently fetches the enrollment token from V1 GET /internal/v1/enrollment-tokens
  • the documented V2 contract already embeds the active token on GET /clients/{client_id}

That token migration is not part of finding #1, but the deployment instructions flow should eventually switch to:

  • one client request for the active enrollment token
  • one root-scoped release request for installer artifacts

Backend Implementation Plan

Phase 1. Document the domain

  • add /agent-releases to docs/api/index.md
  • add domain overview and endpoint pages under docs/api/agent-releases/
  • define the AgentRelease and embedded artifact DTOs in docs first

Phase 2. Add V2 controller surface

  • create a V2 controller under the root /v2/agent-releases namespace
  • reuse the existing releases service where possible
  • map the current service output into the new DTOs

Phase 3. Move frontend to V2

  • update the deployment instructions query to call the new V2 endpoint
  • simplify the query cache key to channel-based metadata
  • update tests and mocks to the new response shape

Phase 4. Deprecate V1 routes

  • keep V1 routes during the transition window
  • mark them deprecated in generated OpenAPI
  • remove them after the frontend and any other consumers have migrated

Testing Plan

Per the repo rules for new API surface:

  • add unit/contract tests for every new V2 /agent-releases endpoint
  • add integration coverage to amm-services/apps/agent-web-api/scripts/validate_api_contract.py

Specific cases to cover:

  • GET /v2/agent-releases/latest?channel=stable
  • GET /v2/agent-releases/{version}
  • GET /v2/agent-releases pagination and sorting
  • Windows download redirect
  • macOS arm64 redirect
  • macOS x86_64 redirect
  • invalid platform / arch combinations
  • 404 for unknown version

Frontend tests to update:

  • DeploymentInstructionsDrawerContent integration coverage
  • installer-card rendering for Windows and both macOS architectures
  • error state when release metadata fails to load

Decisions

Recommended decisions for the redesign:

  1. Make agent-releases a new root-scoped V2 domain.
  2. Treat AgentRelease as the canonical resource.
  3. Remove the synthetic downloads/links response from the contract.
  4. Embed bounded artifacts inside the release DTO.
  5. Expose API-owned redirect download URLs from the artifact objects.
  6. Keep enrollment token retrieval separate and client-scoped.

If the team agrees with those six points, the resulting V2 design is straightforward and the current amm-web deployment instructions screen can migrate without needing another special-case endpoint.