From 8500ddbf347dcc3e615c8e1b378e1527dfffc719 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 7 Jun 2026 11:48:25 +0530 Subject: [PATCH] =?UTF-8?q?chore(copy):=20support@instanode.dev=20?= =?UTF-8?q?=E2=86=92=20contact@instanode.dev=20(public=20contact=20email)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Checkout/billing/pricing/plan-change/terms copy, the mailto links, llms.txt fallback, trust-residency doc, and OpenAPI snapshot/generated types. Matches the api change. Sender unchanged; /support URL unchanged — only the email address. Co-Authored-By: Claude Opus 4.8 (1M context) --- openapi.snapshot.json | 8 ++++---- public/docs/public/trust-residency.md | 4 ++-- public/llms.txt | 4 ++-- src/api/generated.ts | 8 ++++---- src/components/ChangePlanModal.test.tsx | 2 +- src/components/ChangePlanModal.tsx | 6 +++--- src/pages/BillingPage.test.tsx | 4 ++-- src/pages/BillingPage.tsx | 6 +++--- src/pages/CheckoutPage.tsx | 2 +- src/pages/PricingPage.test.tsx | 6 +++--- src/pages/PricingPage.tsx | 6 +++--- src/pages/TermsPage.tsx | 2 +- 12 files changed, 29 insertions(+), 29 deletions(-) diff --git a/openapi.snapshot.json b/openapi.snapshot.json index a3627d9..ccbe58f 100644 --- a/openapi.snapshot.json +++ b/openapi.snapshot.json @@ -1117,7 +1117,7 @@ "type": "boolean" }, "request_id": { - "description": "Echo of the X-Request-ID header for this request. Stable correlator agents can quote when emailing support@instanode.dev — saves the user from copy/pasting headers.", + "description": "Echo of the X-Request-ID header for this request. Stable correlator agents can quote when emailing contact@instanode.dev — saves the user from copy/pasting headers.", "type": "string" }, "retry_after_seconds": { @@ -3051,7 +3051,7 @@ }, "/api/v1/billing/change-plan": { "post": { - "description": "Hobby ↔ Hobby Plus ↔ Pro on the same Razorpay subscription (upgrades only — downgrades are support-assisted). Proration is handled by Razorpay; the new plan takes effect at the end of the current billing period. The Team plan is NOT yet available for self-serve plan changes — target_plan=team returns 400 tier_not_yet_available (contact sales: support@instanode.dev).", + "description": "Hobby ↔ Hobby Plus ↔ Pro on the same Razorpay subscription (upgrades only — downgrades are support-assisted). Proration is handled by Razorpay; the new plan takes effect at the end of the current billing period. The Team plan is NOT yet available for self-serve plan changes — target_plan=team returns 400 tier_not_yet_available (contact sales: contact@instanode.dev).", "requestBody": { "content": { "application/json": { @@ -3103,14 +3103,14 @@ }, "/api/v1/billing/checkout": { "post": { - "description": "Mints a Razorpay subscription for the requested plan (hobby, hobby_plus, or pro) tied to the authenticated team. The dashboard redirects the user to the returned short_url to complete payment; on success Razorpay fires subscription.activated AND subscription.charged to /razorpay/webhook — both trigger the same idempotent tier-elevation path so the team is upgraded as soon as the mandate is authorised, even before the first invoice is collected. The Team plan ($199, finite high-capacity limits — not unlimited) is NOT yet available for self-serve checkout — requesting plan=team returns 400 tier_not_yet_available (contact sales: support@instanode.dev). Capacity beyond the Team caps is Enterprise (contact sales). plan_frequency selects monthly (default) vs yearly billing — yearly returns 503 billing_not_configured until the operator creates the yearly Razorpay plan and sets RAZORPAY_PLAN_ID_*_YEARLY. promotion_code: admin-issued codes are bookmarked in the subscription notes for future discount wiring (no Razorpay Offer is applied yet — codes are not consumed until a real discount is confirmed). IDEMPOTENT: the endpoint never mints a second subscription for a team that already has a live one — if the team already holds the requested tier (or higher) it returns 400 already_on_plan, and if a prior checkout's subscription is still payable at Razorpay (status created/authenticated/pending) it returns that subscription's short_url with reused:true instead of creating a new one. This prevents a confused re-click from producing two parallel subscriptions that both charge the card.", + "description": "Mints a Razorpay subscription for the requested plan (hobby, hobby_plus, or pro) tied to the authenticated team. The dashboard redirects the user to the returned short_url to complete payment; on success Razorpay fires subscription.activated AND subscription.charged to /razorpay/webhook — both trigger the same idempotent tier-elevation path so the team is upgraded as soon as the mandate is authorised, even before the first invoice is collected. The Team plan ($199, finite high-capacity limits — not unlimited) is NOT yet available for self-serve checkout — requesting plan=team returns 400 tier_not_yet_available (contact sales: contact@instanode.dev). Capacity beyond the Team caps is Enterprise (contact sales). plan_frequency selects monthly (default) vs yearly billing — yearly returns 503 billing_not_configured until the operator creates the yearly Razorpay plan and sets RAZORPAY_PLAN_ID_*_YEARLY. promotion_code: admin-issued codes are bookmarked in the subscription notes for future discount wiring (no Razorpay Offer is applied yet — codes are not consumed until a real discount is confirmed). IDEMPOTENT: the endpoint never mints a second subscription for a team that already has a live one — if the team already holds the requested tier (or higher) it returns 400 already_on_plan, and if a prior checkout's subscription is still payable at Razorpay (status created/authenticated/pending) it returns that subscription's short_url with reused:true instead of creating a new one. This prevents a confused re-click from producing two parallel subscriptions that both charge the card.", "requestBody": { "content": { "application/json": { "schema": { "properties": { "plan": { - "description": "Self-serve purchasable plans. The Team plan is NOT yet available for self-serve checkout (contact sales: support@instanode.dev) — plan=team returns 400 tier_not_yet_available.", + "description": "Self-serve purchasable plans. The Team plan is NOT yet available for self-serve checkout (contact sales: contact@instanode.dev) — plan=team returns 400 tier_not_yet_available.", "enum": [ "hobby", "hobby_plus", diff --git a/public/docs/public/trust-residency.md b/public/docs/public/trust-residency.md index 44a20fa..f1c672b 100644 --- a/public/docs/public/trust-residency.md +++ b/public/docs/public/trust-residency.md @@ -45,7 +45,7 @@ Customer databases are backed up via snapshot. Snapshot retention by tier: | Pro | 30 days | | Team | 90 days | -Customer-initiated restore from a snapshot is rolling out for Pro and Team tiers. Until that surface is live, restore is operator-assisted — open a ticket at `support@instanode.dev` with the resource ID and target timestamp. +Customer-initiated restore from a snapshot is rolling out for Pro and Team tiers. Until that surface is live, restore is operator-assisted — open a ticket at `contact@instanode.dev` with the resource ID and target timestamp. ### Vault @@ -79,7 +79,7 @@ We aim to say only what is true. Here is what is true today. |---|---| | SOC 2 Type II | In progress. Target completion Q3 2026. Audit firm not yet selected. We do not have a SOC 2 report to share today. | | HIPAA | Not supported. We do not sign Business Associate Agreements today. If you need a BAA, email `enterprise@instanode.dev` so we can scope a Team-tier engagement and tell you whether and when we can support it. | -| GDPR | Standard Contractual Clauses (Module Two, controller-to-processor) are incorporated by reference in our [Data Processing Agreement](./dpa.md) — sign the DPA via `support@instanode.dev` to activate them. The product gating is separate: as of 2026-05, instanode.dev runs in NYC3 only, so customers whose users are EU residents should not route their PII through us without a separate residency commitment from the platform side. EU customers requiring an EU data-residency posture should wait for eu-west-1. | +| GDPR | Standard Contractual Clauses (Module Two, controller-to-processor) are incorporated by reference in our [Data Processing Agreement](./dpa.md) — sign the DPA via `contact@instanode.dev` to activate them. The product gating is separate: as of 2026-05, instanode.dev runs in NYC3 only, so customers whose users are EU residents should not route their PII through us without a separate residency commitment from the platform side. EU customers requiring an EU data-residency posture should wait for eu-west-1. | | PCI-DSS | We do not handle cardholder data. Payment processing runs through Razorpay. Do not store card numbers in instanode.dev resources. | If you are on a procurement call that requires a compliance answer not listed here, contact `security@instanode.dev` and we will tell you the truth instead of dodging. diff --git a/public/llms.txt b/public/llms.txt index 0419658..bf57304 100644 --- a/public/llms.txt +++ b/public/llms.txt @@ -90,7 +90,7 @@ The `email` field must parse as a valid RFC 5322 address (validated via Go `mail ## Tiers -The public, self-serve tiers visible at `/pricing` are: Anonymous, Hobby, and Pro. Team is not yet a self-serve tier — it is available soon; contact support@instanode.dev for onboarding. +The public, self-serve tiers visible at `/pricing` are: Anonymous, Hobby, and Pro. Team is not yet a self-serve tier — it is available soon; contact contact@instanode.dev for onboarding. Hobby Plus and Growth exist in `plans.yaml` as upsell-only intermediate tiers (reachable via dashboard prompts when a Hobby user hits a wall) and are deliberately omitted from the public tier ladder to keep the customer-facing comparison simple. Both still surface @@ -101,7 +101,7 @@ on `/api/v1/capabilities` for agent introspection. > **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`. -- **Team**: available soon — not yet self-serve. Planned at $199/mo with high finite limits (50 GB Postgres, 1.5 GB Redis, 40 GB Mongo, 40 GB queues, 300 GB storage, 30 GB vector, 100 deployments, 1000 vault entries, 100k webhooks), 50 custom domains, 90-day backups with self-serve restore, RBAC + audit log; SSO/SAML and a 99.9% SLA are also planned. Capacity beyond these caps (or dedicated/isolated infra, multi-region, or compliance such as SOC2/BAA/SSO/SLA/DPA) is Enterprise — contact sales. Team cannot be purchased or claimed today — contact support@instanode.dev for onboarding. +- **Team**: available soon — not yet self-serve. Planned at $199/mo with high finite limits (50 GB Postgres, 1.5 GB Redis, 40 GB Mongo, 40 GB queues, 300 GB storage, 30 GB vector, 100 deployments, 1000 vault entries, 100k webhooks), 50 custom domains, 90-day backups with self-serve restore, RBAC + audit log; SSO/SAML and a 99.9% SLA are also planned. Capacity beyond these caps (or dedicated/isolated infra, multi-region, or compliance such as SOC2/BAA/SSO/SLA/DPA) is Enterprise — contact sales. Team cannot be purchased or claimed today — contact contact@instanode.dev for onboarding. - **Enterprise**: custom limits, dedicated infra, compliance; contact sales@instanode.dev. Not a self-serve tier and not in `plans.yaml` — no price, no checkout. Triggers: needs more than Team's caps, dedicated/isolated or multi-region infra, or SOC2/BAA/SSO/SLA/custom DPA. ## Conventions an LLM should follow when scripting against the platform diff --git a/src/api/generated.ts b/src/api/generated.ts index 18a0b54..cb86f91 100644 --- a/src/api/generated.ts +++ b/src/api/generated.ts @@ -451,7 +451,7 @@ export interface paths { put?: never; /** * Switch the team's subscription to a different tier - * @description Hobby ↔ Hobby Plus ↔ Pro on the same Razorpay subscription (upgrades only — downgrades are support-assisted). Proration is handled by Razorpay; the new plan takes effect at the end of the current billing period. The Team plan is NOT yet available for self-serve plan changes — target_plan=team returns 400 tier_not_yet_available (contact sales: support@instanode.dev). + * @description Hobby ↔ Hobby Plus ↔ Pro on the same Razorpay subscription (upgrades only — downgrades are support-assisted). Proration is handled by Razorpay; the new plan takes effect at the end of the current billing period. The Team plan is NOT yet available for self-serve plan changes — target_plan=team returns 400 tier_not_yet_available (contact sales: contact@instanode.dev). */ post: { parameters: { @@ -526,7 +526,7 @@ export interface paths { put?: never; /** * Create a Razorpay subscription and return its hosted-page URL - * @description Mints a Razorpay subscription for the requested plan (hobby, hobby_plus, or pro) tied to the authenticated team. The dashboard redirects the user to the returned short_url to complete payment; on success Razorpay fires subscription.activated AND subscription.charged to /razorpay/webhook — both trigger the same idempotent tier-elevation path so the team is upgraded as soon as the mandate is authorised, even before the first invoice is collected. The Team plan ($199, finite high-capacity limits — not unlimited) is NOT yet available for self-serve checkout — requesting plan=team returns 400 tier_not_yet_available (contact sales: support@instanode.dev). Capacity beyond the Team caps is Enterprise (contact sales). plan_frequency selects monthly (default) vs yearly billing — yearly returns 503 billing_not_configured until the operator creates the yearly Razorpay plan and sets RAZORPAY_PLAN_ID_*_YEARLY. promotion_code: admin-issued codes are bookmarked in the subscription notes for future discount wiring (no Razorpay Offer is applied yet — codes are not consumed until a real discount is confirmed). IDEMPOTENT: the endpoint never mints a second subscription for a team that already has a live one — if the team already holds the requested tier (or higher) it returns 400 already_on_plan, and if a prior checkout's subscription is still payable at Razorpay (status created/authenticated/pending) it returns that subscription's short_url with reused:true instead of creating a new one. This prevents a confused re-click from producing two parallel subscriptions that both charge the card. + * @description Mints a Razorpay subscription for the requested plan (hobby, hobby_plus, or pro) tied to the authenticated team. The dashboard redirects the user to the returned short_url to complete payment; on success Razorpay fires subscription.activated AND subscription.charged to /razorpay/webhook — both trigger the same idempotent tier-elevation path so the team is upgraded as soon as the mandate is authorised, even before the first invoice is collected. The Team plan ($199, finite high-capacity limits — not unlimited) is NOT yet available for self-serve checkout — requesting plan=team returns 400 tier_not_yet_available (contact sales: contact@instanode.dev). Capacity beyond the Team caps is Enterprise (contact sales). plan_frequency selects monthly (default) vs yearly billing — yearly returns 503 billing_not_configured until the operator creates the yearly Razorpay plan and sets RAZORPAY_PLAN_ID_*_YEARLY. promotion_code: admin-issued codes are bookmarked in the subscription notes for future discount wiring (no Razorpay Offer is applied yet — codes are not consumed until a real discount is confirmed). IDEMPOTENT: the endpoint never mints a second subscription for a team that already has a live one — if the team already holds the requested tier (or higher) it returns 400 already_on_plan, and if a prior checkout's subscription is still payable at Razorpay (status created/authenticated/pending) it returns that subscription's short_url with reused:true instead of creating a new one. This prevents a confused re-click from producing two parallel subscriptions that both charge the card. */ post: { parameters: { @@ -539,7 +539,7 @@ export interface paths { content: { "application/json": { /** - * @description Self-serve purchasable plans. The Team plan is NOT yet available for self-serve checkout (contact sales: support@instanode.dev) — plan=team returns 400 tier_not_yet_available. + * @description Self-serve purchasable plans. The Team plan is NOT yet available for self-serve checkout (contact sales: contact@instanode.dev) — plan=team returns 400 tier_not_yet_available. * @enum {string} */ plan: "hobby" | "hobby_plus" | "pro"; @@ -9332,7 +9332,7 @@ export interface components { * @enum {boolean} */ ok: false; - /** @description Echo of the X-Request-ID header for this request. Stable correlator agents can quote when emailing support@instanode.dev — saves the user from copy/pasting headers. */ + /** @description Echo of the X-Request-ID header for this request. Stable correlator agents can quote when emailing contact@instanode.dev — saves the user from copy/pasting headers. */ request_id?: string; /** @description Seconds the agent should wait before retrying. null on 4xx (no retry — fix the request). int on transient 5xx: 30 for 503, 60 for 429, 10 for 502/504. For 429/502/503/504 the same value is also set in the Retry-After HTTP header. */ retry_after_seconds: number | null; diff --git a/src/components/ChangePlanModal.test.tsx b/src/components/ChangePlanModal.test.tsx index c4de37f..fd079a9 100644 --- a/src/components/ChangePlanModal.test.tsx +++ b/src/components/ChangePlanModal.test.tsx @@ -146,7 +146,7 @@ describe('ChangePlanModal — target tier rendering', () => { it('always renders the downgrade-via-support exit path', () => { render( {}} />) const link = screen.getByTestId('change-plan-downgrade-support') as HTMLAnchorElement - expect(link.href).toContain('mailto:support@instanode.dev') + expect(link.href).toContain('mailto:contact@instanode.dev') }) it('preselects defaultTargetTier when it is a valid (self-serve) upgrade', () => { diff --git a/src/components/ChangePlanModal.tsx b/src/components/ChangePlanModal.tsx index 4320649..5ebe417 100644 --- a/src/components/ChangePlanModal.tsx +++ b/src/components/ChangePlanModal.tsx @@ -255,7 +255,7 @@ export function ChangePlanModal({ You're already on the highest plan available through self-serve. To explore a custom plan,{' '} contact support @@ -354,7 +354,7 @@ export function ChangePlanModal({
Still stuck?{' '} @@ -396,7 +396,7 @@ export function ChangePlanModal({ who clicked "Change plan" hoping to downgrade aren't dead- ended. Policy memory: downgrade is support-only. */} diff --git a/src/pages/BillingPage.test.tsx b/src/pages/BillingPage.test.tsx index 88b9cc1..02c0ea6 100644 --- a/src/pages/BillingPage.test.tsx +++ b/src/pages/BillingPage.test.tsx @@ -300,7 +300,7 @@ describe('BillingPage — initial render', () => { expect(screen.queryByRole('button', { name: /cancel subscription/i })).toBeNull() const link = screen.getByTestId('contact-support-cancel') as HTMLAnchorElement expect(link.tagName).toBe('A') - expect(link.href.toLowerCase()).toContain('mailto:support@instanode.dev') + expect(link.href.toLowerCase()).toContain('mailto:contact@instanode.dev') }) }) @@ -751,7 +751,7 @@ describe('BillingPage — cancellation is support-only', () => { render() await waitForLoaded() const link = screen.getByTestId('contact-support-cancel') as HTMLAnchorElement - expect(link.href.toLowerCase()).toContain('mailto:support@instanode.dev') + expect(link.href.toLowerCase()).toContain('mailto:contact@instanode.dev') }) }) diff --git a/src/pages/BillingPage.tsx b/src/pages/BillingPage.tsx index 27d9ca9..cf5ecef 100644 --- a/src/pages/BillingPage.tsx +++ b/src/pages/BillingPage.tsx @@ -267,7 +267,7 @@ export function BillingPage() { We couldn't load your billing details right now. {billingErr ? {billingErr} : null}
- Try again in a moment, or contact support@instanode.dev if it persists. + Try again in a moment, or contact contact@instanode.dev if it persists.
) @@ -436,7 +436,7 @@ export function BillingPage() {
Cancel? Contact support @@ -718,7 +718,7 @@ function UpdatePaymentButton() { Contact support diff --git a/src/pages/CheckoutPage.tsx b/src/pages/CheckoutPage.tsx index 6b6da47..ef50507 100644 --- a/src/pages/CheckoutPage.tsx +++ b/src/pages/CheckoutPage.tsx @@ -82,7 +82,7 @@ import { useDashboardCtx } from '../hooks/useDashboardCtx' // stuck users back there is the right fallback — they can see what they // were about to buy and email support to complete the purchase manually. const RAZORPAY_FALLBACK_URL = 'https://instanode.dev/pricing' -const SUPPORT_EMAIL = 'support@instanode.dev' +const SUPPORT_EMAIL = 'contact@instanode.dev' // Login redirect target — used when the second-layer auth gate trips. // Lives outside the component so it survives test re-renders and matches diff --git a/src/pages/PricingPage.test.tsx b/src/pages/PricingPage.test.tsx index 8268d7e..62ba43f 100644 --- a/src/pages/PricingPage.test.tsx +++ b/src/pages/PricingPage.test.tsx @@ -10,7 +10,7 @@ * yearly CTA aimed at /app/checkout?plan=…&frequency=…. * 2. The FAQ no longer claims "Cancel anytime" — that contradicted the * no-self-serve-cancel policy. We assert the new copy mentions - * support@instanode.dev so the BillingPage and PricingPage copy + * contact@instanode.dev so the BillingPage and PricingPage copy * stay in lock-step. * 3. M11 source-of-truth-risk regression: the four public tier cards * (anonymous, hobby, pro, team) are all present. The pricing matrix @@ -107,10 +107,10 @@ describe('PricingPage — FAQ matches no-self-serve-cancel policy (W12 H14)', () expect(body).not.toContain('Cancel anytime') }) - it("mentions support@instanode.dev with the 24h SLA — matches BillingPage copy", () => { + it("mentions contact@instanode.dev with the 24h SLA — matches BillingPage copy", () => { renderPage() const body = document.body.textContent ?? '' - expect(body).toContain('support@instanode.dev') + expect(body).toContain('contact@instanode.dev') expect(body).toContain('within 24h') }) }) diff --git a/src/pages/PricingPage.tsx b/src/pages/PricingPage.tsx index 34a1989..ee3bb98 100644 --- a/src/pages/PricingPage.tsx +++ b/src/pages/PricingPage.tsx @@ -236,14 +236,14 @@ const FAQ: { q: string; a: string }[] = [ // Calling this out on the public surface stops customers from // emailing us asking "what's $19/mo or $99/mo?". q: 'What are Hobby Plus and Growth — I see them in the API?', - a: "Intermediate tiers ($19/mo and $99/mo) summarized above under \"Between the headline tiers\". They sit between Hobby/Pro and Pro/Team and are surfaced to existing customers as upgrade nudges (e.g. when a Hobby team hits 80% of its quota). They're not in the headline ladder on purpose — three public tiers are a cleaner first-time funnel. If you want them, ask in the dashboard or email support@instanode.dev." + a: "Intermediate tiers ($19/mo and $99/mo) summarized above under \"Between the headline tiers\". They sit between Hobby/Pro and Pro/Team and are surfaced to existing customers as upgrade nudges (e.g. when a Hobby team hits 80% of its quota). They're not in the headline ladder on purpose — three public tiers are a cleaner first-time funnel. If you want them, ask in the dashboard or email contact@instanode.dev." }, { // W12 H14: previous copy said "Cancel anytime" which contradicted the // platform's no-self-serve-cancel policy. The honest answer matches // the BillingPage copy: cancellation is support-only with a 24h SLA. q: 'How is billing handled?', - a: "Razorpay subscriptions. Cancel by emailing support@instanode.dev — we'll process within 24h. Existing resources keep their tier until the end of the current period." + a: "Razorpay subscriptions. Cancel by emailing contact@instanode.dev — we'll process within 24h. Existing resources keep their tier until the end of the current period." }, { q: 'Can I move resources between envs?', @@ -254,7 +254,7 @@ const FAQ: { q: string; a: string }[] = [ // platform's no-self-serve-cancel/downgrade policy and the sibling // billing FAQ above. The previous copy implied a self-serve toggle. q: 'What happens if I downgrade?', - a: "Downgrades are handled by support — email support@instanode.dev and we'll process within 24h. Existing resources retain their old tier limits as a courtesy; new provisions follow the new tier." + a: "Downgrades are handled by support — email contact@instanode.dev and we'll process within 24h. Existing resources retain their old tier limits as a courtesy; new provisions follow the new tier." } ] diff --git a/src/pages/TermsPage.tsx b/src/pages/TermsPage.tsx index 99aaaff..3ec4f15 100644 --- a/src/pages/TermsPage.tsx +++ b/src/pages/TermsPage.tsx @@ -51,7 +51,7 @@ export function TermsPage() { Anonymous tier is free with a 24-hour TTL. Paid tiers (Hobby / Hobby Plus / Pro / Team) bill via Razorpay from day one — there is no free trial. Cancel by emailing{' '} - support@instanode.dev; existing resources keep + contact@instanode.dev; existing resources keep their tier until the end of the current billing period.