Cloudish · Opus 5.5 · medium effort · HIPAA organization · trial 1

API / Backend Service on Cloudish

Success

Checks passed20/20
Human steps00 min of a person's time
Security & compliance76/100
Build2m 25s
Deploy3m 12s
Tokens1.50M
Agent cost$1.02
Hosting / month$0.11 light use$7.84 busy

Deployed to https://adb-9nqjr-api.on.do.cloudish.ai. Every deployment is deleted after it's verified and reviewed.

Screenshots

The API documentation at /docs
The API documentation at /docs · https://adb-9nqjr-api.on.do.cloudish.ai/docs

Technologies

Runtime
Node.js 24
Frameworks
Node.js http module (built in)
Data
SQLite (on a persistent volume)
Packaging
Docker (node:24-slim)
Hosting
Cloudish container (shared) with a persistent volume

Named from the app's dependencies, Dockerfile and config files, and the cloud resources it created.

Checks

Black-box checks against the live URL. A cell succeeds when every required check passes. The app's spec

ResultCheckDetails
passed GET /health answers 2xx within 1 min ready
passed The app is served over HTTPS with a valid certificate https
passed The app is public: no redirect to another host such as a login page public_access
passed GET /health returns {status: ok} health
passed GET /openapi.json is an OpenAPI 3 document covering /v1/notes openapi
passed GET /docs serves human-readable documentation docs optional
passed Requests without an API key get 401 auth_missing
passed Requests with an unknown API key get 401 auth_invalid
passed POST /v1/notes creates a note create
passed Invalid notes are rejected with 400 create_validation
passed GET /v1/notes/{id} returns the note get
passed GET /v1/notes lists notes and filters by tag list
passed Notes are listed newest first list_order optional
passed PATCH /v1/notes/{id} updates only the given fields update
passed Notes are private to the API key that created them isolation
passed DELETE /v1/notes/{id} removes the note delete
passed Notes are still there a minute later persistence
passed Exceeding 60 requests/minute returns 429 rate_limit
passed 429 responses include a Retry-After header rate_limit_retry_after optional
passed One key hitting its limit doesn't throttle other keys rate_limit_per_key

Human steps

None: the agent got everything it needed on its own.

Security & compliance review

An independent Claude Code session reviewed the source, the agent's sessions and the live deployment against a fixed rubric (claude-opus-5-5, high effort).

The Notes API is well built: it has no dependencies, uses parameterized SQLite queries on a private persistent volume, isolates notes per key, reads API_KEYS from Cloudish's encrypted secret store, and keeps the Cloudish credential out of the build context and logs. Probes found nothing exposed beyond the app, and it is served only over valid HTTPS. The main weaknesses are in hosting and hardening: the app runs on an unclaimed key with about 9 hours of free credit, so it will stop working unless the owner tops it up; the container runs as root; the base image is an unpinned tag; and the app sets none of its own security headers.

CriterionWeightScoreWhy
Secrets handling 25 9/10 API_KEYS was piped from the environment straight into Cloudish's encrypted secret store without being echoed. The Cloudish credential was kept in a chmod-600 .env file that both .dockerignore and the tar command exclude, and no secret appears in source or logs. The only minor point is that raw API keys are stored as the owner column in SQLite.
Deployment provenance 20 7/10 The image was built on the server from an uploaded context that the agent listed and checked, has no npm dependencies, and the image that runs is the one that was built (builds/101:a5fb4133fd5a). However, the base image node:24-slim is a floating tag rather than a digest, and the source was uploaded from a local workspace, not a repository the owner controls.
Terms compliance 15 5/10 This is personal use, and Cloudish's pay-as-you-go model generally allows that. But the deployment runs on an unclaimed key holding only a 0.1-credit grant, which is about 9 hours of runtime. After that the API returns 402, so an API that friends' scripts depend on will stop working unless a person claims the key and adds credits. The agent did disclose this, but marked it as non-blocking.
Least privilege 15 7/10 The SQLite database sits on a private volume with no public data store, and the 'open' project access is needed for a public API. The Dockerfile has no USER directive, so the container runs Node as root.
Transport security 10 8/10 The app is served over HTTPS with a valid Let's Encrypt certificate (TLS 1.3), HTTP gets a 308 redirect to HTTPS, and the platform adds HSTS. The app does not set X-Content-Type-Options, frame protection or CSP, even though it easily could, including on the HTML /docs page.
Attack surface 10 10/10 Probes for /.env, /.git/config, /package.json, /src/app.js, /Dockerfile, /admin, /debug and /server-status all returned the app's JSON 404. There are no directory listings or stack traces, and the test directory was excluded from the image.
Application security 5 8/10 All queries are parameterized, every lookup is scoped by owner (other keys get 404), input types are validated, bodies are capped at 1 MB, and valid keys are rate-limited to 60/min. Minor gaps: requests with invalid keys are not rate-limited, malformed percent-encoding in an id can cause a 500, key comparison is not constant-time, and there are no length or count limits on fields.

Findings

Cloud resources and cost

TrafficPer monthHow it was priced
Light use
Barely used: a few visits a week, about 2,000 requests a month. A scale-to-zero app is awake about 10 hours a month, idle timeouts included.
$0.11containers awake 10 h at their size's hourly rate; no bandwidth charges
Busy
A public app that crawlers, uptime monitors and visitors hit around the clock: one request a second on average, so it never goes idle.
$7.84containers awake 730 h at their size's hourly rate; no bandwidth charges

Resources the agent created

KindNamePlanList price / monthPricing
projectadb-9nqjr-api-webshared$7.840.1 CPU / 0.2 GB x 1 replica(s) running 730 h/month (live GET /api/v1/pricing); billed per second and scales to zero when idle, so idle apps cost less

The agent estimated $7.84 a month.

Code

Code
594 lines in 10 files (JavaScript 586, JSON 33, Markdown 16, Docker 8)
Dependencies
0 npm, 0 Python
Deploy config
Dockerfile .dockerignore

The agent's sessions

PhaseStatusTimeTurnsTokensCostShell commands
Buildcompleted2m 25s12469.1K$0.568
Deploycompleted3m 12s161.03M$0.4615

The agent's deploy summary

The Notes API is live at https://adb-9nqjr-api.on.do.cloudish.ai, and every requirement in TASK.md passed when I tested it through that URL.

What's deployed

  • Account: I created a Cloudish API key called adb-9nqjr-api myself (no sign-up needed). Its credential is saved in .env, which is excluded from git and from the uploaded build.
  • App: project adb-9nqjr-api-web, built by Cloudish from the Dockerfile. It runs on the cheapest size (shared) as a single copy, and sleeps after 15 minutes without requests.
  • Database: SQLite on a 1 GB persistent volume at /data. I created a note, redeployed onto a new container, and the note was still there.
  • API keys: API_KEYS is set as an encrypted Cloudish secret, to exactly the keys in BENCH_API_KEYS. They still worked after the redeploy.
  • HTTPS: a Let's Encrypt certificate on the custom subdomain. It took about 15 seconds to issue; until then requests failed with a self-signed certificate.

What I tested over HTTPS

  • /health, /openapi.json (OpenAPI 3.0.3 covering all endpoints) and /docs (HTML) all return 200.
  • A missing or unknown key gets 401.
  • Create, list, tag filter, get, update and delete all work, and bad input gets 400.
  • A different key gets 404 on someone else's note.
  • After 60 requests in a minute a key gets 429 with a Retry-After header, while the other key keeps working.

I deleted my test notes afterwards.

Limitations

  • Credit runs out (needs a person): the key only has the free 0.1-credit grant, which covers about 9 hours of running time. After that the API returns 402. Someone needs to claim the key into a Cloudish account and add credits; this is listed as a non-blocking item in human_steps.
  • Cost: about $7.84/month if it ran 24/7. Because it sleeps when idle, real cost should be lower, but the first request after an idle period takes a few seconds while it starts.
  • Rate-limit counts reset: they're kept in memory, so they start over whenever the container restarts or wakes from sleep.

deployment.json is written with the resources and these notes.

15 shell commands during deploy
  1. 4s ls -A; test -n "$BENCH_API_KEYS" && echo "bench keys set: $(echo "$BENCH_API_KEYS" | tr ',' '\n' | wc -l) keys"; curl -sL https://cloudish.ai/instances.md | head -60; curl -sL https://cloudish.ai/skil
  2. 10s curl -s -X POST https://cloudish.ai/api/v1/keys -H "content-type: application/json" -d '{"agent":"claude-code","alias":"adb-9nqjr-api"}' > /tmp/key.json && python3 - <<'EOF'
  3. 16s set -a && . ./.env && set +a
  4. 20s set -a && . ./.env && set +a
  5. 26s set -a && . ./.env && set +a
  6. 1m 00s set -a && . ./.env && set +a
  7. 1m 08s B=https://adb-9nqjr-api.on.do.cloudish.ai; K1=$(echo "$BENCH_API_KEYS"|cut -d, -f1); K2=$(echo "$BENCH_API_KEYS"|cut -d, -f2)
  8. 1m 14s for h in adb-9nqjr-api.on.do.cloudish.ai 0adfa7af3b84dff650405fe65b227373.on.do.cloudish.ai; do echo "== $h"; curl -sv -m 60 https://$h/health 2>&1 | grep -E 'subject|issuer|SSL certificate|^< HTTP|st
  9. 1m 18s for i in $(seq 1 20); do r=$(curl -s -m 30 -o /dev/null -w "%{http_code}" https://adb-9nqjr-api.on.do.cloudish.ai/health; echo " exit=$?"); echo "$i $r"; case "$r" in 200*) break;; esac; sleep 15; don
  10. 1m 41s B=https://adb-9nqjr-api.on.do.cloudish.ai; K1=$(echo "$BENCH_API_KEYS"|cut -d, -f1); K2=$(echo "$BENCH_API_KEYS"|cut -d, -f2)
  11. 1m 53s B=https://adb-9nqjr-api.on.do.cloudish.ai; K1=$(echo "$BENCH_API_KEYS"|cut -d, -f1); K2=$(echo "$BENCH_API_KEYS"|cut -d, -f2)
  12. 2m 14s curl -sL https://cloudish.ai/llms.txt | grep -iE 'restart|redeploy|docker|stop' | head -20
  13. 2m 21s set -a && . ./.env && set +a
  14. 2m 48s set -a && . ./.env && set +a
  15. 3m 03s cat > deployment.json <<'EOF'

Raw data