diff --git a/docs/claim.md b/docs/claim.md index 1bc681d..ca845b5 100644 --- a/docs/claim.md +++ b/docs/claim.md @@ -23,6 +23,8 @@ Click the magic link to set a session cookie. Every resource attached to your fingerprint transfers to your team atomically; the connection URLs don't change so any already-running code keeps working. -Claimed resources move to your team's tier (hobby by default — $9/mo). There -is no separate trial period on paid tiers — **the 24-hour anonymous slice -is the trial**. +Claimed resources move to your team's **Free tier** (24h TTL, same limits as +anonymous) — claiming gives you an account, not durability. Resources keep +expiring at 24 hours until you upgrade to a paid tier (Hobby $9/mo or above) +in the dashboard. Paid tiers bill from day one — there is no separate trial +period. diff --git a/llms.txt b/llms.txt index 40ac316..ca8c4fd 100644 --- a/llms.txt +++ b/llms.txt @@ -56,7 +56,14 @@ Pick a descriptive name per resource (e.g. `"prod-db"`, `"sessions-cache"`, `"ev - `POST /auth/github` — body-flow GitHub login (server-to-server; not used by the dashboard). - `GET /auth/me` — current user/team. Requires session JWT. - `POST /auth/logout` — `jti`-revocation via Redis set. Requires session JWT. +- **`POST /api/v1/leads`** — Enterprise contact / interest form. Body: `{"email": "you@corp.com", "name": "Alice", "company": "Acme", "use_case": "We need unlimited Postgres..."}`. Only `email` is required (validated via `mail.ParseAddress`; 254-char cap; returns `400 invalid_email_format` on bad input). Optional fields: `name` (≤128 chars, `400 invalid_name` if over), `company` (≤128 chars, `400 invalid_company`), `use_case` (≤1024 chars, `400 invalid_use_case`). Returns `201 {ok: true, id: ""}` on success. No authentication required. Authenticated callers have the lead automatically linked to their team. Use this when a user needs capacity beyond the Pro tier (e.g. dedicated infra, SAML, compliance) — it reaches the instanode.dev team directly. The MCP tool `create_lead` wraps this endpoint. - **`POST /api/v1/billing/promotion/validate`** — Validate a promo code without applying it. Body: `{"code": "EARLYBIRD"}`. Returns `{ok, discount_percent, discount_amount_inr, message}` for valid codes. Promo codes only DISCOUNT at checkout once a corresponding Razorpay Offer exists in the dashboard — validate-result `ok=true` is necessary but not sufficient until then. +- **Personal Access Tokens (PATs) — long-lived bearer tokens for agents and CI.** Requires a session JWT (dashboard login or CLI device-flow). All three endpoints live under `/api/v1/api-keys`. + - `GET /api/v1/api-keys` — list tokens (without the secret value). Returns `{ok, tokens: [{id, label, created_at, last_used_at, scopes}]}`. + - `POST /api/v1/api-keys` — mint a token. Body: `{"label": "my-agent", "scopes": ["provision", "deploy"]}`. Returns `{ok, token: "", id, label, scopes}` — **store the `token` value now**, it is not retrievable later. Use this value as `Authorization: Bearer ` on subsequent API calls (set as `INSTANT_TOKEN` env var for the CLI and SDKs). + - `DELETE /api/v1/api-keys/:id` — revoke a token immediately. Returns `{ok}`. The token is also revoked if the team deletes the account. + - **When to use a PAT vs CLI device-flow:** device-flow (`instant login`) is convenient interactively but produces a session token that expires when the user's dashboard session does (24h). A PAT never expires until revoked — prefer it for long-running CI pipelines, deployed agents, and MCP server configs. The CLI's `INSTANT_TOKEN` env var accepts both. +- **`GET /api/v1/audit`** — Customer-facing audit log export. Returns a paginated JSON log of all team actions (`resource.created`, `resource.deleted`, `deploy.created`, `deploy.failed`, `billing.upgraded`, etc.). Query params: `limit` (default 50, max 200), `cursor` (opaque string from previous page's `next_cursor`), `kind` (filter by event kind), `from`/`to` (ISO 8601, inclusive). Requires `Authorization: Bearer `. Each event: `{id, kind, team_id, actor_id, actor_email, resource_id, metadata, created_at}`. A CSV stream variant is at `GET /api/v1/audit.csv` (same params, `Content-Type: text/csv`) — useful for compliance exports. ## Anonymous tier limits (free, 24-hour TTL) @@ -99,7 +106,7 @@ the public tier ladder to keep the customer-facing comparison simple. Both still on `/api/v1/capabilities` for agent introspection. - **Anonymous**: free, 24h TTL, the limits above. Note: once an anonymous resource expires, the same network fingerprint must do a one-time email claim before provisioning a replacement — a repeat `POST //new` returns `402 {"error":"free_tier_recycle_requires_claim", "claim_url": "...", "upgrade_url": "..."}` with an `agent_action`. The `claim_url` is a working, tokenized link — `https://api.instanode.dev/start?t=`, which 302s to `instanode.dev/claim?t=` so the claim page hydrates for this fingerprint (it is NOT a tokenless dead-end). Hand the user the `claim_url` (30 seconds, no card), then retry the provision. -- **Hobby**: $9/mo. 1 GB Postgres, 50 MB Redis, 100 MB Mongo, 512 MB storage, 1 app deploy. No TTL. Resource-count cap: 2 active resources per service (postgres/vector/redis/mongodb/storage). +- **Hobby**: $9/mo. 1 GB Postgres, 50 MB Redis, 100 MB Mongo, 512 MB storage, 1 app deploy. No TTL. Resource-count cap: 2 active resources per service (postgres/vector/redis/mongodb/storage). Exceeding the count cap returns `402 {"error":"resource_count_limit_exceeded","agent_action":"..."}` — the agent should surface the upgrade link from `agent_action` rather than retrying. > **Upgrading auto-promotes in-flight deployment TTLs.** When a team upgrades to any paid tier (Hobby / Hobby Plus / Pro / Growth / Team), the Razorpay subscription.charged webhook flips the team's `default_deployment_ttl_policy` from `auto_24h` to `permanent` (so every future `POST /deploy/new` defaults to no TTL) AND promotes every existing `auto_24h` non-terminal deploy to permanent (clearing `expires_at`). Per-deploy `ttl_policy='custom'` and `ttl_policy='permanent'` rows are never touched — only the `auto_24h` class is rolled forward. To restore the 24h-default behaviour after an upgrade, `PATCH /api/v1/team/settings {"default_deployment_ttl_policy":"auto_24h"}`. - **Pro**: $49/mo. 10 GB Postgres, 512 MB Redis, 5 GB Mongo, 50 GB storage, 10 apps. Resource-count cap: 5 active resources per service (redis is 3 — Redis RAM is the binding cost). Per-tier counts are introspectable via `resource_count_limit` on `/api/v1/capabilities`. @@ -118,6 +125,7 @@ on `/api/v1/capabilities` for agent introspection. 5. **Internal vs public URL** — when the calling code itself runs *inside* a `/deploy/new` container, prefer `internal_url` (cluster-internal hostname). For everything else, use `connection_url` (public). 6. **Fingerprint dedup** — anonymous callers get rate-limited to 5 provisions/day/IP-subnet. The 6th call returns the existing token. Don't expect 6 different DBs from 6 calls on the same IP. 7. **Idempotency is on by default** — every POST that creates a resource (db/cache/nosql/queue/storage/webhook/vector/deploy/stacks/backup/restore/billing-checkout/team-invite/api-keys/twin/promote/admin-promo) is idempotent. Two layered guards: (a) the standard `Idempotency-Key` request header, Stripe-shape, 24h TTL — pass a UUID per logical retry; (b) when no header is sent, the server synthesises a fingerprint from `sha256(scope, route, canonical-body)` and dedups identical retries inside a 120s window (covers double-clicks, mobile double-taps, agent retries on transient 5xx). Replays carry `X-Idempotent-Replay: true`. Every create response sets `X-Idempotency-Source: explicit | fingerprint | miss` so callers can branch on which path matched. Use the explicit header when you need exactly-once guarantees beyond 120s. +9. **`env` tags resources to an environment scope.** Every provisioning endpoint (`/db/new`, `/vector/new`, `/cache/new`, `/nosql/new`, `/queue/new`, `/storage/new`, `/webhook/new`, `/stacks/new`) accepts an optional `"env"` string field (`"development"` or `"production"`). Default when omitted: `"development"`. The resolved value is echoed back in the response as `"env"`. Environment is metadata only — it does not change isolation, limits, or TTL. Use `"env":"production"` for resources backing a live user-facing app so the dashboard can show them in the right column. An agent should pass `env` so the user's dashboard reflects the deployment intent, but must never reject or retry a request solely because `env` is missing from the response. 8. **Don't send fields the endpoint doesn't define** — there are no `region`, `size`, `plan`, or similar knobs on the provisioning bodies (the only required field is `name`; a few endpoints take documented optionals like `/deploy/new`'s `port`/`env`/`env_vars`). Unrecognized top-level JSON keys are accepted (not rejected) but **ignored**, and the success response echoes them back in an `ignored_fields` array (sorted). So if you sent `{"name":"db","region":"nyc3","size":"large"}` you'll get `"ignored_fields":["region","size"]` — a signal that those keys did nothing. Treat a non-empty `ignored_fields` as a lint warning that your request shape drifted from the contract; don't keep sending fields it lists. ## Text-only routes for LLM consumption @@ -148,6 +156,7 @@ Aggregate file: [/llms-full.txt](https://instanode.dev/llms-full.txt) — concat ## Machine-readable references +- `GET /api/v1/resources` — List all resources on the caller's account. Returns `{ok, resources: [{id, service, name, tier, env, status, token, created_at, expires_at}]}`. Requires `Authorization: Bearer `. Useful before provisioning to check what already exists — avoids duplicate provisions when an agent loses state between turns. Filter by `service` (`postgres`, `redis`, `mongodb`, `nats`, `storage`, `webhook`) and `env` (`development`, `production`) in the query string. - `https://api.instanode.dev/openapi.json` — OpenAPI 3.1 spec for the full API surface, including paid-tier endpoints, the agent-claim flow, and webhook inspection. - `https://github.com/InstaNode-dev/content` — public repo for marketing content (blog posts, use cases, /docs sections, /llms.txt). Edit prose here. - `https://github.com/InstaNode-dev/instanode-web` — public repo for the React dashboard + SSG pipeline. @@ -158,10 +167,10 @@ Aggregate file: [/llms-full.txt](https://instanode.dev/llms-full.txt) — concat The instanode.dev MCP server wraps the API as tools, so an MCP-capable agent never has to hand-roll curl. It does more than create resources — it can **operate** the whole bundle. - **Provision / deploy (create):** `create_postgres`, `create_vector`, `create_cache`, `create_nosql`, `create_queue`, `create_storage`, `create_webhook`, `create_deploy`, `create_stack` — plus `list_resources`, `list_deployments`, `get_deployment`, `get_stack`, `claim_resource`, `delete_resource`, `delete_deployment`, `redeploy`. -- **Plan before you call:** `get_capabilities` reads the live per-tier capability matrix (storage / connection / deployment caps per tier) so an agent can size a provision *before* a call 402s. +- **Plan before you call:** `get_capabilities` reads the live per-tier capability matrix (storage / connection / deployment caps per tier, plus `resource_count_limit` per service per tier) so an agent can size a provision *before* a call 402s. Equivalent REST call: `GET /api/v1/capabilities` (no auth required). Returns `{tiers: [{name, storage_mb, connections, deployments_apps, resource_count_limit}]}` — iterate the response and pick the lowest tier that satisfies the required limits rather than hard-coding tier names. - **Debug a failed deploy:** `get_deployment_events` reads the failure-timeline autopsy (`kind`/`reason`/`exit_code`/`hint`/`last_lines`) so the agent can self-correct a broken Dockerfile and redeploy in place. - **Operate (not just create):** - - `set_vault_key` / `rotate_vault_key` — write or rotate a team-vault secret a deploy references as `vault://env/KEY`. + - `set_vault_key` / `rotate_vault_key` — write or rotate a team-vault secret a deploy references as `vault://env/KEY`. REST: `PUT /api/v1/vault/:env/:key` (body `{"value":"..."}`) to write; `POST /api/v1/vault/:env/:key/rotate` to rotate. Vault values are AES-256-GCM encrypted at rest. A pod that references `vault://production/DB_URL` in its `env_vars` at deploy-time has the value injected as a plain env var inside the container — the `vault://` URI is never exposed to the running process. Rotate a key and call `PATCH /deploy/:id/env {"env_vars":{"DB_URL":"vault://production/DB_URL"}}` + redeploy to have pods pick up the new value without a full re-build. - `update_deploy_env` / `update_stack_env` — merge env vars into an existing deployment or stack (redeploy to apply). - `presign_storage` — mint a short-lived (≤1h) presigned S3 URL scoped to a storage prefix (the `broker`-mode flow). - `pause_resource` / `resume_resource` — suspend and un-pause a resource without deleting it (Pro+). @@ -171,6 +180,37 @@ The instanode.dev MCP server wraps the API as tools, so an MCP-capable agent nev This means an agent can run the entire arc — provision → deploy → set secrets → presign storage → debug a failure → rotate credentials → pause/resume — without ever leaving the MCP toolset. Every `create_*` tool surfaces the API's `note` + `upgrade` (claim URL) fields so the agent can show the user how to keep a resource past the anonymous TTL. +## Go SDK (`github.com/InstaNode-dev/sdk-go`) + +The instanode.dev Go SDK wraps the full API surface as typed method calls so Go agents never have to hand-roll HTTP. It mirrors the MCP server's coverage — provision, operate, inspect. + +```go +import instanode "github.com/InstaNode-dev/sdk-go" + +c := instanode.NewClient(instanode.WithToken(os.Getenv("INSTANT_TOKEN"))) + +// Provision +db, err := c.CreatePostgres(ctx, instanode.CreateParams{Name: "prod-db", Env: "production"}) +// db.ConnectionURL — ready to use + +// Deploy +deploy, err := c.CreateDeploy(ctx, instanode.DeployParams{ + Name: "my-api", Tarball: tarBytes, Port: 8080, Env: "production", +}) + +// Operate +_, err = c.SetVaultKey(ctx, instanode.VaultKeyParams{Env: "production", Key: "DB_URL", Value: db.ConnectionURL}) +_, err = c.UpdateDeployEnv(ctx, instanode.EnvPatchParams{ID: deploy.ID, EnvVars: map[string]string{"RELOAD": "1"}}) + +// Inspect +events, err := c.GetDeploymentEvents(ctx, deploy.ID) + +// Enterprise contact +_, err = c.CreateLead(ctx, instanode.LeadParams{Email: "cto@corp.com", Company: "Acme", UseCase: "..."}) +``` + +Token resolution: `WithToken(t)` → `INSTANT_TOKEN` env var → CLI saved credentials. All methods return typed structs; error is `*instanode.APIError` with `Code`, `StatusCode`, and `AgentAction` for machine-parseable recovery. + ## Deleting a deployment (paid tiers — two-step, email-confirmed) Paid customers free a consumed deployment slot via a two-step flow: @@ -186,6 +226,16 @@ Anonymous tier (24h TTL): no email on file, `DELETE` returns 200 immediately. Se - [Deployments](https://instanode.dev/docs#deploy): POST /deploy/new + private deploys + custom domains + two-step email-confirmed deletion + 24h-TTL semantics (default; three routes to keep a deploy permanent) +## GitHub auto-deploy (continuous deployment from a repository) + +Connect a paid-tier deployment to a GitHub repository so every push to the tracked branch triggers a rebuild in place — same URL, no TTL change. + +- **`POST /api/v1/deployments/:id/github`** — Connect a deployment to a GitHub repo. Body: `{"repo": "owner/repo", "branch": "main", "install_id": }`. Returns `{ok, github: {repo, branch, connected_at}}`. Requires the instanode.dev GitHub App to be installed on the target repository (dashboard Settings → Integrations → GitHub) and the request must carry an authenticated team Bearer. Once connected, a push to `branch` triggers `POST /deploy/:id/redeploy` automatically. +- **`GET /api/v1/deployments/:id/github`** — Inspect the current GitHub connection. Returns `{ok, github: {repo, branch, connected_at, last_triggered_at, last_commit_sha}}` or `{ok, github: null}` if not connected. +- **`DELETE /api/v1/deployments/:id/github`** — Disconnect GitHub auto-deploy. Does not delete the deployment or alter its current running state. Returns `{ok}`. + +**When to use:** recommended for production deployments where the user wants CI/CD without a separate pipeline. The agent sets it up once; subsequent deploys are automatic on push. For one-shot or ephemeral deploys (demos, prototypes), skip the connection and call `POST /deploy/new` with `redeploy=true` manually. + ## Debugging a failed deploy (agent self-recovery) Deploy failed? You don't need cluster access — the platform classifies the failure and serves the real error back over HTTP. Read the classified cause at `GET /api/v1/deployments/:id/events` → `events[]` with `reason`, `last_lines` (the tail of the build-pod log = the actual error output), and `hint` (a plain-language remedy). For the one-line summary use `GET /api/v1/deployments/:id` → `status` + `error_message`. The loop: read `reason` + `last_lines` + `hint` → fix the Dockerfile / tarball / `port` / `env_vars` → redeploy in place (`POST /deploy/:id/redeploy`, or `redeploy=true` on `POST /deploy/new` with the same `name`) → re-poll status until `running`. Watch the build live with the SSE stream `GET /deploy/:id/logs`. @@ -196,6 +246,27 @@ Deploy failed? You don't need cluster access — the platform classifies the fai Full guide: [https://instanode.dev/docs#troubleshooting-deploys](https://instanode.dev/docs#troubleshooting-deploys) (markdown mirror: [/docs/troubleshooting-deploys.md](https://instanode.dev/docs/troubleshooting-deploys.md)). +## Quick error-recovery reference for agents + +| HTTP status | `error` code | Meaning | Recovery | +|---|---|---|---| +| 400 | `name_required` | Missing or empty `name` field | Add `"name": "..."` to the request body | +| 400 | `invalid_name` | `name` fails `^[A-Za-z0-9][A-Za-z0-9 _-]*$` or >64 chars | Use a valid name | +| 400 | `invalid_email_format` | Email fails validation on `/claim` or `/api/v1/leads` | Use a valid RFC 5322 address | +| 400 | `missing_email` | `email` field absent on `/claim` | Add `"email": "..."` | +| 401 | `unauthorized` | Missing or invalid Bearer token | Set `INSTANT_TOKEN` or run `instant login` | +| 402 | `free_tier_recycle_requires_claim` | Anonymous resource expired; fingerprint must claim before re-provisioning | Hand user the `claim_url` from the envelope | +| 402 | `resource_count_limit_exceeded` | Hit the per-service resource count cap for the tier | Upgrade (follow `agent_action`) or delete an existing resource | +| 402 | `deploy_limit_exceeded` | Hit `deployments_apps` cap for the tier | Upgrade or delete an existing deployment | +| 402 | `storage_limit_exceeded` | Object write would exceed tier storage quota | Upgrade or prune objects | +| 404 | `resource_not_found` | Token refers to a deleted or non-existent resource | Re-provision | +| 409 | `already_claimed` | JWT used for claim was already consumed | Redirect user to dashboard | +| 409 | `not_ready` | Deployment exists but has no provider id yet | Re-poll in 5s | +| 413 | `tarball_too_large` | Tarball decoded size >10 MB | Slim the tarball or use a multi-stage Docker build | +| 503 | `provisioner_unavailable` | Provisioner gRPC backend is down | Retry with exponential back-off (the `retry` field in the envelope gives a suggested delay) | + +Every error envelope includes an `agent_action` string when recovery is possible — surface it to the user verbatim. The `retry` field (seconds) signals the server-suggested back-off when present. + ## How to use this file If you're an LLM helping a user build something, you can: