Skip to main content
A project is the core resource in Shipyard. It represents a connected GitHub repository with a chosen branch, build settings, and optional encrypted secrets. When you create a project, Shipyard immediately registers a webhook on the GitHub repository so that every push to the configured branch triggers a build. All five endpoints on this page require a valid JWT and enforce ownership — you can only read or modify your own projects.

POST /api/project

Creates a new project, registers a GitHub push webhook on the target repository, and queues an initial build from the latest commit on the specified branch. Requires JWT.

Request body

name
string
required
Project name. Must be unique across all projects. This value is used as the subdomain when Shipyard serves the deployed output (e.g., a project named my-app is served at my-app.<BASE_DOMAIN>). Validated to be non-empty and unique.
repoUrl
string
required
The HTTPS URL of the GitHub repository (e.g., https://github.com/owner/repo). Shipyard parses the owner and repo name from this URL to register the webhook via the GitHub API.
branch
string
required
The branch to watch. Shipyard only triggers builds for push events that target this branch.
installCommand
string
Command to install dependencies. Defaults to npm install if omitted. Run inside the Docker container before the build command.
buildCommand
string
Command to produce the build output. If omitted, Shipyard skips the build phase and uses the repository contents directly.
outputDirectory
string
Not accepted on project creation — use PUT /api/project/projects/:projectId to set this after creation. Defaults to ./. Shipyard auto-detects dist for Vite projects and .next for Next.js projects at build time regardless of this setting.
secrets
array
Array of environment variable key-value pairs. Each secret is encrypted with AES-256-GCM before being stored. Secrets are decrypted only at build time and are never returned in API responses.
[
  { "key": "API_KEY", "value": "supersecret" },
  { "key": "DATABASE_URL", "value": "postgresql://..." }
]
secrets[].key
string
required
Environment variable name.
secrets[].value
string
required
Environment variable value. Encrypted before storage.

Curl example

curl -X POST https://your-server/api/project \
  -H "Authorization: Bearer <your-jwt>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "my-vite-app",
    "repoUrl": "https://github.com/acme-corp/my-vite-app",
    "branch": "main",
    "installCommand": "npm install",
    "buildCommand": "npm run build",
    "secrets": [
      { "key": "VITE_API_BASE", "value": "https://api.example.com" }
    ]
  }'

Response

{
  "message": "project created",
  "project": {
    "id": 7,
    "name": "my-vite-app",
    "branch": "main",
    "installCommand": "npm install",
    "buildCommand": "npm run build",
    "outputDirectory": "./",
    "repoUrl": "https://github.com/acme-corp/my-vite-app",
    "webhookId": "489012345",
    "productionUrl": null,
    "userId": 3,
    "createdAt": "2026-04-11T12:00:00.000Z",
    "updatedAt": "2026-04-11T12:00:00.000Z"
  },
  "initialBuildId": 14
}
The initial build starts immediately in the background. Monitor its progress via the buildStatusUpdate Socket.io event using the returned initialBuildId.

Error responses

StatusCondition
401JWT is missing or invalid
422Validation failed — name, branch, or repoUrl is missing or the project name is already taken
500Failed to register the GitHub webhook or insert the project

GET /api/project/projects

Returns all projects belonging to the authenticated user, each with their full build history and deployments. Requires JWT.

Curl example

curl https://your-server/api/project/projects \
  -H "Authorization: Bearer <your-jwt>"

Response

{
  "message": "Projects fetched",
  "projects": [
    {
      "id": 7,
      "name": "my-vite-app",
      "branch": "main",
      "installCommand": "npm install",
      "buildCommand": "npm run build",
      "outputDirectory": "dist",
      "repoUrl": "https://github.com/acme-corp/my-vite-app",
      "webhookId": "489012345",
      "productionUrl": "http://my-vite-app.useshipyard.xyz",
      "userId": 3,
      "createdAt": "2026-04-11T12:00:00.000Z",
      "updatedAt": "2026-04-11T12:00:00.000Z",
      "secrets": [
        { "id": 2, "key": "VITE_API_BASE", "value": "<encrypted>", "projectId": 7 }
      ],
      "builds": [
        {
          "id": 14,
          "status": "passed",
          "commit": "fix: update hero copy",
          "branch": "main",
          "commitAuthor": "Jane Doe",
          "commitHash": "a1b2c3d4e5f6",
          "exitCode": 0,
          "projectId": 7,
          "startedAt": "2026-04-11T12:00:05.000Z",
          "finishedAt": "2026-04-11T12:02:30.000Z",
          "deployment": {
            "id": 9,
            "status": "live",
            "buildId": 14,
            "createdAt": "2026-04-11T12:02:35.000Z"
          },
          "logs": [...]
        }
      ]
    }
  ]
}
Secret values are returned as stored (AES-256-GCM ciphertext). They are never decrypted in API responses — only at build time inside the containerized pipeline.

PUT /api/project/projects/:projectId

Updates settings on an existing project. The server verifies that the project belongs to the authenticated user before applying any changes. Only fields present in the request body are updated; omitted fields retain their existing values. Requires JWT.

Path parameters

projectId
number
required
Numeric ID of the project to update.

Request body

name
string
New project name. Must be unique across all projects if changed.
branch
string
New branch to watch for push events.
buildCommand
string
Updated build command.
installCommand
string
Updated install command.
outputDirectory
string
Updated output directory.
secrets
array
Additional secrets to add to the project. These are appended to any existing secrets — to remove a secret, use DELETE /api/project/secret/:secretId.
secrets[].key
string
required
Environment variable name.
secrets[].value
string
required
Environment variable value. Encrypted before storage.

Curl example

curl -X PUT https://your-server/api/project/projects/7 \
  -H "Authorization: Bearer <your-jwt>" \
  -H "Content-Type: application/json" \
  -d '{
    "buildCommand": "npm run build:prod",
    "outputDirectory": "dist"
  }'

Response

{
  "message": "Project updated",
  "project": {
    "id": 7,
    "name": "my-vite-app",
    "branch": "main",
    "buildCommand": "npm run build:prod",
    "outputDirectory": "dist",
    "updatedAt": "2026-04-11T13:00:00.000Z"
  }
}

Error responses

StatusCondition
400projectId path parameter is missing
401JWT is missing or invalid
403The project does not belong to the authenticated user
404No project found with the given ID

DELETE /api/project/projects/:projectId

Deletes a project and removes the associated GitHub webhook. All related records — builds, build logs, deployments, and secrets — are deleted via database cascade. Requires JWT.
Deleting a project is irreversible. All associated builds, deployment history, and encrypted secrets are permanently removed. The GitHub webhook registered on the repository is also deleted, so future pushes will no longer trigger builds.

Path parameters

projectId
number
required
Numeric ID of the project to delete.

Curl example

curl -X DELETE https://your-server/api/project/projects/7 \
  -H "Authorization: Bearer <your-jwt>"

Response

{
  "message": "Project deleted"
}

Error responses

StatusCondition
400projectId or user ID is missing
401JWT is missing or invalid
403The project does not belong to the authenticated user
404No project found with the given ID

DELETE /api/project/secret/:secretId

Permanently deletes a single encrypted secret by its ID. The server verifies that the secret belongs to a project owned by the authenticated user. Requires JWT.

Path parameters

secretId
number
required
Numeric ID of the secret to delete. Secret IDs are returned in the secrets array of the GET /api/project/projects response.

Curl example

curl -X DELETE https://your-server/api/project/secret/2 \
  -H "Authorization: Bearer <your-jwt>"

Response

{
  "message": "secret deleted"
}

Error responses

StatusCondition
400secretId or user ID is missing
401JWT is missing or invalid
403The secret’s project does not belong to the authenticated user
500Deletion failed unexpectedly

Build docs developers (and LLMs) love