---
title: "Import a GitHub repository and deploy it"
method: POST
path: "/api/computers/{id}/deploy-projects"
tags: ["deploys"]
---

# Import a GitHub repository and deploy it

`POST /api/computers/{id}/deploy-projects`

## Request

- Base URL: `https://www.orgo.ai`
- URL: `https://www.orgo.ai/api/computers/{id}/deploy-projects`
- Auth: HTTP bearer

Body: { installation_id, repo_id } (a repository a GitHub account linked to the workspace reads) or { repo_url } (a public repository, https://github.com/owner/name; deploys by hand only), plus name?, branch? (default: the repository’s default), root_dir?, framework? (auto, node, python, dockerfile, compose, static), port? (3000), health_path? (/), install_cmd?, build_cmd?, start_cmd?, auto_deploy?, env? and deploy? (false: import only). Detection when framework is auto: orgo.json, a compose file, a Dockerfile, package.json (npm, pnpm, yarn or bun by lockfile; Node.js from .nvmrc or engines, 22 by default), a Python project (uv; a Procfile web: line or a start command), index.html. The app must listen on 0.0.0.0:<port>; it gets PORT. env values are stored encrypted, never returned, and masked in build logs. The first deployment is queued and built once the computer runs; nothing wakes it. At most 10 deploys a computer, and each is one of its 20 apps. Or an Ingot repo: { source: "ingot", repo: "<workspace>/<name>", at? } (a repo of the central Ingot server: the id of a workspace the caller owns or is a member of, then the repo’s name; another, or another workspace than a workspace key’s own, is 403 INGOT_WORKSPACE_MISMATCH; at: the changeset a deploy builds when it names none, default the newest on main); no GitHub repository, deploys main by hand only, its files exported on the computer with ingot export (which Bops installs with its config) instead of a clone.

## Path parameters

- `id` string, required

## Request body

- CreateDeployProject
  - `source` 'github_app' | 'public' | 'ingot': Required for an Ingot repo; read from the other fields when left out.
  - `repo` string: source ingot: the Ingot repo, <workspace>/<name>: the workspace’s id (or local), then a name of at most 64 characters.
  - `at` string: source ingot: the changeset a deploy builds when it names none (default: the newest on main).
  - `installation_id` integer
  - `repo_id` integer
  - `repo_url` string
  - `name` string
  - `branch` string
  - `root_dir` string
  - `framework` 'auto' | 'node' | 'python' | 'dockerfile' | 'compose' | 'static'
  - `port` integer
  - `health_path` string
  - `install_cmd` string, nullable
  - `build_cmd` string, nullable
  - `start_cmd` string, nullable
  - `auto_deploy` boolean
  - `env` object
  - `deploy` boolean

## Response `201`

Imported; its first deployment is in deployments.

- DeployProject
  - `id` string, uuid, required
  - `name` string, required: Also the name of the computer app that runs it.
  - `source` 'github_app' | 'public' | 'ingot', required
  - `repo` string, nullable, required: Null for an Ingot repo.
  - `repo_url` string, nullable
  - `installation_id` string, nullable
  - `branch` string, required: main for an Ingot repo.
  - `ingot_repo` string, nullable: An Ingot repo: <workspace>/<name> on the central Ingot server.
  - `ingot_at` string, nullable: An Ingot repo’s: the changeset a deploy builds when it names none; null is the newest on main.
  - `root_dir` string
  - `framework` 'auto' | 'node' | 'python' | 'dockerfile' | 'compose' | 'static'
  - `detected_framework` string, nullable
  - `install_cmd` string, nullable
  - `build_cmd` string, nullable
  - `start_cmd` string, nullable
  - `port` integer, required
  - `health_path` string
  - `auto_deploy` boolean
  - `disabled` string, nullable: Why pushes no longer deploy it.
  - `url` string, nullable: Its public URL, made with its first live deploy where public URLs are on.
  - `app_id` string, nullable: Its app (GET /api/computers/{id}/services).
  - `env_keys` string[]
  - `current` Deployment
    - `id` string, uuid, required
    - `project_id` string, uuid, required
    - `trigger` 'push' | 'manual' | 'rollback' | 'redeploy', required
    - `state` 'queued' | 'running' | 'succeeded' | 'failed' | 'cancelled' | 'superseded', required
    - `phase` 'preparing' | 'cloning' | 'detecting' | 'installing' | 'building' | 'starting' | 'health_check' | 'live' | 'null', nullable
    - `branch` string, required
    - `source_version` string, nullable: An Ingot repo’s: the changeset it builds, once known (the one asked for, else the one the runner found newest on main). A rollback: the changeset of the release it went back to.
    - `commit_sha` string, nullable
    - `commit_message` string, nullable
    - `commit_author` string, nullable
    - `framework` string, nullable
    - `error_code` string, nullable: Failed: build_timeout, runner_lost, computer_not_running, github_token, clone_failed, detect_failed, install_failed, build_failed, docker_unavailable, start_failed, health_check_failed, release_gone, apply_failed, ingot_missing.
    - `error` string, nullable
    - `rollback_of` string, uuid, nullable
    - `current` boolean: This deployment is the project’s live one. After a rollback it is the rollback, not the deployment it went back to.
    - `release_live` boolean: Its release is the one live now, through this deployment or another of the same release (a rollback to it, or the one this rollback went back to). Nothing to roll back to or build again.
    - `can_rollback` boolean: Its release is still on the computer and is not the one live now.
    - `created_at` string, date-time
    - `started_at` string, date-time, nullable
    - `finished_at` string, date-time, nullable
  - `deployments` Deployment[]
    - `id` string, uuid, required
    - `project_id` string, uuid, required
    - `trigger` 'push' | 'manual' | 'rollback' | 'redeploy', required
    - `state` 'queued' | 'running' | 'succeeded' | 'failed' | 'cancelled' | 'superseded', required
    - `phase` 'preparing' | 'cloning' | 'detecting' | 'installing' | 'building' | 'starting' | 'health_check' | 'live' | 'null', nullable
    - `branch` string, required
    - `source_version` string, nullable: An Ingot repo’s: the changeset it builds, once known (the one asked for, else the one the runner found newest on main). A rollback: the changeset of the release it went back to.
    - `commit_sha` string, nullable
    - `commit_message` string, nullable
    - `commit_author` string, nullable
    - `framework` string, nullable
    - `error_code` string, nullable: Failed: build_timeout, runner_lost, computer_not_running, github_token, clone_failed, detect_failed, install_failed, build_failed, docker_unavailable, start_failed, health_check_failed, release_gone, apply_failed, ingot_missing.
    - `error` string, nullable
    - `rollback_of` string, uuid, nullable
    - `current` boolean: This deployment is the project’s live one. After a rollback it is the rollback, not the deployment it went back to.
    - `release_live` boolean: Its release is the one live now, through this deployment or another of the same release (a rollback to it, or the one this rollback went back to). Nothing to roll back to or build again.
    - `can_rollback` boolean: Its release is still on the computer and is not the one live now.
    - `created_at` string, date-time
    - `started_at` string, date-time, nullable
    - `finished_at` string, date-time, nullable
  - `created_at` string, date-time
  - `updated_at` string, date-time

## Other responses

- `400`: A field is not valid (INVALID_*), or the repository is not public (REPO_NOT_PUBLIC).
- `404`: The installation is not linked to the workspace (INSTALLATION_NOT_FOUND), or the App cannot see the repository (REPO_NOT_FOUND).
- `409`: Name or port taken (NAME_TAKEN, PORT_TAKEN), or the computer has the most deploys or apps it can have (PROJECT_LIMIT, SERVICE_LIMIT).
- `429`: Over one of the account's rate limits (rate_limited): `limit` names it, `retry_after` and Retry-After say how many seconds to wait. Limits are per account (the workspace owner when the request acts on a computer or a workspace) and set by its plan (x-rate-limits).

## Changes

- **2026-11-01** `60e768a2bb9d`: 3 info
  - added the optional property `docs_url` to the response with the `400` status
  - added the optional property `next` to the response with the `400` status
  - added the optional property `retryable` to the response with the `400` status
- **2026-10-30** `afb6f0ee1aac`: 1 info
  - added the non-success response with the status `429`
- **2026-10-26** `68dbea0d710c`: 2 breaking, 1 warning, 7 info
  - the response property `repo` became nullable for the status `201`
  - the response property `repo_url` became nullable for the status `201`
  - added the new `ingot` enum value to the `source` response property for the response status `201`
  - added the new optional request property `at`
  - …6 more
- **2026-10-18** `dcaee154d80e`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/orgo/apis/orgo-api/changes/api/computers/:id/deploy-projects/post.md)

---

[API](https://skmtc.dev/orgo/apis/orgo-api.md) - [All operations](https://skmtc.dev/orgo/apis/orgo-api/llms.txt) - [OpenAPI document](https://skmtc.dev/orgo/apis/orgo-api/revisions/e4c1e7379fa5?raw)
