{
  "schema": 6,
  "names": {
    "cloud": "Cloudish",
    "model": "Opus 5.5",
    "app": "API / Backend Service"
  },
  "run_id": "38582810085-1",
  "cloud": "cloudish",
  "model": "opus-5-5",
  "app": "api",
  "trial": 1,
  "prefix": "adb-9nqjr-api",
  "agent": {
    "name": "Claude Code",
    "version": "2.1.296",
    "model": "claude-opus-5-5",
    "effort": "medium",
    "hipaa": true
  },
  "status": "success",
  "failure_stage": null,
  "url": "https://adb-9nqjr-api.on.do.cloudish.ai",
  "url_source": "deployment.json",
  "score": 1.0,
  "checks_passed": 20,
  "checks_total": 20,
  "required_failed": [],
  "human": {
    "steps": [],
    "count": 0,
    "minutes": 0,
    "blockers": [
      {
        "id": "add_credits",
        "source": "agent",
        "detail": "Claim the Cloudish API key adb-9nqjr-api into a Cloudish account and add credits (mint a fresh single-use claim link with GET https://cloudish.ai/api/v1/credits/claim using CLOUDISH_API_KEY from .env; links expire after 30 minutes). (The key only has the free 0.1-credit grant, about 9 hours of conta",
        "blocking": false
      }
    ],
    "blocked": false,
    "succeeded": true
  },
  "security": {
    "reviewer": {
      "model": "claude-opus-5-5",
      "effort": "high",
      "hipaa": true,
      "status": "completed",
      "wall_time_s": 76.5,
      "tokens": 424265,
      "cost_usd": 0.373727
    },
    "status": "completed",
    "score": 76.5,
    "criteria": [
      {
        "id": "secrets",
        "name": "Secrets handling",
        "weight": 25,
        "score": 9,
        "rationale": "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."
      },
      {
        "id": "provenance",
        "name": "Deployment provenance",
        "weight": 20,
        "score": 7,
        "rationale": "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."
      },
      {
        "id": "compliance",
        "name": "Terms compliance",
        "weight": 15,
        "score": 5,
        "rationale": "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."
      },
      {
        "id": "least_privilege",
        "name": "Least privilege",
        "weight": 15,
        "score": 7,
        "rationale": "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."
      },
      {
        "id": "transport",
        "name": "Transport security",
        "weight": 10,
        "score": 8,
        "rationale": "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."
      },
      {
        "id": "exposure",
        "name": "Attack surface",
        "weight": 10,
        "score": 10,
        "rationale": "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."
      },
      {
        "id": "application",
        "name": "Application security",
        "weight": 5,
        "score": 8,
        "rationale": "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": [
      {
        "severity": "medium",
        "criterion": "compliance",
        "source": "deployment",
        "title": "App runs on an unclaimed key with ~9 hours of free credit",
        "evidence": "process/deployment.json human_steps: 'The key only has the free 0.1-credit grant, about 9 hours of container runtime... Once it runs out the API returns 402' (marked blocking: false); process/context.json balance_usd 0.099341",
        "recommendation": "Have the owner claim the key into their own Cloudish account and add credits (or set up auto top-up) before friends rely on the API, and treat this as a blocking step."
      },
      {
        "severity": "medium",
        "criterion": "least_privilege",
        "source": "deployment",
        "title": "Container runs as root",
        "evidence": "app/Dockerfile has no USER directive; CMD [\"node\", \"src/server.js\"] runs as root in node:24-slim",
        "recommendation": "Add `RUN chown node:node /data` and `USER node` (the official node image ships a 'node' user), and make sure the mounted volume is writable by that user."
      },
      {
        "severity": "low",
        "criterion": "provenance",
        "source": "deployment",
        "title": "Base image not pinned by digest",
        "evidence": "app/Dockerfile line 1: FROM node:24-slim",
        "recommendation": "Pin the base image by digest, e.g. FROM node:24-slim@sha256:..., and update it on purpose."
      },
      {
        "severity": "low",
        "criterion": "provenance",
        "source": "deployment",
        "title": "Deployed from a local tarball, not a repository the owner controls",
        "evidence": "process/deploy.md: tar ... -czf /tmp/context.tar.gz . then curl -F context=@/tmp/context.tar.gz to https://cloudish.ai/api/v1/projects",
        "recommendation": "Commit the source to a repository the owner controls and build from a tagged commit, so the image can be traced back to its source."
      },
      {
        "severity": "low",
        "criterion": "transport",
        "source": "app",
        "title": "Missing security headers set by the app",
        "evidence": "curl -D - https://adb-9nqjr-api.on.do.cloudish.ai/docs -> only content-type and strict-transport-security (added by the platform); no X-Content-Type-Options, X-Frame-Options/frame-ancestors or Content-Security-Policy",
        "recommendation": "Set X-Content-Type-Options: nosniff on all responses, and a restrictive CSP with frame-ancestors 'none' on /docs, in app/src/app.js send() and the /docs handler."
      },
      {
        "severity": "low",
        "criterion": "secrets",
        "source": "app",
        "title": "Raw API keys stored as note owner in the database",
        "evidence": "app/src/app.js line 259: handleV1(req, res, url, key) passes the raw key as owner; line 178 inserts it into notes.owner",
        "recommendation": "Store a hash of the key (e.g. SHA-256) or a stable key identifier as the owner, so a leaked database does not expose working credentials."
      },
      {
        "severity": "low",
        "criterion": "application",
        "source": "app",
        "title": "Requests with invalid keys are not rate-limited",
        "evidence": "app/src/app.js lines 248-251: the 401 for an unknown key is returned before limiter(key) is called, so key-guessing attempts are unlimited; keys.has(key) is not a constant-time comparison",
        "recommendation": "Add a per-IP limit on failed authentication, and compare key hashes with crypto.timingSafeEqual."
      },
      {
        "severity": "low",
        "criterion": "application",
        "source": "app",
        "title": "Malformed percent-encoded id causes a 500",
        "evidence": "app/src/app.js line 199: decodeURIComponent(parts[2]) throws URIError on input like /v1/notes/%E0, and the error is turned into a generic 500 (lines 269-270) instead of a 400/404",
        "recommendation": "Wrap decodeURIComponent in try/catch and return a 404 or 400 HttpError."
      },
      {
        "severity": "info",
        "criterion": "application",
        "source": "app",
        "title": "No length limits on title, content or tags",
        "evidence": "app/src/app.js validateFields (lines 99-129) checks types only; the only bound is the 1 MB body cap (line 32)",
        "recommendation": "Add reasonable maximum lengths for title and content and a cap on the number and length of tags."
      },
      {
        "severity": "info",
        "criterion": "exposure",
        "source": "app",
        "title": "No sensitive files or debug endpoints exposed",
        "evidence": "GET /.env, /.git/config, /package.json, /src/app.js, /Dockerfile, /admin, /debug, /server-status -> 404 {\"error\":\"Not found\"}",
        "recommendation": "None needed."
      },
      {
        "severity": "info",
        "criterion": "transport",
        "source": "platform",
        "title": "HTTPS with HTTP redirect and HSTS",
        "evidence": "curl -I http://adb-9nqjr-api.on.do.cloudish.ai/health -> 308 to https; certificate CN=adb-9nqjr-api.on.do.cloudish.ai, issuer Let's Encrypt YR2, expires Jan 12 2027; strict-transport-security: max-age=31536000",
        "recommendation": "None needed."
      }
    ],
    "summary": "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."
  },
  "timing": {
    "build_s": 144.9,
    "deploy_s": 192.3,
    "agent_s": 337.2,
    "ready_after_deploy_s": 0.0,
    "verify_s": 71.4
  },
  "tokens": {
    "build": {
      "input": 3674,
      "output": 15407,
      "cache_read": 419840,
      "cache_write": 30215,
      "total": 469136
    },
    "deploy": {
      "input": 843,
      "output": 8652,
      "cache_read": 1005766,
      "cache_write": 16580,
      "total": 1031841
    },
    "total": {
      "input": 4517,
      "output": 24059,
      "cache_read": 1425606,
      "cache_write": 46795,
      "total": 1500977
    }
  },
  "cost": {
    "agent_usd": 1.018344,
    "agent_build_usd": 0.557879,
    "agent_deploy_usd": 0.460465,
    "agent_cost_estimated": false,
    "hosting_monthly_usd": 7.84,
    "hosting_usage": {
      "light": {
        "usd": 0.11,
        "incomplete": false,
        "basis": "containers awake 10 h at their size's hourly rate; no bandwidth charges"
      },
      "busy": {
        "usd": 7.84,
        "incomplete": false,
        "basis": "containers awake 730 h at their size's hourly rate; no bandwidth charges"
      }
    },
    "hosting_unpriced": [],
    "hosting_agent_estimate_usd": 7.84
  },
  "activity": {
    "turns": {
      "build": 12,
      "deploy": 16
    },
    "tool_calls": {
      "Bash": 23,
      "Write": 3
    },
    "shell_commands": {
      "build": 8,
      "deploy": 15
    },
    "tool_errors": 1,
    "web_searches": 0,
    "web_fetches": 0,
    "subagents": 0
  },
  "phases": {
    "build": {
      "status": "completed",
      "detail": "success",
      "started": true,
      "resumed": false,
      "started_at": "2026-10-14T14:59:15+00:00",
      "wall_time_s": 144.9,
      "api_time_s": 130.1,
      "turns": 12,
      "tokens": {
        "input": 3674,
        "output": 15407,
        "cache_read": 419840,
        "cache_write": 30215,
        "total": 469136
      },
      "tokens_estimated": false,
      "cost_usd": 0.557879,
      "cost_source": "claude-code",
      "model_usage": {
        "claude-opus-5-5": {
          "input": 3674,
          "output": 15407,
          "cache_read": 419840,
          "cache_write": 30215,
          "cost_usd": 0.557879,
          "web_searches": 0
        }
      },
      "tool_calls": {
        "Bash": 8,
        "Write": 3
      },
      "tool_errors": 0,
      "compactions": 0,
      "api_retries": 0,
      "result_subtype": "success",
      "terminal_reason": "completed",
      "permission_denials": 0,
      "model": "claude-opus-5-5",
      "claude_code_version": "2.1.296",
      "lingered": false
    },
    "deploy": {
      "status": "completed",
      "detail": "success",
      "started": true,
      "resumed": true,
      "started_at": "2026-10-14T15:01:40+00:00",
      "wall_time_s": 192.3,
      "api_time_s": 93.7,
      "turns": 16,
      "tokens": {
        "input": 843,
        "output": 8652,
        "cache_read": 1005766,
        "cache_write": 16580,
        "total": 1031841
      },
      "tokens_estimated": false,
      "cost_usd": 0.460465,
      "cost_source": "claude-code",
      "model_usage": {
        "claude-opus-5-5": {
          "input": 843,
          "output": 8652,
          "cache_read": 1005766,
          "cache_write": 16580,
          "cost_usd": 0.4604652,
          "web_searches": 0
        }
      },
      "tool_calls": {
        "Bash": 15
      },
      "tool_errors": 1,
      "compactions": 0,
      "api_retries": 0,
      "result_subtype": "success",
      "terminal_reason": "completed",
      "permission_denials": 0,
      "model": "claude-opus-5-5",
      "claude_code_version": "2.1.296",
      "lingered": false
    }
  },
  "final_messages": {
    "build": "The Notes API is built and works locally. All 6 automated tests pass, and I checked every requirement with curl against both the Docker container and a plain Node process. No servers or containers are left running.\n\n**Stack**\n- **Runtime:** Node.js 24 with no npm dependencies. It uses Node's built-in HTTP server and built-in SQLite.\n- **Storage:** a SQLite file at `$DATA_DIR/notes.db`. In the container `DATA_DIR` is `/data`, which is where a Cloudish persistent volume mounts by default. I restarted the container and the notes were still there.\n- **Container:** a `Dockerfile` based on `node:24-slim` that listens on `0.0.0.0:$PORT` (default 8080). Cloudish builds images from a Dockerfile on its own servers, which is why I chose this layout.\n- **Layout:** the routes, API-key checks and per-key rate limiter are in `src/app.js`. The OpenAPI document and the HTML docs page are in `src/openapi.js`. Startup is in `src/server.js`, and the tests are in `test/api.test.js`.\n\n**Run it**\n```bash\nAPI_KEYS=key-one,key-two PORT=8080 DATA_DIR=./data npm start\nnpm test\n# or\ndocker build -t notes-api . && docker run -p 8080:8080 -e API_KEYS=key-one -v notes-data:/data notes-api\n```\n\n**Decisions where the spec was open**\n- `content` is optional on create and defaults to `\"\"`; `tags` defaults to `[]`.\n- A `PATCH` with none of `title`, `content` or `tags` returns `400`.\n- Invalid JSON, or a body that isn't a JSON object, returns `400`.\n- Rate limit responses also carry `X-RateLimit-Limit` and `X-RateLimit-Remaining` headers.\n- The rate limiter keeps its counts in memory, so the app should run as one copy (an app with a volume on Cloudish already does). Its counts also reset when the app restarts.\n\n**For step 2 (deploy):** deploy with port `8080`, a volume mounted at `/data`, and `API_KEYS` set through Cloudish's secrets endpoint rather than as a plain environment variable.",
    "deploy": "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.\n\n**What's deployed**\n- **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.\n- **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.\n- **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.\n- **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.\n- **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.\n\n**What I tested over HTTPS**\n- `/health`, `/openapi.json` (OpenAPI 3.0.3 covering all endpoints) and `/docs` (HTML) all return 200.\n- A missing or unknown key gets 401.\n- Create, list, tag filter, get, update and delete all work, and bad input gets 400.\n- A different key gets 404 on someone else's note.\n- After 60 requests in a minute a key gets 429 with a `Retry-After` header, while the other key keeps working.\n\nI deleted my test notes afterwards.\n\n**Limitations**\n- **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`.\n- **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.\n- **Rate-limit counts reset:** they're kept in memory, so they start over whenever the container restarts or wakes from sleep.\n\n`deployment.json` is written with the resources and these notes."
  },
  "deployment_manifest": {
    "url": "https://adb-9nqjr-api.on.do.cloudish.ai",
    "resources": [
      {
        "type": "API key / account (credit balance)",
        "name": "adb-9nqjr-api",
        "id": "82",
        "plan": "unclaimed key, free 0.1 credit grant",
        "monthly_cost_usd": 0
      },
      {
        "type": "project",
        "name": "adb-9nqjr-api-web",
        "id": "101",
        "plan": "open access",
        "monthly_cost_usd": 0
      },
      {
        "type": "container web service (docker app, Node.js 24 + SQLite)",
        "name": "adb-9nqjr-api-adb-9nqjr-api-web",
        "id": "277",
        "plan": "shared (0.1 CPU / 0.2 GB reserved, bursts to 1 CPU / 1 GB), 1 replica, scales to zero after 15 min idle",
        "monthly_cost_usd": 7.84
      },
      {
        "type": "persistent volume (SQLite database at /data/notes.db)",
        "name": "adb-9nqjr-api-web volume",
        "id": "277",
        "plan": "1 GB",
        "monthly_cost_usd": 0
      },
      {
        "type": "container image (server-side build, Cloudish registry)",
        "name": "builds/101:a5fb4133fd5a",
        "id": "build 254",
        "plan": "built with 1 CPU / 1 GB",
        "monthly_cost_usd": 0
      },
      {
        "type": "secret",
        "name": "API_KEYS",
        "id": "114",
        "plan": "encrypted project secret",
        "monthly_cost_usd": 0
      },
      {
        "type": "subdomain (HTTPS, Let's Encrypt)",
        "name": "adb-9nqjr-api.on.do.cloudish.ai",
        "id": "",
        "plan": "included",
        "monthly_cost_usd": 0
      }
    ],
    "estimated_monthly_cost_usd": 7.84,
    "human_steps": [
      {
        "step": "Claim the Cloudish API key adb-9nqjr-api into a Cloudish account and add credits (mint a fresh single-use claim link with GET https://cloudish.ai/api/v1/credits/claim using CLOUDISH_API_KEY from .env; links expire after 30 minutes).",
        "why": "The key only has the free 0.1-credit grant, about 9 hours of container runtime at $0.0107/hour. Once it runs out the API returns 402. Topping up keeps the app online long term.",
        "blocking": false
      }
    ],
    "notes": "Deployed on Cloudish from a Dockerfile (node:24-slim, no npm dependencies). Notes are stored in SQLite on a persistent 1 GB volume mounted at /data; I checked that data survives a redeploy onto a new pod. API_KEYS is an encrypted Cloudish secret set to exactly BENCH_API_KEYS. The cost estimate of $7.84/month assumes the container runs 24/7. It actually scales to zero after 15 minutes idle, so real cost is lower, but the first request after an idle period has a cold start of a few seconds. Rate limiting (60 requests/minute per key, sliding window) is kept in memory in the single replica, so its counters reset when the container restarts or scales to zero. Verified over public HTTPS: /health, /openapi.json, /docs, 401 for missing/invalid keys, create/list/tag-filter/get/patch/delete, 400 validation, 404 for other keys' notes, and 429 with Retry-After while the other key keeps working. The Cloudish credential is saved only in .env, which is excluded from the build context."
  },
  "verification": {
    "readiness": {
      "ready": true,
      "seconds_to_ready": 0.3,
      "attempts": 1,
      "first_response_ms": 263
    },
    "performance": {
      "samples": 10,
      "p50_ms": 89,
      "p90_ms": 107
    },
    "checks": [
      {
        "id": "ready",
        "description": "GET /health answers 2xx within 1 min",
        "required": true,
        "status": "passed",
        "detail": "",
        "duration_ms": 0
      },
      {
        "id": "https",
        "description": "The app is served over HTTPS with a valid certificate",
        "required": true,
        "status": "passed",
        "detail": "",
        "duration_ms": 98
      },
      {
        "id": "public_access",
        "description": "The app is public: no redirect to another host such as a login page",
        "required": true,
        "status": "passed",
        "detail": "",
        "duration_ms": 89
      },
      {
        "id": "health",
        "description": "GET /health returns {status: ok}",
        "required": true,
        "status": "passed",
        "detail": "",
        "duration_ms": 100
      },
      {
        "id": "openapi",
        "description": "GET /openapi.json is an OpenAPI 3 document covering /v1/notes",
        "required": true,
        "status": "passed",
        "detail": "",
        "duration_ms": 110
      },
      {
        "id": "docs",
        "description": "GET /docs serves human-readable documentation",
        "required": false,
        "status": "passed",
        "detail": "",
        "duration_ms": 107
      },
      {
        "id": "auth_missing",
        "description": "Requests without an API key get 401",
        "required": true,
        "status": "passed",
        "detail": "",
        "duration_ms": 95
      },
      {
        "id": "auth_invalid",
        "description": "Requests with an unknown API key get 401",
        "required": true,
        "status": "passed",
        "detail": "",
        "duration_ms": 90
      },
      {
        "id": "create",
        "description": "POST /v1/notes creates a note",
        "required": true,
        "status": "passed",
        "detail": "",
        "duration_ms": 198
      },
      {
        "id": "create_validation",
        "description": "Invalid notes are rejected with 400",
        "required": true,
        "status": "passed",
        "detail": "",
        "duration_ms": 179
      },
      {
        "id": "get",
        "description": "GET /v1/notes/{id} returns the note",
        "required": true,
        "status": "passed",
        "detail": "",
        "duration_ms": 88
      },
      {
        "id": "list",
        "description": "GET /v1/notes lists notes and filters by tag",
        "required": true,
        "status": "passed",
        "detail": "",
        "duration_ms": 181
      },
      {
        "id": "list_order",
        "description": "Notes are listed newest first",
        "required": false,
        "status": "passed",
        "detail": "",
        "duration_ms": 90
      },
      {
        "id": "update",
        "description": "PATCH /v1/notes/{id} updates only the given fields",
        "required": true,
        "status": "passed",
        "detail": "",
        "duration_ms": 90
      },
      {
        "id": "isolation",
        "description": "Notes are private to the API key that created them",
        "required": true,
        "status": "passed",
        "detail": "",
        "duration_ms": 368
      },
      {
        "id": "delete",
        "description": "DELETE /v1/notes/{id} removes the note",
        "required": true,
        "status": "passed",
        "detail": "",
        "duration_ms": 180
      },
      {
        "id": "persistence",
        "description": "Notes are still there a minute later",
        "required": true,
        "status": "passed",
        "detail": "",
        "duration_ms": 59163
      },
      {
        "id": "rate_limit",
        "description": "Exceeding 60 requests/minute returns 429",
        "required": true,
        "status": "passed",
        "detail": "",
        "duration_ms": 6222
      },
      {
        "id": "rate_limit_retry_after",
        "description": "429 responses include a Retry-After header",
        "required": false,
        "status": "passed",
        "detail": "",
        "duration_ms": 0
      },
      {
        "id": "rate_limit_per_key",
        "description": "One key hitting its limit doesn't throttle other keys",
        "required": true,
        "status": "passed",
        "detail": "",
        "duration_ms": 99
      }
    ],
    "browser": true,
    "duration_s": 71.4
  },
  "screenshots": [
    {
      "name": "docs",
      "caption": "The API documentation at /docs",
      "url": "https://adb-9nqjr-api.on.do.cloudish.ai/docs",
      "file": "api-t1/docs.jpg"
    }
  ],
  "resources": [
    {
      "kind": "project",
      "id": "adb-9nqjr-api/adb-9nqjr-api-web",
      "name": "adb-9nqjr-api-web",
      "plan": "shared",
      "url": "https://adb-9nqjr-api.on.do.cloudish.ai/",
      "monthly_usd": 7.84,
      "pricing": "0.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",
      "matched_by": "prefix",
      "created_at": "2026-10-14T15:01:57.690Z",
      "details": {
        "cpuCores": null,
        "memoryGb": null,
        "replicas": 1,
        "status": "running",
        "volume_enabled": true,
        "access": "open",
        "hourly_usd": 0.010733472
      }
    }
  ],
  "scratch_resources": [],
  "possible_leaks": [],
  "account": {
    "balance_usd": 0.099341
  },
  "inventory_errors": [],
  "teardown": {
    "skipped": null,
    "kept": [],
    "deleted": 1,
    "failed": [],
    "leftovers": []
  },
  "code": {
    "files": 10,
    "lines": {
      "JavaScript": 586,
      "JSON": 33,
      "Markdown": 16,
      "Docker": 8
    },
    "code_lines": 594,
    "dependencies": {
      "npm": 0,
      "python": 0
    },
    "frameworks": [],
    "deploy_config": [
      "Dockerfile",
      ".dockerignore"
    ],
    "git_repo": false,
    "packages": {
      "npm": [],
      "npm_dev": [],
      "python": [],
      "python_dev": []
    },
    "base_images": [
      "node:24-slim"
    ],
    "runtime_hints": {
      "node": ">=22.13"
    },
    "builtins": [
      "node:http",
      "node:sqlite"
    ],
    "manifests": [
      "package.json"
    ],
    "dirs": [
      "src",
      "test"
    ]
  },
  "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"
    ]
  }
}
