Docs 00-shared COPY-AND-STATES.md
Copy, States & Error Messages
Use these strings verbatim (localize via each platform's string catalog). Sentence case on every platform.
Status and value labels
| Domain value | Label | Tile / row helper text |
|---|---|---|
entitlement active |
Active | — |
scheduled |
Scheduled | Starts {date} |
disabled |
Turned off | Turned off by an administrator |
expired |
Expired | Access ended {date} |
revoked |
Revoked | Access was removed |
none |
No access | Get access · (standard app on Free) Included with Standard |
source purchase / trial / admin_grant / org_seat |
Purchased / Trial / Granted by admin / Via {org name} | |
source free |
Free | Managed by Substratal |
source subscription |
Standard | Your subscription · or · Via {org name}'s subscription |
app access_tier free / standard |
Free / Standard | |
user plan free / standard |
Free / Standard | |
subscription active / trialing / past_due |
Active / Trial / Payment issue | — / Trial ends {date} / We couldn't charge your card. Update it in Manage billing. |
subscription canceled / unpaid / incomplete / incomplete_expired / paused |
Canceled / Unpaid / Checkout not finished / Checkout expired / Paused | |
cancel_at_period_end: true |
Ends {date} | Standard access continues until {date}. |
user invited / suspended / deleted |
Invited / Suspended / Deleted | |
member scope all_members / allowlist / denylist |
Everyone in the organization / Only selected members / Everyone except selected members | |
tier shared / isolated / dedicated_region |
Shared / Isolated / Dedicated region | |
device active / inactive |
On / Off | |
device inactive_reason unregistered / session_revoked / user_suspended / user_deleted / stale / limit / credential_removed |
Notifications turned off on the device / Signed out / Account suspended / Account deleted / Not opened in 9 months / Replaced by a newer device / Push setup removed by the app | |
notification state unread / read / archived |
Unread / Read / Archived | |
notification priority high |
Important | |
notification source platform |
Substratal | |
push status queued / sent / failed / skipped |
Sending / Pushed to {n} devices / Couldn't push / Not pushed | |
skip_reason push_not_requested / push_disabled / no_devices / no_credentials / credential_failed / expired |
Sent to inbox only / Notifications are off for this app / No devices to push to / Push isn't set up for this app / Push setup needs attention / Expired before it could be sent | |
push credential active / failed |
Active / Needs attention |
Confirmations
| Action | Title | Body | Destructive button |
|---|---|---|---|
| Access off | Turn off {app} for {name}? | They'll lose access right away. You can turn it back on later. | Turn off |
| Access off (platform-managed) | Turn off {app} for {name}? | This access comes with their {Free plan / Standard subscription}. It stays off until you turn it back on, even if their plan changes. | Turn off |
| Remove grant (<24h) | Remove this grant? | Use this only for a grant made by mistake. It won't appear as revoked — it'll be gone. | Remove |
| Suspend user | Suspend {name}? | They'll be signed out everywhere, stop receiving push notifications, and can't sign in until reactivated. | Suspend |
| Delete user | Delete {name}? | Their account is deactivated. An admin can restore it. | Delete |
| Delete my account | Delete your account? | Your account is deactivated now and permanently erased on {date}. Until then, support can cancel it. | Delete account |
| Delete my account (paying) | Cancel your subscription first? | You have a Standard subscription. Deleting your account doesn't stop billing — cancel it in Manage billing. | Manage billing (secondary: Delete anyway) |
| Rotate API key | Rotate this key? | The current secret stops working immediately. Update every service that uses it first. | Rotate now |
| Revoke API key | Revoke {key name}? | This can't be undone. | Revoke |
| Rotate webhook secret | Rotate signing secret? | Both the old and new secret are valid for 24 hours. | Rotate |
| Sign out everywhere | Sign out of all other sessions? | Every other device will need to sign in again and will stop receiving push notifications until then. | Sign out |
| Remove member | Remove {name} from {org}? | They lose any app access that came from this organization, including its Standard subscription. | Remove |
| Change org coverage | Update who's covered? | {n} people gain Standard and {m} lose it right away. Billing changes on the next daily sync. | Update |
| Remove device | Stop notifications on {device}? | That device stops getting push notifications until it's set up again. | Remove |
| Turn off push (this device) | Turn off notifications on this device? | You'll still see everything in your Inbox. | Turn off |
| Remove push credential | Remove {APNs / FCM / Web Push}? | {app} stops sending push notifications through {provider}. (Web Push: every browser that signed up for notifications has to sign up again.) | Remove |
Empty states
| Screen | Title | Body | Action |
|---|---|---|---|
| Home (no apps) | No apps yet | When someone gives you access to an app, it shows up here. | — |
| Inbox | You're all caught up | New alerts and updates from your apps show up here. | — |
| Inbox (Archived) | Nothing archived | Notifications are kept for 90 days. | — |
| Devices | No devices yet | Turn on notifications to get alerts on this device. | Turn on |
| Organizations | No organizations | Create one to manage a team and share apps. | New organization |
| Users (search miss) | No results for "{q}" | Check the spelling or search by user ID. | Clear search |
| API Keys | No API keys | Create a key so your backend can call Substratal. | Create key |
| Webhooks | No webhooks | Get notified when access changes. | Add webhook |
| Push credentials | Push isn't set up | Add APNs, FCM or Web Push so this app can send notifications. | Add provider |
| Audit | No events in this range | Try a wider date range. | — |
| Forbidden | You don't have access to this | Ask an administrator if you think you should. | — |
Upsell and plan copy
| Where | Copy |
|---|---|
| Standard tile on Free | Included with Standard |
| Upsell sheet | Get every app and service for $6 a month. · Upgrade to Standard |
| Org upsell | Cover your team: $6 per person per month. You choose who's included. |
| Covered by org | Your organization covers you with Standard. |
| Double coverage | Your organization already covers you. You can cancel your own subscription in Manage billing. |
| Waiting after checkout | Finishing up… This usually takes a few seconds. |
| Downgraded | Your Standard access ended on {date}. Your data is kept and comes back when you subscribe again. |
| Business / Enterprise | Need more? Contact sales for business and enterprise plans. |
Error code → message
error.code |
HTTP | Message |
|---|---|---|
unauthenticated |
401 | Your session ended. Sign in again. |
session_revoked |
401 | You were signed out from another device. |
forbidden |
403 | You don't have permission to do that. |
not_found |
404 | This item doesn't exist or was removed. |
validation_failed |
422 | (field-level from details) |
not_unique |
409 | That {field} is already in use. |
email_taken |
409 | An account with this email may already exist. Sign in or reset your password. |
version_conflict |
409 | This changed since you opened it. Reload · Re-apply |
entitlement_already_exists |
409 | This person already has access. View it |
entitlement_platform_managed |
409 | This access is managed by Substratal. You can only turn it off or back on. |
entitlement_required |
403 | Access to this app is turned off. |
invalid_source |
422 | Free and Standard access can't be granted by hand. |
subscription_exists |
409 | You already have a subscription. Manage billing |
no_billing_account |
409 | There's no billing account yet. Subscribe first. |
plan_requires_sales |
422 | Business and enterprise plans are arranged with sales. Contact sales |
subscription_not_found |
404 | This organization doesn't have a subscription yet. Subscribe |
member_override_not_a_member |
422 | {name} isn't an active member of this organization. |
test_mode_billing_unavailable |
409 | Billing isn't available in test mode here. |
billing_unavailable |
503 | Billing is temporarily unavailable. Try again in {n}s. |
push_credential_exists |
409 | This provider is already set up. Edit it instead. |
invalid_push_credential |
422 | That key file isn't valid for {provider}. |
push_not_configured |
409 | Push isn't set up for this app yet. |
invalid_device_token |
422 | (silent; retry registration on next launch) |
reserved_category |
422 | Categories starting with security., account. or billing. are reserved. |
notification_too_large |
422 | This notification is {n} bytes over the 4 KB push limit. |
invalid_status_transition |
409 | That change isn't allowed. |
user_token_required |
400 | Sign in as a person to do this. |
tenant_suspended |
403 | This workspace is suspended. Contact support. |
destructive_operation_restricted |
403 | This key can't perform destructive actions. |
idempotency_key_reused |
422 | That request was already sent with different details. Try again. |
rate_limited |
429 | Too many attempts. Try again in {n}s. (Notifications to one person: That person has had too many notifications from this app this hour.) |
not_implemented |
501 | (never shown — the feature is hidden behind its flag) |
service_unavailable |
503 | Substratal is briefly unavailable. Retrying in {n}s. |
internal_error |
500 | Something went wrong on our side. Request ID {id} |
plan_limit_reached |
409 | Reserved by the API for future business plans; nothing returns it today. If seen: You've reached a plan limit. Contact sales |
Always show the X-Request-Id (or equivalent) in a "Details" disclosure for 5xx errors so support can trace it.
Push notification text (platform alerts shown by the Hub)
The API supplies title and body; the Hub supplies actions per category:
| Category | Actions on the notification |
|---|---|
security.new_sign_in |
It was me · Review sessions |
security.password_changed, security.email_changed, security.mfa_disabled, security.session_reuse_detected |
Secure my account |
account.organization_invitation |
Accept · View |
account.access_granted |
Open {app} |
Loading
- Lists: skeleton rows (shape of the real row) after 300 ms; never a full-screen spinner for list refreshes.
- Actions: button shows inline progress and is disabled; the rest of the screen stays interactive.