Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions docs/claim.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
77 changes: 74 additions & 3 deletions llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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: "<uuid>"}` 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: "<value>", id, label, scopes}` — **store the `token` value now**, it is not retrievable later. Use this value as `Authorization: Bearer <token>` 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 <INSTANT_TOKEN>`. 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)

Expand Down Expand Up @@ -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 /<service>/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=<jwt>`, which 302s to `instanode.dev/claim?t=<jwt>` 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`.
Expand All @@ -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
Expand Down Expand Up @@ -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 <INSTANT_TOKEN>`. 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.
Expand All @@ -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+).
Expand All @@ -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:
Expand All @@ -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": <github_app_installation_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`.
Expand All @@ -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:
Expand Down
Loading