Added

  • Assign a resource to projects: a resource is shared by the whole workspace until it is assigned, after which only the projects it is assigned to may place workloads on it. Anything else is refused with 422.
    • POST /api/v1/projects/{project_id}/resources with resource_id assigns one. Needs both projects:manage and resources:manage.
    • DELETE /api/v1/projects/{project_id}/resources/{resource_id} returns it to the shared pool. Needs only projects:manage — removing an assignment only ever widens access, and every workload already there stays legal.
    • Assigning is refused with 422 while the resource still runs workloads from a project outside the resulting list. The error names those projects and their workload counts, except the ones you cannot reach, which it reduces to a count. Send with_hosting_projects: true to assign the resource to those projects too and let the call through; when a blocking project is redacted this is the only way forward, since you cannot name a project you cannot see.
  • Assignment on the resource response: every resource carries assigned (boolean) and project_ids (project UUIDs). assigned: false means any project may deploy on it. Read both before offering a resource_id: the resource is usable when assigned is false, or when project_ids contains the target project. project_ids lists only projects you can access.
  • Project access: a project can be closed to a list of members and roles, returned as restrictions on the project response. Empty means the project is open to the whole workspace.
    • POST /api/v1/projects/{project_id}/restrictions with exactly one of user_email or role_name; sending both or neither is a 400. Adding the first entry is what closes the project.
    • DELETE /api/v1/projects/{project_id}/restrictions/{id} removes one entry, where {id} is the numeric id from the restrictions list. Removing the last one reopens the project.
    • Both need projects:manage, and are available on organization and enterprise workspaces only. The plan refusal is checked before the subject, so on a smaller plan you get the plan message even when the email is also wrong.
  • Move a workload between projects: project_id on PUT /api/v1/apps/{uuid}, PUT /api/v1/static/{uuid}, PUT /api/v1/services/{id}, PUT /api/v1/stacks/{uuid} and PUT /api/v1/buckets/{uuid} moves it to another project while it keeps running on the same resource. Moving a stack moves every application and service in it. Moving needs manage-level permission (apps:manage, services:manage), not the operate level the rest of the update takes.
  • Buckets can belong to a project: project_id is accepted on POST /api/v1/buckets and PUT /api/v1/buckets/{uuid}. It is never inferred — omit it and the bucket has no project, even in a workspace with exactly one. Send null on update to clear it.
  • Workspace API tokens: tokens created under Settings → Developers can now be scoped to a workspace instead of a person. A workspace token carries its own permission list and its own project list, and nothing else is consulted — not the role of whoever created it, not workspace ownership. It is pinned to its workspace (an X-Workspace-Id naming a different one is refused with 403), cannot reach /api/v1/users/me or anything under it, and cannot be granted workspace administration, so it can never mint another token. Tokens are managed in the dashboard only; there is no endpoint for them.

Changed

  • Webhook endpoints require workspace:general, not workspace:integrations. API tokens and webhooks moved to their own Developers settings tab, and the permission moved with them.
  • A project you cannot reach is invisible rather than forbidden. Restricted projects are absent from GET /api/v1/projects, and so are their applications, static sites, services, stacks and buckets in their own listings. Addressing any of them by UUID returns 404, never 403. The same applies to resources assigned solely to projects you cannot reach; an unassigned resource stays visible to everyone. A short list is no longer proof that the workspace holds nothing else.
  • A project_id you cannot reach and one that does not exist answer identically: 404 with {"error": "Project not found"}, on every endpoint that accepts a move. Telling them apart would confirm a restricted project is there. Read this as “the destination could not be resolved” — the workload is untouched and still in its original project.
  • Creating or updating a resource no longer falls back to ownership. PUT /api/v1/resources/{uuid} and PUT /api/v1/resources/{uuid}/labels previously allowed the user who created the resource through regardless of role; they now require resources:operate like every other caller.

Fixed

  • The permission reference was wrong in both directions. It listed apps:create, resource:view, resource:manage and workspace:settings, none of which exist, and omitted every services: permission along with projects:operate and five of the seven workspace: ones. A 403 can now be turned into the name of a permission that can actually be granted.

Added

  • Static sites: a new resource type with its own endpoints under /api/v1/static. A static site serves prebuilt HTML, CSS and JavaScript from object storage — there is no compute resource, no replicas, no ports and no environment variables. It is not an application and is not reachable through /api/v1/apps.
    • GET /api/v1/static and POST /api/v1/static to list and create.
    • GET, PUT and DELETE /api/v1/static/{uuid} to read, rename or move between projects, and delete the site with its content and domains.
    • PUT /api/v1/static/{uuid}/deployment to update build and routing settings. Applied as a patch: fields you omit keep their stored value. source_type is not accepted here.
    • POST /api/v1/static/{uuid}/deployments to deploy — send a multipart archive (zip, max 1 GB) for a zip site, or omit it to rebuild a github site. Returns 409 Conflict while a deployment is in flight.
    • GET /api/v1/static/{uuid}/deployments for deployment history.
    • POST /api/v1/static/{uuid}/deployments/{id}/cancel to stop a build that is still running. Only git_push and github sites run a build; a zip or sftp deployment is a direct sync with nothing to interrupt.
    • POST /api/v1/static/{uuid}/deployments/{id}/rollback to republish the site from the commit that deployment shipped. github only — a static site produces no image, so there is no stored artifact to redeploy and the platform rebuilds that commit instead. A git_push site is rolled back by pushing again, and zip and sftp deployments carry no commit; both return 422.
    • GET /api/v1/static/{uuid}/files to browse deployed content one directory level per call (prefix, limit, cursor). Read-only. Returns 422 while the site’s storage is still being provisioned.
    • GET|POST /api/v1/static/{uuid}/domains, PUT|DELETE .../domains/{domain_uuid} and POST .../domains/{domain_uuid}/verify for custom domains, in the same shape as an app’s.
  • Static site content sources: deployment_config.source_type is required at creation and is one of github, git_push, zip or sftp. It decides what gets provisioned and cannot be changed afterwards — switching means creating another site. github and git_push are built for you with the generator auto-detected; build_command, output_dir and project_path override detection, and spa_mode rewrites unknown paths to /index.html. zip and sftp take already-built output.
  • Connection details come back ready to use: deployment_config.git_ssh_url is the remote a git_push site is pushed to, and appears once its repository has been provisioned — poll for it before pushing. deployment_config.sftp_endpoint is the complete user@host target for an sftp site. Read both from the site response rather than assembling them from the name and region.
  • Static site names are exact: unlike applications, no random suffix is appended. The name you send is the name the site is served under, and a name already in use is rejected with 422 rather than silently renamed.
  • Static sites are created in eu-east-1 only: region on POST /api/v1/static accepts that one value, and anything else — including us-east-1, which remains valid for every other resource — is rejected with 400. Omit the field and you get eu-east-1. The region is only where the content is stored; serving is region-less, so it does not affect how the site is reached.
  • Copy security settings when cloning: POST /api/v1/apps/{uuid}/clone accepts clone_security (boolean, default false), which copies allowed connections and Basic Auth from the source application. The flag was previously undocumented.
  • Outbound webhooks: register an endpoint and the platform POSTs deployment events to it, so you no longer have to poll GET /api/v1/apps/{uuid}/deployments to find out when a deploy finished. Every endpoint below requires the workspace:integrations permission.
    • GET /api/v1/webhooks and POST /api/v1/webhooks to list and create.
    • GET, PUT and DELETE /api/v1/webhooks/{uuid} to read, update and remove one. PUT changes only the fields you send.
    • POST /api/v1/webhooks/{uuid}/test to send a test event and get the result back immediately.
    • GET /api/v1/webhooks/{uuid}/deliveries for delivery history.
    • POST /api/v1/webhooks/{uuid}/deliveries/{delivery_uuid}/retry to replay a delivery.
  • Two event types to subscribe to: deploy_started when a deployment enters running, and deploy_ended when it reaches completed, failed or cancelled. There is no separate build event — on Miget the build and the deployment are one lifecycle, and build_id is the deployment UUID.
  • Static site deployments emit these events too, with data.app_id holding the UUID that GET /api/v1/static returns — the same UUID that scopes a webhook to a single site. A zip or sftp deployment has no commit behind it, so commit_sha, commit_message and branch arrive as null.
  • Deliveries follow the Standard Webhooks specification, so any Standard Webhooks library verifies them as-is. Every request carries webhook-id, webhook-timestamp and webhook-signature, where the signature is an HMAC-SHA256 of {webhook-id}.{webhook-timestamp}.{body}, base64-encoded and prefixed v1,. Reject deliveries whose timestamp is more than a few minutes old — that is what makes a captured request non-replayable.
  • The signing secret is returned exactly once. POST /api/v1/webhooks is the only response containing secret; every other endpoint omits it, and there is no rotation endpoint. Store it when you create the webhook — to replace it, delete the webhook and create a new one.
  • Scope a webhook to specific apps with app_uuids. Leave it empty and the webhook covers every app in the workspace, including apps created later. The apps must belong to the same workspace or the request is rejected with 422.
  • Delivery history tells you whether your endpoint is actually working. Each entry carries status (pending, delivered or failed), response_code, a message holding the response body or the transport error, an attempts count, and payload — the exact JSON body that was sent, so you can compare it against what you received. There is one entry per event, updated in place across retries, and the last 50 events per webhook are kept.
  • Test an endpoint before it matters. POST /api/v1/webhooks/{uuid}/test POSTs a signed event immediately and returns the resulting delivery, so you can verify a URL and your signature verification without waiting for a real deployment. It works on a disabled webhook, and responds 200 whether or not your endpoint accepted it — read status on the returned delivery.
  • Your consumer needs to tolerate a ping type. The test event carries "type": "ping" with an empty data object, and ping is not one of the subscribable event types. A consumer that rejects unknown types will fail the test even though the endpoint is otherwise fine. Accept ping explicitly, or ignore types you do not recognise.
  • Failed deliveries are retried for about 24 hours, across 9 attempts spaced 30s, 2m, 8m, 30m, 1h, 3h, 8h and 12h apart with jitter. Make your endpoint idempotent and deduplicate on the event id, which is stable across automatic retries and across manual replays. A failing endpoint is never disabled automatically.
  • The endpoint must be publicly reachable. A url pointing into private address space — loopback, RFC1918, link-local including 169.254.169.254, CGNAT, or the localhost, .local and .internal hostnames — is rejected with 422. The host is re-resolved before every delivery, so a name that later starts resolving to a private address stops being delivered to rather than being retried.

Fixed

  • Deploying an unresolvable commit: POST /api/v1/apps/{uuid}/deploy with a commit_sha that is not in the GitHub repository the application is configured with now returns 422 and names the commit, instead of reporting 201 Application is being deployed and failing in the build several seconds later. Push the commit before deploying it, and pass a SHA from that same repository — one that is unpushed, on a fork, or removed by a force-push will not resolve. This check applies to applications deployed from GitHub; deploying the latest commit on a branch already behaved this way.
  • Unknown parent_app_id: POST /api/v1/apps with deployment_method: "parent_image" and a parent_app_id that does not exist now returns 422 saying so. It previously returned 500.

Added

  • Git build settings: POST /api/v1/apps and PUT /api/v1/apps/{uuid}/deployment now accept project_path, run_command, language, build_command, pre_deploy_command, post_deploy_command, and use_dhi in the public_git and github deployment configuration. The same fields are returned in deployment_config on the app response.
  • Release-phase commands: pre_deploy_command runs once before a new release starts and post_deploy_command once after a successful deploy. Use pre_deploy_command for database migrations (npx prisma migrate deploy, alembic upgrade head, bin/rails db:migrate) so they run once per release rather than on every replica boot.
  • Custom builder configuration: builder: "custom" requires language and build_command, which the API previously rejected. You can now select and configure the custom builder in the same request.
  • Monorepo builds: set project_path to the subdirectory holding the app and the build treats it as the root.
  • App URL on the app response: POST /api/v1/apps and GET /api/v1/apps/{uuid} return public_url, the address the app is served on. The name you send gets a random suffix appended, so the URL is never the one you would have built from that name — read it back from the response. Custom domains are not included; they are listed under GET /api/v1/apps/{uuid}/domains.
  • Plan type on the plan object: GET /api/v1/plans returns plan_type (dev or pro) on each plan, so the two tiers can be told apart from the plan list itself.

Changed

  • Deployment updates are a patch, not a replacement: PUT /api/v1/apps/{uuid}/deployment merges deployment_config_attributes over the stored configuration. Fields you omit keep their value, and a field sent as an empty string is cleared. Previously every omitted field was wiped, so a partial update could silently clear settings such as the repository URL. The method’s identifying fields are the exception and must still be sent on every request: repository_url for public_git, repository and credential_id for github, image_url and tag for container_registry. Sending a different deployment_method still builds the configuration from scratch, so supply every field that method needs.
  • Add-on creation validates its required fields: POST /api/v1/apps/{uuid}/addons now rejects an incomplete request with 400. label is required. postgres_version, mysql_version, and valkey_version are each required for their matching type. mount_point and storage_access are required for a standalone storage add-on — pass service_id instead and both are inherited from that service.
  • Service creation validates its required fields: POST /api/v1/services now returns 400 when postgres_version is missing for service_type: "postgres", or mount_point is missing for service_type: "shared_storage".
  • Database versions are checked against a list: an unsupported version is rejected with 400 instead of being accepted. Accepted values are postgres_version: 18, 17, 16, 15, 14, 13; mysql_version: 8.2, 8.0; valkey_version: 7, 7.2. 8.4 was previously listed as a MySQL version but was never supported.
  • plan_type is no longer required when creating a resource: POST /api/v1/resources resolves the plan from plan_code_name alone. plan_type is still accepted for backward compatibility but is ignored. Pass a code_name from GET /api/v1/plans verbatim — the values are opaque identifiers such as miget_hobby_0, and they differ between environments.

Fixed

  • App detail for public_git apps: GET /api/v1/apps/{uuid} returns the repository under repository_url, matching the field you use to create and update it. This request previously failed for apps using the public_git deployment method.
  • Deployment update response: PUT /api/v1/apps/{uuid}/deployment returns the deployment configuration it just saved. It previously returned a null deployment_config, so you had to re-fetch the app to see your own change.
  • App create response: POST /api/v1/apps returns the deployment_config it just saved instead of null, so you can read back your own configuration without a follow-up request.
  • Storage add-on on a service without storage: POST /api/v1/apps/{uuid}/addons with type: "storage" and a service_id pointing at a service that has no storage now returns 422 explaining the problem. It previously returned 500.
  • Rate-limited Git host: when a public_git repository cannot be read because the Git host is rate-limiting the request, the error now says so and asks you to retry. It previously reported that the repository or branch does not exist, which sent you looking for a problem with a URL that was correct.
  • Branch with no commits: pointing a public_git deployment at an empty branch now fails with a message naming the branch, instead of an unhelpful server error.

Added

  • App internal URL & auth state: GET /api/v1/apps/{uuid} now returns internal_url (the <service>.<resource>.<region>.migetapp.internal:5000 address for app-to-app and addon connections, null until a compute resource is assigned) and basic_auth_enabled (whether HTTP Basic Auth is enforced at the ingress; credentials are never returned).
  • Deployment commit metadata: deployment records from GET /api/v1/apps/{uuid}/deployments now include commit_sha, commit_message, and branch for git-based deployment methods (null otherwise), so you can confirm which commit is live.
  • Cron run logs: GET /api/v1/apps/{uuid}/cronjobs/{id}/stream_logs streams the cron job’s most recent run logs over SSE (returns 404 until the job has run at least once).

Changed

  • Deploy while busy: POST /api/v1/apps/{uuid}/deploy now returns 409 Conflict (previously a generic 422) when a deployment is already in progress, with a message pointing at GET /api/v1/apps/{uuid}/deployments to poll before retrying.

Changed

  • Scaling profile within_resources: this field on PUT /api/v1/apps/{uuid}/scaling_profile was never active, and its description did not say so. Scaling is always limited to the resource’s allocation; scaling beyond it is not implemented. The field is still accepted and requests need no change — the reference now states that it is ignored.

Changed

  • Public Git deployment field: For public_git apps, the deployment_config field is repository_url (a full HTTPS URL), not repository. The OpenAPI spec now declares repository_url on POST /api/v1/apps and PUT /api/v1/apps/{uuid}/deployment. repository remains the github field (owner/repo format). Clients that were sending repository for public_git should switch to repository_url.
  • Switching to Git deployment: PUT /api/v1/apps/{uuid}/deployment now documents the public_git and github configuration bodies (previously only container_registry, parent_image, and kamal were described).
  • Container image reference: Clarified that image_url must be provided without a scheme and without a tag (e.g. docker.io/library/nginx); put the tag in the separate tag field (defaults to latest).
  • Deployment method naming: Corrected the documented enum for deployment_method to container_registry (the value docker_registry never existed).

Added

Deploy Button Deep Links

“Deploy to Miget” buttons can now open the deploy wizard prefilled from a source. Supported query parameters:
  • Stack: /stacks/new?repo=<url-or-slug>&branch=<branch>&path=<compose-path>
  • App (public Git): /apps/new?repo=<repo-url>&branch=<branch>
  • App (container image): /apps/new?image=<registry/image:tag>

Added

Docker Compose Stacks API

Deploy a multi-service application from a single compose.yaml in a Git repository. Analyze a repo first, then create a stack that provisions every detected app and managed service together:
  • Analyze: POST /api/v1/stacks/analyze (detects services and required env vars; creates nothing)
  • Create: POST /api/v1/stacks
  • List / Get: GET /api/v1/stacks · GET /api/v1/stacks/{uuid}
  • Update: PUT /api/v1/stacks/{uuid} (label, compose_path)
  • Deploy: POST /api/v1/stacks/{uuid}/deploy
  • Deployment config: PUT /api/v1/stacks/{uuid}/deployment (branch, auto-deploy, repository)
  • Deployment history: GET /api/v1/stacks/{uuid}/deployments · GET /api/v1/stacks/{uuid}/deployments/{id}
  • Delete: DELETE /api/v1/stacks/{uuid} (cascades to the stack’s apps and services)

Git Credentials API

Read-only access to the workspace Git credentials (GitHub App installs / tokens) used to clone private repositories. The returned uuid is what you pass as credential_id when creating a stack or a github/public_git app. Tokens are never returned.
  • List: GET /api/v1/git_credentials
  • Get: GET /api/v1/git_credentials/{uuid}

Changed

  • App response now includes region: GET /api/v1/apps/{uuid} now returns a nested region object (id, name, code) alongside the app’s other fields.

Added

App Activity API

A new endpoint for retrieving the activity feed of an application:
  • Get App Activity: GET /api/v1/apps/{uuid}/activity (supports page and limit query parameters for pagination)

Container Registry Credentials API

A workspace-scoped CRUD surface for managing registry credentials used by Dockerfile and container deployments:
  • Create: POST /workspaces/{workspace_id}/container_registry_credentials
  • Update: PUT /workspaces/{workspace_id}/container_registry_credentials/{uuid}
  • Delete: DELETE /workspaces/{workspace_id}/container_registry_credentials/{uuid}

Changed

  • App Creation: POST /api/v1/apps now requires the resource_id parameter to identify the target Resource plan. The previous region_id parameter has been removed because it was misleading on a fixed-capacity model.
  • App Domains Path: The path parameter on domain-scoped endpoints has been renamed from uuid to domain_uuid to disambiguate it from the parent app’s uuid. This affects:
    • GET /api/v1/apps/{uuid}/domains/{domain_uuid}
    • PUT /api/v1/apps/{uuid}/domains/{domain_uuid}
    • DELETE /api/v1/apps/{uuid}/domains/{domain_uuid}
  • Replica Endpoints: POST /api/v1/apps/{id}/addons/create_replica and POST /api/v1/services/{id}/create_replica now return the full Addon and Service entities (previously returned a generic Message). The response now includes the new replica’s uuid and name.
  • Cron Job Delete: DELETE on cron job endpoints now returns a Message body alongside the 204 status for consistency with other destroy endpoints.
  • Cron Job Entity: Renamed state to status (running, stopped) and added two new required fields: last_job_name (name of the most recent run) and last_job_status (e.g. running, complete, failed). Clients reading the state field should switch to status.
  • Region Entity: Now exposes a numeric id field in addition to name and code. The id is required.
  • Container Image Overrides: App and addon deployment configs now accept command and args arrays to override the image’s ENTRYPOINT and CMD. Useful for images like Keycloak that print help on a bare run.

Added

Buckets API (S3-Compatible Object Storage)

A complete set of endpoints for managing S3-compatible storage buckets:
  • List Buckets: GET /api/v1/buckets
  • Create Bucket: POST /api/v1/buckets
  • Get Bucket: GET /api/v1/buckets`/{uuid}`
  • Update Bucket: PUT /api/v1/buckets`/{uuid}`
  • Delete Bucket: DELETE /api/v1/buckets`/{uuid}`
  • Regenerate Credentials: POST /api/v1/buckets`/{uuid}`/regenerate_credentials
  • Update Policy: PUT /api/v1/buckets`/{uuid}`/update_policy
  • Update ACL: PUT /api/v1/buckets`/{uuid}`/update_acl

Bucket Objects API

File and folder management within buckets:
  • List Objects: GET /api/v1/buckets`/{uuid}`/objects/list
  • Get Upload URL: POST /api/v1/buckets`/{uuid}`/objects/upload_url
  • Get Download URL: POST /api/v1/buckets`/{uuid}`/objects/download_url
  • Create Folder: POST /api/v1/buckets`/{uuid}`/objects/create_folder
  • Rename Object: PUT /api/v1/buckets`/{uuid}`/objects/rename
  • Delete Object: DELETE /api/v1/buckets`/{uuid}`/objects/destroy

Database Replication API

New endpoints for managing PostgreSQL streaming replication on both app addons and standalone services:
  • Create Replica: POST /api/v1/apps`/{id}`/addons/create_replica and POST /api/v1/services`/{id}`/create_replica
  • Promote Replica: POST /api/v1/apps`/{id}`/addons/promote_replica and POST /api/v1/services`/{id}`/promote_replica
  • Promote External: POST /api/v1/apps`/{id}`/addons/promote_external and POST /api/v1/services`/{id}`/promote_external
  • Enable Streaming Replication: POST /api/v1/services`/{id}`/enable_streaming_replication
  • Disable Streaming Replication: POST /api/v1/services`/{id}`/disable_streaming_replication

Changed

  • Deploy Endpoint: The POST /api/v1/apps`/{uuid}`/deploy endpoint now supports deploying from GitHub repositories with github_url and github_branch parameters, in addition to the existing commit_sha support.

Added

App Deployments API

A new set of endpoints for managing the application deployment lifecycle has been introduced:
  • List Deployments: GET /api/v1/apps`/{uuid}`/deployments
  • Get Deployment Details: “GET /api/v1/apps/{uuid}/deployments/{id}
  • Get Build Logs: GET /api/v1/apps`/{uuid}`/deployments`/{id}`/logs
  • Stream Build Logs: GET /api/v1/apps`/{uuid}`/deployments`/{id}`/stream_logs
  • Cancel Deployment: POST /api/v1/apps`/{uuid}`/deployments`/{id}`/cancel
  • Rollback Deployment: POST /api/v1/apps`/{uuid}`/deployments`/{id}`/rollback

App Ports API

Full management of application network ports is now available:
  • List Ports: GET /api/v1/workspaces`/{ws}`/apps`/{app}`/ports
  • Create Port: POST /api/v1/workspaces`/{ws}`/apps`/{app}`/ports
  • Delete Port: “DELETE /api/v1/workspaces/{ws}/apps/{app}/ports/{id}
  • Expose Port Publicly: PATCH /api/v1/workspaces`/{ws}`/apps`/{app}`/ports`/{id}`/expose_publicly
  • Make Port Private: PATCH /api/v1/workspaces`/{ws}`/apps`/{app}`/ports`/{id}`/make_private

Add-on Password Rotation

The new POST /api/v1/apps`/{uuid}`/addons`/{id}`/rotate_password endpoint allows for secure password rotation for supported add-ons, such as databases.

Basic Authentication

The PUT /api/v1/apps`/{uuid}`/security endpoint now supports basic_auth_enabled, basic_auth_username, and basic_auth_password parameters, allowing you to enable and configure Basic Authentication for your application.

Changed

  • Deployment Options: The POST /api/v1/apps`/{uuid}`/deploy endpoint now accepts commit_sha and branch parameters, enabling more precise deployments from Git repositories.
  • Builder Options: The builder parameter in POST /api/v1/apps has been expanded to support auto (automatic buildpack detection) and custom (language-specific builder) in addition to dockerfile.
  • Resource Validation: The “PUT /api/v1/resources/{uuid} endpoint now validates against plan downgrades to prevent assigning a plan with insufficient resources (CPU, RAM, storage).
  • Add-on Deletion Logic: The logic for deleting an add-on (“DELETE /api/v1/apps/{uuid}/addons/{id}) has been refactored into a dedicated service object for improved maintainability.

Removed

  • The POST /api/v1/apps`/{uuid}`/sync_parent_image endpoint has been removed.