Skip to content

Resource releases

ResourceRelease is the Python adapter for deployed FastAPI, Harness Agent, and static-site releases. It is available through the public SDK import:

from mainsequence.client import ResourceRelease

Find a release by exact name

releases = ResourceRelease.filter(
    name="Shared MetaTables",
    release_kind="fastapi",
)

name is an exact match. The existing string normalizer trims surrounding whitespace; substring and name__in lookups are not supported. Ordinary filter, iter_filter, and filtered get retain the current CodeRepositoryBranch context. Names can repeat across branches and release kinds.

If exactly one release is expected in the current scope:

release = ResourceRelease.get(
    name="Shared MetaTables",
    release_kind="fastapi",
)

get raises DoesNotExist for no match and ApiError for multiple matches. Filtered get returns the collection row. Fetch its UID directly when full release details are required.

Find a shared deployment owned by another repository

A shared MetaTables API can belong to a different CodeRepository than its consumer. Adding name to an ordinary query does not change the consuming process's branch scope. For intentional administrative discovery, use the existing explicit collection path with the deployment's owning branch:

matches = ResourceRelease.filter_admin(
    name="Shared MetaTables",
    code_repository_branch_uid="<OWNING_CODE_REPOSITORY_BRANCH_UID>",
    release_kind="fastapi",
)

if len(matches) != 1:
    raise ValueError("Expected exactly one visible Shared MetaTables release")

release = ResourceRelease.get(pk=matches[0].uid)

iter_filter_admin provides the same explicit scope while iterating collection pages. These methods do not inject the consuming checkout's branch or expand object permissions or an authenticated runtime's Environment boundary.

Use a known release UID

release = ResourceRelease.get(pk="<RESOURCE_RELEASE_UID>")

The public UID avoids display-name ambiguity and performs a detail lookup. For FastAPI runtime routing and readiness, use release.resolve_runtime_access() and the backend-returned routing information. Do not construct a deployment hostname from the name or UID.

Call a release

A release can be scaled to zero, and a request to it is then held until a replica starts. request keeps that start out of your timeout:

release = ResourceRelease.by_uid("<RESOURCE_RELEASE_UID>")
response = release.request("POST", "/v1/prices", timeout=60, json={"symbol": "ABC"})
response.raise_for_status()

by_uid makes a handle from the UID without contacting the platform, unlike get(pk=...), which fetches the release's details each time it runs. request itself asks the platform only on the first call in the process, when the release has shown no sign of serving within its idle window, when its token is about to expire, and after a timeout.

In asynchronous code use await release.arequest(...), which runs the same call on a worker thread.

The call first makes sure the release is ready. It asks the platform, which also starts the release, and waits while the answer is waking. Only then is the request sent, to the URL and with the token the platform returned, so timeout measures the application's own work. timeout is a total in seconds or a (connect, read) tuple. Other keyword arguments (json, params, data, headers) go to requests. The response is returned whatever its status. path is a path on the release; the SDK sets Authorization.

Argument Meaning
timeout Required. How long the application's work may take once it is running.
ready_timeout How long a start may take. Default: twice the start time the platform expects, at least 30 seconds; or MAINSEQUENCE_RELEASE_READY_TIMEOUT_SECONDS (300) when the platform states none.
idempotent True allows one more send, only after a timeout that happened while the release was starting. Default False: the request is sent once.

What the process remembers

The SDK keeps one in-memory cache per process, release_runtime_cache, with the platform's last answer for each release called, when the release last answered a request, and the last start this process waited for. While a release is known warm, request skips the platform call. A release is warm when its token is still valid and either the platform reports min_instances >= 1, or the release answered within its idle window less a margin (the larger of 10 seconds and 20% of the window). The window is the platform's idle_timeout_seconds, or MAINSEQUENCE_RELEASE_IDLE_TIMEOUT_SECONDS (60) when the platform states none.

The cache is not shared between processes and is not the truth: another caller may keep a release warm, and a deployment restarts it. Do not build another one. Read it with runtime_status(), which never contacts the platform and never starts the release:

status = release.runtime_status()
status.state                    # "warm", "cold" or "unknown"
status.last_cold_start_at       # when the last start this process waited for began
status.last_cold_start_seconds  # how long it took
status.process_started_at       # when the release's process started, as it reported

min_instances, idle_timeout_seconds and expected_cold_start_seconds are None until the platform returns a scaling object in its answer.

Failures

Each is a ReleaseCallError with resource_release_uid, phase (readiness or request), timeout_seconds and runtime_phase.

Error Raised when Sent?
ReleaseReadinessTimeoutError ready_timeout ended while the release was starting. No
ReleaseUnavailableError The platform answered that the release cannot take requests. No
ReleaseDeadlineTooShortError The caller of the current request will not wait long enough for the release to start. No
ReleaseColdStartTimeoutError timeout ended and the platform then shows the release starting. Yes
ReleaseRequestTimeoutError timeout ended on a serving release. Yes

After a request times out, the SDK asks the platform once to tell the last two apart. A start that finished before that question is recognised only when the platform returns scaling.serving_since; without it, such a call reads as ReleaseRequestTimeoutError.

wait_for_runtime_access() runs the same wait without sending a request, for code that only needs the platform's answer. Concurrent first calls to a cold release share one wait.

Starting several releases

prewarm_releases([release_a, release_b]) asks the platform to start each, in parallel, and returns at once. It accepts releases or UIDs and never raises. Call it from an application's lifespan with the releases it depends on, so a chain of cold releases costs about the slowest start instead of the sum.

Deadlines across calls

request sends Mainsequence-Request-Timeout-Ms, the time it will still wait. An application with install_request_identity reads it from a verified caller, and calls made with request inside that handler are bounded by what remains and send the remainder on. A call that cannot fit the target's expected start in that time fails with ReleaseDeadlineTooShortError before anything is sent. Without the header nothing is bounded.

Supported collection filters

The SDK exposes these filters through ResourceRelease.FILTERSET_FIELDS:

Field Supported lookups Meaning
name exact Shared release display name
uid exact, in Public release UID
code_repository_branch_uid exact Owning CodeRepositoryBranch UID
resource__uid exact, in Runtime repository-resource UID
related_job__uid exact, in Runtime backing Job UID
release_kind exact, in fastapi, harness_agent, or static_site
mcp_available exact Whether the active revision advertises an MCP endpoint

An exact lookup uses the field directly, such as name="Shared MetaTables". An in lookup uses the suffix and a sequence, such as release_kind__in=["fastapi", "static_site"]. Unsupported filter names fail locally before an HTTP request is sent.

MCP endpoints

A FastAPI release can serve MCP at its URL plus /mcp. mcp_enabled is the desired capability, applied by the release's next successful deployment, and mcp_connection describes the endpoint its active revision advertises, or is None:

from mainsequence.client import ResourceRelease

for release in ResourceRelease.filter(mcp_available=True):
    connection = release.mcp_connection
    print(release.name, connection.url, connection.transport)

mcp_connection carries url, transport (streamable-http), resource_metadata_url, organization_environment_uid and active_revision_uid. Availability is an advertised capability, not a health check. A response without these fields reads as False and None. To serve MCP from a FastAPI application, see Serving MCP.

Who can use a release

A release's access is decided by its policies:

  • viewer: see the release's configuration, revisions and logs, and call it: its API, site or MCP endpoint;
  • user: see the release and call it, without its logs;
  • editor: also change its configuration (scaling, concurrency, timeouts, CORS, public paths, MCP), activate static deployments, delete it, manage what its workload can reach, and see who has access;
  • owner: also decide who has access.

A call reaches the application as you: the application receives your identity and decides what you may do in it, and its logs can contain other callers' request data. resolve_runtime_access() and wait_for_runtime_access() need the release's invoke action, which every policy holds. The release's creator holds owner; repository workflows create releases as the Organization's automation identity, which holds no policy on them. Organization admins hold every action, and turning on acting for the requester is in no policy: only they do it.

The token that resolve_runtime_access() returns in access belongs to the login that asked for it. Asked with your Main Sequence login (mainsequence login or a token pair), it stops working within at most 60 seconds after you log out or that login session is revoked, even before it expires. Asked inside a hosted application with its own credential, it lasts until it expires. Either way the platform checks again, at least every 60 seconds, that the caller may still call the release.

A release also takes the policies of the branch that declares it: reader on the branch gives viewer, and editor or owner gives editor, whether the branch policy is held on the branch or passed down from its repository or a Project. get_access() shows each of those as a computed row with source inherited. A release that backs an Agent follows the Agent instead, and branch access does not reach it: the Agent's viewer and operator give viewer, its editor and owner give editor, and its user gives nothing. Such a release takes no policy of its own: attach_policy() raises BadRequestError whose payload has code sharing_managed_by_owner, so give access on the Agent; see Agents. A release's backing Job follows the release.

Read and change the policies with the access methods on ResourceRelease:

from mainsequence.client import ResourceRelease

release = ResourceRelease.get(pk=release_uid)

for row in release.get_access().computed:
    print(row.source, row.policy, row.parent_policy)

binding = release.attach_policy("user", principal_type="team", principal_uid=team_uid)
print(release.get_my_access().allowed_actions)

The access methods address the release by its UID alone and take no Environment; with a workload's runtime credentials they reach only the releases of that workload's Environment. A refused change raises PermissionDeniedError, whose payload names the required_action and the granting_policies. A repository workflow file's access entry for a release gives its workload the policy it names, such as policy: user. mainsequence access policies --type resource-releases lists the policies with their actions, and Who Has Access to an Object describes every method and model.

From the command line:

mainsequence code-repository resources access list <RELEASE_UID>
mainsequence code-repository resources access grant <RELEASE_UID> user --user <USER_UID>
mainsequence code-repository resources access revoke <RELEASE_UID> <BINDING_UID>
mainsequence code-repository resources access explain <RELEASE_UID> resource-releases.invoke

See Resource releases in the CLI reference.