Skip to content

Commit ba5af38

Browse files
chore(copy): support@instanode.dev → contact@instanode.dev (public contact email) (#202)
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 <noreply@anthropic.com>
1 parent c6776c2 commit ba5af38

12 files changed

Lines changed: 29 additions & 29 deletions

openapi.snapshot.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1117,7 +1117,7 @@
11171117
"type": "boolean"
11181118
},
11191119
"request_id": {
1120-
"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.",
1120+
"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.",
11211121
"type": "string"
11221122
},
11231123
"retry_after_seconds": {
@@ -3051,7 +3051,7 @@
30513051
},
30523052
"/api/v1/billing/change-plan": {
30533053
"post": {
3054-
"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).",
3054+
"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).",
30553055
"requestBody": {
30563056
"content": {
30573057
"application/json": {
@@ -3103,14 +3103,14 @@
31033103
},
31043104
"/api/v1/billing/checkout": {
31053105
"post": {
3106-
"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.",
3106+
"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.",
31073107
"requestBody": {
31083108
"content": {
31093109
"application/json": {
31103110
"schema": {
31113111
"properties": {
31123112
"plan": {
3113-
"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.",
3113+
"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.",
31143114
"enum": [
31153115
"hobby",
31163116
"hobby_plus",

public/docs/public/trust-residency.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ Customer databases are backed up via snapshot. Snapshot retention by tier:
4545
| Pro | 30 days |
4646
| Team | 90 days |
4747

48-
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.
48+
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.
4949

5050
### Vault
5151

@@ -79,7 +79,7 @@ We aim to say only what is true. Here is what is true today.
7979
|---|---|
8080
| 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. |
8181
| 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. |
82-
| 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. |
82+
| 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. |
8383
| PCI-DSS | We do not handle cardholder data. Payment processing runs through Razorpay. Do not store card numbers in instanode.dev resources. |
8484

8585
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.

public/llms.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ The `email` field must parse as a valid RFC 5322 address (validated via Go `mail
9090

9191
## Tiers
9292

93-
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.
93+
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.
9494
Hobby Plus and Growth exist in `plans.yaml` as upsell-only intermediate tiers (reachable
9595
via dashboard prompts when a Hobby user hits a wall) and are deliberately omitted from
9696
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.
101101

102102
> **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"}`.
103103
- **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`.
104-
- **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.
104+
- **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.
105105
- **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.
106106

107107
## Conventions an LLM should follow when scripting against the platform

src/api/generated.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -451,7 +451,7 @@ export interface paths {
451451
put?: never;
452452
/**
453453
* Switch the team's subscription to a different tier
454-
* @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).
454+
* @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).
455455
*/
456456
post: {
457457
parameters: {
@@ -526,7 +526,7 @@ export interface paths {
526526
put?: never;
527527
/**
528528
* Create a Razorpay subscription and return its hosted-page URL
529-
* @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.
529+
* @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.
530530
*/
531531
post: {
532532
parameters: {
@@ -539,7 +539,7 @@ export interface paths {
539539
content: {
540540
"application/json": {
541541
/**
542-
* @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.
542+
* @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.
543543
* @enum {string}
544544
*/
545545
plan: "hobby" | "hobby_plus" | "pro";
@@ -9332,7 +9332,7 @@ export interface components {
93329332
* @enum {boolean}
93339333
*/
93349334
ok: false;
9335-
/** @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. */
9335+
/** @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. */
93369336
request_id?: string;
93379337
/** @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. */
93389338
retry_after_seconds: number | null;

src/components/ChangePlanModal.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ describe('ChangePlanModal — target tier rendering', () => {
146146
it('always renders the downgrade-via-support exit path', () => {
147147
render(<ChangePlanModal currentTier="hobby" onClose={() => {}} />)
148148
const link = screen.getByTestId('change-plan-downgrade-support') as HTMLAnchorElement
149-
expect(link.href).toContain('mailto:support@instanode.dev')
149+
expect(link.href).toContain('mailto:contact@instanode.dev')
150150
})
151151

152152
it('preselects defaultTargetTier when it is a valid (self-serve) upgrade', () => {

src/components/ChangePlanModal.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ export function ChangePlanModal({
255255
You're already on the highest plan available through self-serve.
256256
To explore a custom plan,{' '}
257257
<a
258-
href="mailto:support@instanode.dev?subject=Plan%20change"
258+
href="mailto:contact@instanode.dev?subject=Plan%20change"
259259
style={{ color: 'var(--accent)' }}
260260
>
261261
contact support
@@ -354,7 +354,7 @@ export function ChangePlanModal({
354354
<div style={{ marginTop: 6 }}>
355355
Still stuck?{' '}
356356
<a
357-
href="mailto:support@instanode.dev?subject=Change%20plan%20failed"
357+
href="mailto:contact@instanode.dev?subject=Change%20plan%20failed"
358358
data-testid="change-plan-support-fallback"
359359
style={{ color: 'var(--accent)' }}
360360
>
@@ -396,7 +396,7 @@ export function ChangePlanModal({
396396
who clicked "Change plan" hoping to downgrade aren't dead-
397397
ended. Policy memory: downgrade is support-only. */}
398398
<a
399-
href="mailto:support@instanode.dev?subject=Downgrade%20plan"
399+
href="mailto:contact@instanode.dev?subject=Downgrade%20plan"
400400
data-testid="change-plan-downgrade-support"
401401
style={{ fontSize: 11, color: 'var(--text-faint)' }}
402402
>

src/pages/BillingPage.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,7 @@ describe('BillingPage — initial render', () => {
300300
expect(screen.queryByRole('button', { name: /cancel subscription/i })).toBeNull()
301301
const link = screen.getByTestId('contact-support-cancel') as HTMLAnchorElement
302302
expect(link.tagName).toBe('A')
303-
expect(link.href.toLowerCase()).toContain('mailto:support@instanode.dev')
303+
expect(link.href.toLowerCase()).toContain('mailto:contact@instanode.dev')
304304
})
305305
})
306306

@@ -751,7 +751,7 @@ describe('BillingPage — cancellation is support-only', () => {
751751
render(<BillingPage />)
752752
await waitForLoaded()
753753
const link = screen.getByTestId('contact-support-cancel') as HTMLAnchorElement
754-
expect(link.href.toLowerCase()).toContain('mailto:support@instanode.dev')
754+
expect(link.href.toLowerCase()).toContain('mailto:contact@instanode.dev')
755755
})
756756
})
757757

0 commit comments

Comments
 (0)