Dawnrose

Docs Getting started

API and SDK

Everything you do in the dashboard you can do from code: create a project, send it code, wait until it is live and read its URL. A service that gives each of its own customers a server, a CI job that deploys previews, or a script that spins up a database for a test run all work this way.

Call the API from outside Dawnrose: your own servers, your laptop or CI. An app deployed on Dawnrose can’t reach the API or the dashboard from inside its container. To start a deploy from CI, you can also use the service’s deploy hook.

The REST API at https://app.dawnrose.ai/v1 works from any language. The SDKs for TypeScript, Python, Go, Ruby and PHP wrap it, one method per endpoint, and add deploy(), which does the whole create, send, wait cycle in one call. Every endpoint is listed in the API reference, and the machine-readable spec is at /v1/openapi.json.

Tokens

Every request carries a token in the Authorization header: Authorization: Bearer drt_…. Call the API from your server, never from a browser, since anyone who sees the token can use it.

TokenWhere to create itReaches
Project tokenA project’s Settings → Tokens, or tokens.createOne project: its environments, services, deployments, variables and domains. Nothing else in the workspace.
Personal tokenAccount → TokensEverything you can reach yourself, including creating projects.

A token acts with the permissions of the person who created it. Use a project token wherever you can: if it leaks, it exposes one project. A project token cannot create projects or tokens, and cannot read workspace settings, members or billing. Revoke a project token under the project’s Settings → Tokens; anything using it gets 401 from then on.

Install an SDK

npm install https://dawnrose.ai/sdk/dawnrose-sdk-0.1.0.tgz
pip install https://dawnrose.ai/sdk/dawnrose-0.1.0-py3-none-any.whl
go get dawnrose.ai/go
gem install dawnrose --clear-sources --source https://dawnrose.ai/gems

# or in a Gemfile:
# source "https://dawnrose.ai/gems" do
#   gem "dawnrose"
# end
# in composer.json:
# "repositories": [
#   { "type": "composer", "url": "https://dawnrose.ai/composer" }
# ]
composer require dawnrose/sdk

None of them has dependencies. They need Node.js 18 (or Bun or Deno), Python 3.8, Go 1.21, Ruby 3.0 or PHP 8.1. Each reads the token from DAWNROSE_TOKEN when you don’t pass one, and the address from DAWNROSE_URL for a self-hosted Dawnrose.

Every file under https://dawnrose.ai/sdk/, such as the TypeScript tarball and the Python wheel, is listed with its SHA-256 in a checksum file. To check a file before you install it, download it, then run this in the same folder. Each file you downloaded should print OK.

curl -fLO https://dawnrose.ai/sdk/SHA256SUMS
shasum -a 256 -c --ignore-missing SHA256SUMS

On Linux, sha256sum -c --ignore-missing SHA256SUMS does the same as the second line. Then install from the file, such as npm install ./dawnrose-sdk-0.1.0.tgz. The Go module and the Ruby gem come from other addresses and aren’t in the list.

Deploy a server in one call

import { Dawnrose } from "@dawnrose/sdk";

const dawnrose = new Dawnrose({ token: process.env.DAWNROSE_TOKEN });

const { url, service } = await dawnrose.deploy({
  project: "customer-42",
  service: "api",
  files: {
    "package.json": JSON.stringify({ scripts: { start: "node server.js" } }),
    "server.js": `require("http")
      .createServer((q, r) => r.end("hi"))
      .listen(process.env.PORT)`,
  },
  variables: { CUSTOMER_ID: "42" },
});

console.log(url); // https://api-….up.dawnrose.ai
from dawnrose import Dawnrose

dawnrose = Dawnrose()  # reads DAWNROSE_TOKEN

result = dawnrose.deploy(
    project="customer-42",
    service="api",
    files={"main.py": open("main.py").read()},  # a server that listens on $PORT
    deploy_config={"startCommand": "python main.py"},
    variables={"CUSTOMER_ID": "42"},
)

print(result.url)  # https://api-….up.dawnrose.ai
client, err := dawnrose.New("") // reads DAWNROSE_TOKEN
if err != nil {
	log.Fatal(err)
}
res, err := client.Deploy(ctx, dawnrose.DeployOptions{
	Project:   "customer-42",
	Service:   "api",
	Files:     map[string]string{"main.go": code, "go.mod": "module api\n\ngo 1.22\n"},
	Variables: map[string]string{"CUSTOMER_ID": "42"},
})
if err != nil {
	log.Fatal(err)
}
fmt.Println(res.URL) // https://api-….up.dawnrose.ai
require "dawnrose"

dawnrose = Dawnrose::Client.new # reads DAWNROSE_TOKEN

result = dawnrose.deploy(
  project: "customer-42",
  service: "api",
  files: { "app.rb" => File.read("app.rb") }, # a server that listens on $PORT
  deploy_config: { "startCommand" => "ruby app.rb" },
  variables: { "CUSTOMER_ID" => "42" }
)

puts result.url # https://api-….up.dawnrose.ai
<?php
use Dawnrose\Client;

$dawnrose = new Client(); // reads DAWNROSE_TOKEN

$result = $dawnrose->deploy(
    project: 'customer-42',
    service: 'api',
    files: ['index.php' => file_get_contents('index.php')],
    variables: ['CUSTOMER_ID' => '42'],
);

echo $result->url; // https://api-….up.dawnrose.ai

deploy() finds the project by name or id and creates it if it doesn’t exist, then does the same for the environment (production by default) and the service. It sends the code, sets the variables, gives the service a generated domain, and resolves once the new version is live. Calling it again with the same names deploys a new version of the same service. If the build fails or the app crashes, it throws a DeploymentFailedError (in Go, returns a *dawnrose.DeploymentFailedError) whose deployment says what happened.

The options are the same in every language, written the way that language writes names: deployConfig in TypeScript and PHP, deploy_config in Python and Ruby, Deploy in Go.

OptionWhat it does
projectProject id or name. A new name creates the project; with a project token, use the token’s project.
environmentEnvironment id or name. Default production; a new name creates it.
serviceService name. Default app.
files, image or repoThe code: files as path → content (up to 20 MB), a Docker image such as nginx:1.27, or a GitHub repository owner/name with branch and rootDir.
variablesVariables to set. Values can reference other services, such as ${{ Postgres.DATABASE_URL }}.
build, deployConfigBuild and run settings: builder, start command, port, replicas, healthcheck, CPU and memory limits, sleep when idle. See Deploying.
domainGive the service a public URL. Default true.
wait, timeoutSecWait until it is live, default true, for up to 900 seconds.

Files are built like any other source: Dawnrose detects the language, installs dependencies and starts the app on $PORT, or uses your Dockerfile when there is one. Binary files can be passed as Uint8Array. For a bigger source, send a .tar.gz with services.upload(id, archive) (up to 500 MB).

More than one service

Every SDK has a method for every endpoint, grouped like the reference: projects, environments, services, deployments, variables, domains, functions, templates and tokens. The names follow the reference in each language’s style: services.deployFiles is services.deploy_files in Python and Ruby, Services.DeployFiles in Go and services->deployFiles in PHP. Responses are plain dictionaries, hashes and arrays, except in Go and TypeScript, which have types. A customer project with its own database and a one-file function, in TypeScript:

const project = await dawnrose.projects.create({ name: "customer-42" });
const env = project.environments[0];

// a Postgres with its volume and DATABASE_URL
await dawnrose.templates.deploy(env.id, "postgres");

// the app, from a repository, wired to the database over the private network
const api = await dawnrose.services.create(env.id, {
  name: "api",
  source: { repo: "acme/api", branch: "main" },
  variables: { DATABASE_URL: "${{ Postgres.DATABASE_URL }}" },
  generateDomain: true,
});
await dawnrose.deployments.wait(api.deployment.id, { throwOnFailure: true });

// a TypeScript function that answers HTTP on its own URL
const fn = await dawnrose.functions.create(env.id, {
  name: "webhook",
  runtime: "typescript",
  code,
});

// a token for this project only, for code that runs outside Dawnrose
const { token } = await dawnrose.tokens.create(project.id, {
  name: "customer-42 CI",
});

services.request(id, { path: "/health" }) sends an HTTP request to a service over the private network, without a public domain, and services.exec(id, "npm run migrate") runs a command in it. To call an endpoint the methods don’t wrap, use dawnrose.request(method, path, { query, body }).

The REST API

The same steps with curl. Every response is JSON.

TOKEN=drt_…
API=https://app.dawnrose.ai/v1

# a project comes with a production environment
curl -s -X POST $API/projects \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "customer-42"}'

# a service with a public URL
curl -s -X POST $API/environments/ENV_ID/services \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "api", "generateDomain": true}'

# send the code: this starts a build and returns the deployment
curl -s -X POST $API/services/SERVICE_ID/files \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "files": [
      { "path": "server.js", "content": "…" },
      { "path": "package.json", "content": "…" }
    ]
  }'

# wait until it is live; the service's url field is its address
curl -s "$API/deployments/DEPLOYMENT_ID/wait?timeout=600" \
  -H "Authorization: Bearer $TOKEN"
curl -s $API/services/SERVICE_ID -H "Authorization: Bearer $TOKEN"

Generate a client in another language from /v1/openapi.json; it is OpenAPI 3.0.

Errors

A request that fails returns a 4xx or 5xx status and a body like this:

{
  "error": {
    "message": "A service named \"api\" already exists in production",
    "hint": "Pick another name.",
    "code": "error"
  }
}

The hint says what to do about it. In the SDK, failures throw DawnroseError with status, code and hint. Read requests are retried on network errors and on 429, 502, 503 and 504; writes are not, so nothing happens twice.

StatusMeans
401No token, or it was revoked.
402Over a plan limit, such as projects or services per project. The message names the limit; see Scaling and plan limits.
403The token’s owner lacks the role, or a project token reached outside its project.
404Not found, or not visible to this token.
409A conflict, such as a duplicate name or a service with no running deployment.

Versioning

Everything under /v1 stays compatible: new endpoints and new fields can appear, but nothing is removed or renamed. Responses can carry fields the reference doesn’t list yet; ignore the ones you don’t use. The dashboard’s own API under /api changes with the dashboard, so build on /v1.