Create project
const url = 'https://api.deploybase.eu/api/v1/api/v1/projects';const options = { method: 'POST', headers: {Authorization: '<Authorization>', 'Content-Type': 'application/json'}, body: '{"auto_deploy_enabled":true,"branch":"example","build_command":"example","default_branch":"example","deploy":true,"forgejo_audience":"example","framework":"nextjs","git_auth_mode":"pat","git_provider":"github","git_repo_url":"example","git_token":"example","git_username":"gitlab+deploy-token-123","mr_previews_enabled":true,"name":"example","output_dir":"example","package_manager":"npm","preview_branch_exclude":["example"],"preview_branch_include":["example"],"preview_branch_mode":"example","root_directory":"example","skip_git_validation":true,"slug":"example","subdomain":"example"}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url https://api.deploybase.eu/api/v1/api/v1/projects \ --header 'Authorization: <Authorization>' \ --header 'Content-Type: application/json' \ --data '{ "auto_deploy_enabled": true, "branch": "example", "build_command": "example", "default_branch": "example", "deploy": true, "forgejo_audience": "example", "framework": "nextjs", "git_auth_mode": "pat", "git_provider": "github", "git_repo_url": "example", "git_token": "example", "git_username": "gitlab+deploy-token-123", "mr_previews_enabled": true, "name": "example", "output_dir": "example", "package_manager": "npm", "preview_branch_exclude": [ "example" ], "preview_branch_include": [ "example" ], "preview_branch_mode": "example", "root_directory": "example", "skip_git_validation": true, "slug": "example", "subdomain": "example" }'Creates a new static site project for the authenticated team. With subdomain, that exact production address is reserved or the request is refused with alternatives. With deploy: true (and a git repository and branch) the first build starts in the same call, and data becomes {project, deployment_id} when it started or {project, deploy_error: {status, code, message}} when the project was created but the build could not start (still a 201; the code is what POST /deployments would answer). With auto_deploy_enabled and a token that can read the repository but may not add the push webhook (the host answered 401, 403 or 404), the project is still created, with auto_deploy_enabled false and data.warnings [{code: webhook_not_added, message}].
Authorizations
Section titled “ Authorizations ”Request Body
Section titled “ Request Body ”Project details
object
AutoDeployEnabled, when true, registers a webhook at create time so the flow stays a single atomic call (no post-create PATCH). Requires a git source whose provider supports auto-deploy and a resolvable token; otherwise creation is rejected.
Branch is the branch the first build deploys, typically detection’s default branch. Only read with deploy: true. When default_branch is not sent, it becomes the project’s default branch too, so the first build goes to production.
Deploy starts the first build in the same call (DEP-671). It needs git_repo_url and a branch (branch, else default_branch). The response is then {project, deployment_id} or, when the project was created but the build could not start, {project, deploy_error}; either way a 201, because the project exists.
ForgejoAudience is the value Forgejo generates for an Authorized Integration. Not a secret, so it is accepted and returned in plain text.
GitAuthMode selects how deploybase authenticates to the git host (DEP-447): “pat” (default) uses a stored token; “forgejo_jwt” mints a short-lived JWT per operation and stores nothing. The latter needs Forgejo 16.0+ and an audience.
GitProvider names the git host. Omitted with a GitLab token (glpat-, gldt-), it is stored as gitlab, which also covers self-hosted GitLab.
GitUsername is sent with git_token instead of the provider default. Only needed for a GitLab deploy token (gldt-), which works only with its own username, e.g. gitlab+deploy-token-123. Letters, digits and . + _ - only; requires git_token.
Example
gitlab+deploy-token-123Whether merge/pull request events build previews (default true). Independent of the branch policy, which never applies to MR events.
Branch patterns evaluated before the include list under mode “custom”; a match blocks the build. “Everything except bots” is include [“”], exclude [“renovate/”].
Branch patterns that opt a branch in under mode “custom”. Exact name, or one “” alone or at either end (e.g. “release/”, “*-beta”). Maximum 32.
Which webhook pushes auto-build a preview: “all” (default), “none” (only the default branch) or “custom”. The default branch always builds, under every mode.
Subdomain is the production address to reserve, {subdomain}.sites.deploybase.eu (DEP-671). Reserved exactly or refused: 422 SUBDOMAIN_INVALID / SUBDOMAIN_RESERVED or 409 SUBDOMAIN_TAKEN, each with details.suggestions. Omit it to have one derived from the name (with a random suffix if that is taken). GET /projects/suggest-subdomain returns the one a create would derive, so a page can show it first.
No binding tag on purpose: a binding failure is a blanket 400 naming no field, and this must answer 422 with suggestions.
Responses
Section titled “ Responses ”Without deploy; with deploy: true, data is a CreateProjectResponse
object
object
object
Site analytics (DEP-17). On by default; switching it off stops collection and deletes the project’s site_analytics_daily rows.
Auto-deploy (GitHub webhook) fields
AutoDeployPauseReason says why; the only value is AutoDeployPauseReasonRepeatedFailures.
AutoDeployPausedAt is when the breaker tripped. NULL means auto-deploy is not paused.
Auto-deploy circuit breaker (DEP-544). All five are SYSTEM state, written only by the dedicated repository methods (RecordBuildFailure, ResetBuildFailureStreak) and never by ProjectRepository.Update, which omits them: a settings save that read the row before the breaker tripped must not be able to write the pause back out.
They are deliberately separate from AutoDeployEnabled, the user’s own setting, which the platform never writes. See AutoDeployPaused in preview_branch_policy.go.
ConsecutiveBuildFailures counts user-caused failures sharing LastBuildFailureSignature.
Provenance (DEP-674, migration 000113): how the request that created this project was authenticated (proven: session or api_key) and which of our clients says it sent it (claimed, from X-Deploybase-Client). Written once by Create and omitted by ProjectRepository.Update. nil = unknown, including every project from before 000113.
object
ForgejoAudience is the aud value Forgejo generates for an Authorized Integration. Forgejo’s docs state it is not confidential, so unlike GitTokenEncrypted it is stored in plain text AND returned by the API – the user has to read it back to check it against their Forgejo settings.
GitAuthMode selects which credential ResolveProjectToken produces (DEP-447). “pat” is the historical behaviour; “forgejo_jwt” mints a short-lived JWT per call against ForgejoAudience and stores nothing. Empty is treated as “pat” so a row loaded before the migration, or a struct built in a test, still behaves.
Optional FK to team_git_connections; nil = manual PAT
GitUsername is the HTTP Basic username sent with the stored PAT instead of the provider default (DEP-810). A GitLab deploy token is only accepted with its own username, e.g. gitlab+deploy-token-123. Not a secret, so it is returned by the API. Set only alongside a stored PAT; cleared with it and by the keyless Forgejo mode.
LastBuildFailureMessage is the classified error line of the current streak, which the pause banner shows. Never raw build output.
MRPreviewsEnabled governs merge/pull-request previews on its own: the branch policy above never applies to an MR event.
PackageManager overrides the Node package manager used at build time. nil = auto-detect (lockfile / packageManager field); set = user override.
PlatformURLMode is read only when PrimaryDomainID is set. See PlatformURLMode.
Preview branch policy (DEP-287). Decides which webhook pushes auto-build a preview; see ShouldAutoBuildBranch in preview_branch_policy.go. Nothing here touches manual/API/MCP/ZIP deploys, and the default branch is never filtered.
PreviewSlug is the MUTABLE, globally unique label that preview hostnames are derived from: {branch}-{preview_slug}.previews.deploybase.eu (DEP-436). It drives hostnames and nothing else — never a storage path, never a manifest. Globally unique because the previews zone is shared by every team, so the hostname namespace is global.
PrimaryDomainID is the project’s canonical hostname, as a foreign key into
domains — deliberately not a copy of the hostname string (DEP-112). The route
reconciler recomputes every pointer from this FK on each sweep, so a wrong
redirect is drift it can see and repair; a denormalised host column would be read
back by that same reconciler and confirmed rather than fixed, and the 301 it
produces is cached at the edge for 30 days.
NULL means no canonical hostname: every hostname serves content directly, which is the behaviour every project had before DEP-112.
ProductionBranchDeletedAt is when a push deleted the production branch (EffectiveDefaultBranch). Cleared by the next push to that branch and by a change of DefaultBranch. nil = not known to be deleted.
ProvisionalUntil is set while a project created by a folder upload has not published yet (DEP-671, migration 000109). The project and its upload session are created together so the chosen address is held by the unique index from the start; past this time the upload sweeper deletes the project if it is still empty. NULL for every other project.
SYSTEM state, like the breaker columns below: written only by Create and the dedicated repository methods, and omitted by ProjectRepository.Update, so a stale struct saved by the publisher can never put a deadline back on an adopted project.
RepoDefaultBranch is the forge’s default branch as the last push reported it. nil when no push has reported one; Bitbucket Cloud push payloads never do.
ScreenshotDeploymentID is the live deployment’s id when a screenshot of it is stored (DEP-270), else absent. Fetch it from GET /projects/{id}/screenshot?d={this id}.
Slug is IMMUTABLE. It is the storage key — the middle segment of every deployed object’s path ({team_id}/{slug}/{deployment_id}/…) and the project_slug baked into each deployment’s write-once route manifest. Renaming it makes the router answer 500 slug_mismatch for every deployment already built. Use PreviewSlug to rename a hostname.
Production URL fields
Warnings is set only by POST /projects without deploy: what the create did not do although the project exists (webhook_not_added, DEP-810). Absent otherwise.
object
Code is stable for clients to switch on, e.g. webhook_not_added.
Message says it in words, for the page.
Example
{ "data": { "created_client": "dashboard", "created_via": "session", "framework": "nextjs", "package_manager": "npm", "platform_url_mode": "redirect", "preview_branch_mode": "all", "status": "active", "warnings": [ { "code": "webhook_not_added" } ] }}Bad Request
object
object
Example generated
{ "code": "example", "details": "example", "error": "example", "meta": { "request_id": "example", "timestamp": "example", "trace_id": "example" }}Unauthorized
object
object
Example generated
{ "code": "example", "details": "example", "error": "example", "meta": { "request_id": "example", "timestamp": "example", "trace_id": "example" }}PLAN_LIMIT_EXCEEDED: site limit reached
object
object
Example generated
{ "code": "example", "details": "example", "error": "example", "meta": { "request_id": "example", "timestamp": "example", "trace_id": "example" }}SUBDOMAIN_TAKEN with details.suggestions, or PROJECT_NAME_TAKEN with details.field name
object
object
Example generated
{ "code": "example", "details": "example", "error": "example", "meta": { "request_id": "example", "timestamp": "example", "trace_id": "example" }}SUBDOMAIN_INVALID or SUBDOMAIN_RESERVED with details.suggestions; INVALID_BUILD_SETTINGS names the refused field in details.field; GIT_VALIDATION_FAILED carries details.git_error_code, and with auth_failed details.git_error_reason (token_not_accepted: the host answered 401; token_cannot_read: 403, the token may not read code; token_needs_fine_grained: 403, the GitLab group accepts only fine-grained tokens); INVALID_INPUT with details.field deploy or branch
object
object
Example generated
{ "code": "example", "details": "example", "error": "example", "meta": { "request_id": "example", "timestamp": "example", "trace_id": "example" }}Live a8b7eb1d built