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..7c2ef40 100644 --- a/llms.txt +++ b/llms.txt @@ -56,6 +56,7 @@ 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. ## Anonymous tier limits (free, 24-hour TTL) @@ -99,7 +100,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 +119,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 +150,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 +161,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 +174,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: