User guide

Everything Shipnest does, top to bottom.

Each section maps to a real screen — the path in /like-this opens the page directly when you're signed in. Use the sidebar to jump, or scroll for the full tour.

Get started

Quick start

/onboarding
  1. Sign up at /signup. Free tier covers 100 labels/month with no credit card.
  2. On /onboarding, the 4-step wizard walks you through naming your warehouse, picking a default currency, connecting your first sales channel, and connecting at least one carrier.
  3. Place a test order on the connected channel. It syncs into /orders automatically (within 10 min via cron, or immediately via webhook for Shopify / Woo / BigCommerce).
  4. Open the order detail and click Rate-shop. Quotes from every enabled carrier appear sorted by price. Pick one and click Buy label.
  5. Print the label (browser print or via PrintNode if wired), drop the parcel with the carrier, and the handover scan flips the row to "handed over".

That's the core loop. Everything else below makes the loop faster, cheaper, more automated, or more compliant.

Tip
Use the sidebar to skip ahead. Each section's monospace pill (e.g. /orders) is the in-app route — open the dashboard in another tab to follow along live.

Dashboard

/dashboard

/dashboard is the start-of-day glance. Six KPI cards across the top:

  • Orders awaiting ship — pending count, deep-links to /orders filtered to AWAITING_SHIPMENT + ON_HOLD.
  • Labels printed (7d) — sparkline + week-over-week delta.
  • Pending handover — labels printed but not yet physically handed to the carrier (orange when > 0).
  • At SLA risk — orders whose channel-derived ship-by deadline is today or past (red when > 0).
  • Spend (7d) — per-currency stack: a multi-shop org running US + UK + EU sees $2,000 · £900 · €800 rather than a fake FX-converted single number.
  • Open claims — insurance claims you filed inDRAFT / SUBMITTED / UNDER_REVIEW.

Below: a Ready to ship strip with the 5 most recent unshipped orders (one-click into the order detail), a Carrier split chart showing volume + cost per carrier, and a Channel prompt that lights up when you have channels available to connect.

The dashboard polls every 30 seconds while the tab is focused so the numbers stay fresh without a hard refresh.

Daily workflow

Orders

/orders

/orders is the table of every order across every channel — synced from Shopify / Amazon / Etsy / eBay / Woo / BigCommerce / Squarespace / Magento, plus manual orders entered via /orders/new.

The DataTable supports global search, multi-column sort (shift-click to add a sort), per-column filters (text / number / select / multi-select / boolean / date), column reordering, hiding, pinning, density toggle, group- by, saved views and CSV export. Every preference is per- user per-table, synced across devices via the TablePreference table.

Channel column reads the denormalised sourceName field first (so the channel label survives a store disconnect), falling back to the live Store relation, finally to "Manual".

Ship-by column shows the channel-derived deadline (Amazon 1 business day, Shopify 2 days, Etsy 3 days) with colour coding: red for overdue, amber for today, soft amber for next 2 days, grey for later.

Bulk actions when you select multiple rows: add tag, remove tag, change status, queue to /batch for fan-out rate-shop + buy.

Order detail (/orders/[id]) renders the full picture: items + images, ship-to / ship-from / bill-to addresses, channel + customer, applied automation hints, customs declaration form (international lanes only), hazmat panel, rate-shop panel and a terminal-status banner once the order ships.

Shipments

/shipments

/shipments lists every label you've bought, voided or had returned. Same DataTable surface as orders: search, filter, sort, save views, export CSV.

Per-row columns: tracking number, carrier, service, status (label-purchased / in-transit / out-for-delivery / delivered / exception / returned / voided), cost in the shipment's native currency, CO₂e estimate (hidden by default; reveal via column chooser), handover state.

Shipment detail covers print history, claims filed against the shipment, third-party insurance policy if any, tracking timeline, void button (with carrier-specific window: UPS 90d, FedEx 60d, DHL 28d, USPS 28d, Royal Mail 14d, EasyPost 30d).

Tracking updates land via carrier webhooks (signed) or the /api/cron/tracking cron every 30 minutes for in-transit labels.

/batch queues N orders, fans out a rate-shop for each, and lets you buy the labels in one transaction. Picks the cheapest by default; toggle Cheapest / Fastest / Best margin to re-sort. Each row tracks status: queued → quoted → bought → printed.

Stats at the top stack money per currency (a batch with UK + US orders shows Quoted total: $4,500 · £1,200 rather than meaningless mixed sum).

On Buy-all, labels print to PrintNode (when wired) or open in a browser tab (fall-back). Toast summarises: 20 to PrintNode · 3 to browser.

/scan is one barcode-first console with three modes — Print label, Pick & pack, Handover — selectable from the segmented control at the top, also keyboard-driven via 1 / 2 / 3. Each mode is a focused workflow; the URL keeps the active mode (/scan?mode=pick, etc.) so you can deep-link floor-station bookmarks.

All modes share the same hardware ergonomics: auto-focus + window-focus re-focus so a Zebra / Honeywell scanner with Enter-terminator works straight out of the box, and the same WebAudio beep palette (880Hz OK / 520Hz warn / 180Hz error) so a worker moving between modes hears consistent feedback.

Mode 1 · Print label

Scan an order barcode → Shipnest loads the order, runs rate-shop with your configured strategy, picks the winner, buys the label, dispatches to your default printer via PrintNode. The session feed shows the last scans with status + tracking + carrier. Toggle off Ship-on-scan if you want the scan to just match the order without auto-buying.

Mode 2 · Pick & pack

Two-stage workflow that sits between order receipt and label buy:

  1. Scan the order barcode (or type the order number). System loads the order and renders a checklist of SKUs with quantities.
  2. Scan each SKU off the shelf. Each scan ticks the matching line's counter. Mismatches beep error and don't increment.
  3. When every line is fully scanned, hit Mark packed & continue. A pack.verified audit row writes and you redirect to the order detail for rate-shop + buy.

Pack verification is informational — nothing blocks the label buy if you skipped the scan. It exists to catch wrong-item-in-box errors before they become refunds / chargebacks, and the audit row tells ops who packed what at what time when a customer complaint arrives.

Mode 3 · Handover

Records the moment a parcel physically leaves the warehouse. Orthogonal to the carrier-driven shipment status — a label can show IN_TRANSIT per the carrier without ever being scanned handover, and vice versa.

Today's scans show in the Scanned today feed (org-wide, persisted via AuditLog so it survives refresh / device swap / laptop lid close). Each row shows tracking + carrier + order number + operator name so two workers sharing a station can tell their scans apart. Use /scan/handover/history for the full audit trail with operator + outcome filter + date-range search + CSV export.

Rate calculator

/rate-calculator

/rate-calculator is an ad-hoc rate quote without an order. Paste any addresses + parcels + options → see quotes from every enabled carrier account. Useful for customer service answering "how much would shipping be for X?" or comparing your UPS rate vs your contract rate-card before committing.

Automations

/rules is where you encode operational policy: which carrier handles which lane, when to add insurance, when to flag for review, what tags to apply. Rules fire automatically on five triggers and stack cleanly so a single order can be touched by many.

The /rules/[id] builder is a visual nested AND/OR tree. Add a rule, choose a trigger, build the condition tree, list the actions. Click Test against an order to dry-run before saving.

Each leaf in the AND/OR tree is a triple:

  • Field — order, customer or destination attribute. Most-used fields: lane (domestic / international / intra-EU / EU export / EU import — derived from your warehouse + ship-to country), destination.country, weightG, totalCents, tag, sku, channel, orderAgeHours, placedAt, itemCount.
  • Operator — eq / neq / gt / lt / between / contains / startsWith / endsWith / in / notIn / exists. Text operators are case-insensitive. Use in with a comma-separated list (e.g. ES, FR, DE, IT) instead of multiple OR groups.
  • Value — typed by field kind. Closed-set fields (like lane) render a dropdown so you pick from the legal values; everything else takes a typed input. Values are literals, not field references — typing warehouse.country is matched as the literal string, not the warehouse's country. Use the lane field for that case.

20 action types covering shipping, customs and ops:

  • Shipping: SET_CARRIER (resolves to your first enabled account of that carrier when the user doesn't pick one), SET_SERVICE, SET_PACKAGE (uses one of your saved package presets — overrides dimensions + adds the empty box weight), SET_WEIGHT, SET_NEAREST_WAREHOUSE (postal-prefix scoring within the same country).
  • Risk: SET_INSURANCE, REQUIRE_SIGNATURE, SET_SIGNATURE_LEVEL (NONE / INDIRECT / DIRECT / ADULT), SET_HAZMAT (flips the dangerous-goods flag, mirrors to the order's hazmat panel).
  • International / customs: SET_INCOTERM (DAP / DDP), SET_BILL_SHIPPING_TO + SET_BILL_DUTIES_TO (sender / recipient / third-party with carrier account), SET_CONTENTS_TYPE (merchandise / gift / documents / sample / return), SET_NON_DELIVERY (return / abandon), SET_SIGNER_NAME (commercial-invoice signer), SET_HS_CODE + SET_ORIGIN_COUNTRY (apply uniformly across all line items — for per-SKU overrides, set Product.hsCode / Product.originCountry in the catalog instead, the customs builder reads them automatically), SET_PAPERLESS (electronic commercial invoice).
  • Workflow: ADD_TAG, REMOVE_TAG, SET_STATUS, SET_NOTES, NOTIFY (email / Slack / webhook).

Priority chain at label-buy time: your explicit input on the rate-shop panel → per-order customs form editsrule hintcatalog default (Product.hsCode etc.) → org / adapter default. So a rule never silently overrides a value you typed by hand on a specific order.

  • ORDER_IMPORTED — fires on every channel sync + manual create + headless API push. The most common trigger.
  • ORDER_UPDATED — every update (status, tags, items, addresses).
  • STATUS_CHANGED — fires only when the status actually transitions.
  • TAG_ADDED — only on orders that gained the tag. Use this with ADD_TAG on a different rule for chained workflows (e.g. tag "needs-review" on big orders → another rule fires on TAG_ADDED to NOTIFY).
  • MANUAL — "Apply to existing orders" button + the legacy "Run now" on order detail. Bypasses the dedupe gate so you can re-fire on an order that already matched.

Replay-safe: a second ORDER_UPDATED for the same order skips rules that already fired via a prior matched audit row. Per-rule isolation: a broken rule doesn't take down the rest — its run is wrapped, the state snapshot is reverted, and an audit row records the failure visible in /audit.

Default behaviour for a new rule: it only fires on orders going forward. Existing open orders sit unchanged — use the Apply to existing orders button to backfill.

Real automations from real merchants. Copy the structure, swap the carrier / country / tag for yours.

1 · Default carrier per lane

Your most important rule — you ship the same way for the vast majority of orders. Encode it once, free your attention for the exceptions.

Trigger:    On order imported
When:       lane equals Domestic
Then:       Assign carrier   = USPS
            Assign service   = usps_priority
Trigger:    On order imported
When:       lane equals Intra-EU (no customs)
Then:       Assign carrier   = DHL
            Assign service   = N
Trigger:    On order imported
When:       lane equals International
Then:       Assign carrier   = UPS
            Assign service   = 65   (UPS Worldwide Saver)

2 · Customs signer for every international invoice

Without this you have to type the signer on every international order's customs form. Set once, never again.

Trigger:    On order imported
When:       lane not equals Domestic
Then:       Customs signer name = Veronica Isaac

For the actual handwritten signature image (DHL paperless trade, FedEx ETD), upload it once at /settings/customs — that's org-wide, not per-rule.

3 · Insurance for high-value orders

Trigger:    On order imported
When:       totalCents greater than 50000   (>$500 / €500)
Then:       Insurance         = true
            Signature required = true
            Signature level   = ADULT

Stack signature + insurance on the same rule — both hints land on the order, both fire at label buy. Adult signature is overkill for a $20 t-shirt; reserve it for the actually-valuable shipments.

4 · Route to the nearest warehouse

Multi-warehouse merchants: pick the closest warehouse to the customer automatically. Postal-prefix scoring within the same country (GB postcodes share 2-4 chars, US ZIPs 1-3, etc).

Trigger:    On order imported
Then:       Route to nearest warehouse

5 · DDP for UK orders (avoid surprise duty bills)

Post-Brexit, EU → GB ships need a clear duties story. DDP means you pay the duties up-front; the customer never gets a delivery surprise. Pair with Bill duties to + your own carrier account (Shipnest blocks DDP on platform carrier accounts to prevent duty-bill leakage).

Trigger:    On order imported
When:       destination.country equals GB
Then:       Set incoterm     = DDP
            Bill duties to   = SENDER
                              account = <your-UPS-account-#>

6 · Hazmat on battery SKUs

Lithium batteries need the hazmat flag for FedEx DG / UPS HazMat / DHL DGD compliance. Tag your battery SKUs once in the catalog, then this rule trips the flag on every affected order.

Trigger:    On order imported
When:       sku contains BATT
Then:       Hazmat / dangerous goods = true
            Add tag = hazmat

7 · Notify on big orders

Slack ping for the warehouse manager when a high-value order lands so it gets handled by the senior packer.

Trigger:    On order imported
When:       totalCents greater than 100000   (>$1,000)
Then:       Add tag = vip
            Notify  channel = slack
                    target  = https://hooks.slack.com/services/…
                    message = High-value order {{orderNumber}} in queue ({{totalFormatted}})

8 · Stuck orders → on hold + alert

Catch orders that have been sitting too long (typically indicates a sync issue, missing inventory or address problem).

Trigger:    On order updated
When:       orderAgeHours greater than 48
            AND status equals AWAITING_SHIPMENT
Then:       Set status = ON_HOLD
            Add tag    = needs-review
            Notify     channel = email
                       target  = ops@example.com

9 · Standard package preset

Most of your shipments fit one of 2-3 box sizes. Encode the "default box" once and stop typing dimensions per order. Stack with a SKU condition for size-specific boxes.

Trigger:    On order imported
When:       weightG less than 1000
Then:       Set package preset = small-box   (your saved preset)

10 · Different signers for different shipping countries

Two rules with different priorities — the lower priority number runs first. Pair with Stop after this rule matches on each so they don't both fire.

Rule A — priority 50
Trigger: On order imported
When:    destination.country in ES, FR, IT, PT
Then:    Customs signer name = María García
         ☑ Stop after this rule matches

Rule B — priority 100
Trigger: On order imported
When:    destination.country in US, CA, MX
Then:    Customs signer name = John Smith
         ☑ Stop after this rule matches

By default a new rule only fires on orders coming after you save it (next channel sync, next webhook, next manual create). Existing open orders sit unchanged.

The Apply to existing button on the rule editor walks every open order in your workspace (AWAITING_SHIPMENT + ON_HOLD) and runs the rule against each. Shipped, delivered and cancelled orders are explicitly skipped — they're snapshots, the label is on the wire.

  • Confirm dialog shows the open-order count before running so you see the blast radius.
  • Processed in batches of 100. Per-order failures are counted but don't abort the sweep.
  • A rule.backfill_run audit row records the totals (processed / matched / errors).

The rule must be saved + enabled for the button to work — disabled rules don't fire on backfill either, intentionally.

The Test button on the rule editor opens a dry-run dialog. Type an order ID or order number (e.g. 1003 or #1003 — both work) and click Run.

You get back:

  • A green / red banner: matched or did not match.
  • The list of actions that would run (badge per action type) if the rule fired.
  • A per-condition trace table — every predicate's expected vs actual value, pass / fail. So when a rule doesn't match, you see which condition failed and what the actual value was on that order.

Test is read-only — no audit, no NOTIFY, no changes to the order. The rule doesn't even need to be saved yet — edit conditions / actions in place and hit Test repeatedly while you iterate.

Carriers & rates

/carriers manages your carrier accounts. Connect a new one (Connect dialog has per-carrier field specs and a test-connection step), edit the markup policy, toggle enabled, rotate the webhook secret.

Six carrier integrations: UPS, FedEx, DHL, USPS, Royal Mail, EasyPost. UPS + USPS support one-click OAuth install when the operator has wired the env vars (zero fields for the tenant to paste). The rest take a traditional credentials paste.

Each carrier account has a source:

  • PLATFORM — the operator's (Shipnest) corporate UPS / FedEx accounts offered to tenants at markup. Visible in the rate-shop panel with a pink Platform badge + shield icon. Subtitle shows base $X · fee $Y for transparency.
  • CUSTOMER — the tenant's own carrier contracts. No badge, no fee — passes through at the carrier's native price.

When the operator wires the platform env vars (UPS_* / FEDEX_* + account numbers) on Railway, every new signup auto-gets PLATFORM CarrierAccount rows. Existing orgs pick them up via the one-click backfill at /admin/carriers.

DDP on PLATFORM is blocked
International shipments with billDutiesTo: SENDER or incoterms: DDP are rejected at buy-time on PLATFORM accounts — duty bills would land on the operator's account weeks after delivery with no path to invoice the tenant back. Switch to DAP (recipient pays at customs), use third-party billing with the tenant's own account number, or connect your own carrier on /carriers and route this order to it.

Default markup applies to PLATFORM rates only: PLATFORM_MARKUP_BPS=500 (5%) + PLATFORM_MARKUP_FLAT_CENTS=50 ($0.50 fixed). Per-org override on Organization.markupBps + markupFlatCents, edited from /carriers.

CUSTOMER-source accounts pass through at cost — operators cannot mark up rates from a contract they don't own.

/rate-cards is for mid-market merchants with their own UPS / FedEx contracts at 35–45% off public rates. Upload a flat CSV (to_country, weight_to_g, price_cents) per card and the contract price surfaces in rate-shop alongside live public quotes — tagged Contract.

Informational only. Labels still buy through a connected CarrierAccount. The card's purpose is to show the gap between your real cost and the public published rate so margin is visible across the dashboard.

Channels

/integrations manages connected sales channels. Eight supported: Shopify, Amazon, Etsy, eBay, WooCommerce, BigCommerce, Squarespace, Magento.

One-click OAuth install for the five that support it: Shopify, Amazon, Etsy, eBay, WooCommerce. The tenant clicks Connect, approves on the channel's site, and the redirect-back persists encrypted tokens with no field-paste in between.

Connected store row shows: channel + display name + shop identity (the actual creds.shop / siteUrl / storeHash) + Connected / Auth-failed badge + last synced timestamp. Inline buttons: Sync now (manual pull), Test (re-test connection, auto-re-enable on success), Disable, Delete.

Advanced menu consolidates secondary controls: Rename, Rotate webhook secret, Inspect webhooks (Shopify only — GET /webhooks.json with mismatch detection), Re- register webhooks (Shopify), Enable/Disable checkout rates (Shopify Carrier Service toggle).

The most-integrated channel:

  • One-click OAuth install with the operator's Partner App. Token rotation enabled — Shipnest handles the 24h refresh-token cycle automatically.
  • Webhooks registered at install for orders/create, orders/updated, orders/paid, orders/cancelled, refunds/create. HMAC verified per-request against the app client secret.
  • Fulfillment uses the modernfulfillment_orders flow (API 2023-10+). Tracking numbers post back to the storefront on label buy.
  • Migrate tokens button on the row appears when a store still holds a legacy non-expiring offline token (Shopify deprecated these in 2025). One click re-runs OAuth in place — order history + rules + webhooks all stay linked.

Each channel has its own connect flow tuned to the platform's API quirks. Order pulls run on the/api/cron/sync cron (every 10 min) plus real-time webhooks where supported. Stock sync flows outbound on every label buy.

  • Amazon — SP-API one-click install. Marketplaces auto-detected from the refresh token; no picker. Fulfillment via Amazon Feeds API.
  • Etsy — OAuth 2.0 PKCE, zero fields. Receipts pulled with delta filter (min_last_modified) so a stale backlog doesn't burn rate budget.
  • eBay — OAuth auth-code, 18-month refresh token. Username derived via commerce.identity.
  • WooCommerce — "auto-auth" flow: tenant pastes site URL, gets redirected to wp-admin, approves there, keys come back to us via callback.
  • BigCommerce / Squarespace / Magento — manual paste of API credentials. Webhook signing supported where the platform offers it.

When toggled on (Advanced → Enable checkout rates), your rates appear natively inside Shopify checkout as shipping options — not post-facto in /orders. The /api/shopify/carrier-service callback runs rate-shop on every checkout rate request and returns the winning quotes.

Opt-in per store. Merchants with existing Shopify Shipping profiles aren't surprised by new options appearing. The state is persisted on Store.shopifyCarrierServiceId; Disable issues the DELETE on Shopify's side.

Operations

Warehouses

/warehouses

/warehouses manages ship-from locations. Multi-warehouse merchants use the SET_NEAREST_WAREHOUSE rule action to auto-route orders by destination postal prefix (same-country match required, longest shared prefix wins). Per-warehouse PrintNode printer routing ensures the label prints at the right dock.

/products is the catalog: SKU + name + weight + dimensions + HS code + origin country + stock level + reorder point. Build bundles at /products/bundles (one virtual SKU mapped to N parts).

HS code + origin country auto-fill on customs. When you set hsCode and originCountry on a product, every future international order containing that SKU gets those values pre-filled on its commercial invoice — no per- order customs form needed. Bulk-edit via the toolbar (Set HS code / Set origin) to backfill an existing catalog in a few minutes. Priority at label-buy: per-order customs form edit → SET_HS_CODE rule → Product.hsCode → undefined.

Catalog sync (opt-in per store at /products/catalog-sync) pulls the channel's catalog into Prisma daily. Off by default because it overwrites local edits.

/inventory shows stock movements: every label buy decrements local stock and best-effort pushes the new level to the channel (Shopify, Woo, BC, Etsy, eBay, Amazon).

/customers lists every distinct customer across channels (deduplicated by email). Lifetime spend per currency, last active date, tags. Click into a customer for their full order history.

The Addresses tab on the same page is your saved-address list — warehouse partners, repeat buyers, drop-ship vendors. Used as quick-pick on manual order create + return-from-sender.

/pickups schedules carrier pickups: pick a date + time window + carrier, the request is submitted to the carrier API. /manifests generates end-of-day manifests per carrier with one row per shipment (the "Only handed-over shipments" checkbox defaults on so the manifest reflects what actually left, not what was just printed).

CSV imports

/imports

/imports bulk-imports orders, products, or address-book entries from CSV. ShipStation preset auto- maps columns and converts oz → g + decimal $ → cents so you can drop in a ShipStation export without manual field-mapping.

Bulk import for products supports SKU + name + weight + dimensions + HS code + origin in one go.

Post-sale

/returns manages return RMAs./returns/portal is a public, branded self-serve portal: customer enters order number + email, picks items to return, gets a return label by email. Set the policy at /returns/policy.

Lifecycle states: REQUESTED → APPROVED → LABEL_ISSUED → IN_TRANSIT → RECEIVED → REFUNDED (or DENIED). Each transition fires a return.* outbound webhook for downstream automation.

/claims is the claim filing UI. From the shipment detail, click File a claim — pre-fills shipmentId + carrier + currency. Toggle file against third-party policy when the shipment has an active Shipsurance / XCover policy and the claim should go there instead of the carrier.

Lifecycle: DRAFT → SUBMITTED → UNDER_REVIEW → APPROVED / DENIED → PAID → CLOSED. Each transition lands in the notifications bell + outbound webhook.

/track/<tracking-number> is a public tracking page styled with your logo + brand colour + support email + storefront URL. The "Powered by Shipnest" credit in the footer is non-removable; everything else swaps to your identity.

When the shipment's associated return has been REFUNDED with a non-zero refund amount, an emerald "Refund processed" panel appears with the amount + date + a 5-10 business day card-settlement note.

/reports/voids-and-refunds aggregates three sources: successful voids (carrier refunds full label cost), past-window void rejections (you tried but the carrier's refund deadline elapsed), and customer refunds on returned orders. CSV export available.

Per-carrier deadlines: UPS 90d, FedEx 60d, DHL 28d, USPS 28d, Royal Mail 14d, EasyPost 30d. The shipment detail greys out the Void button + tooltip when past-window.

International

International orders get a Customs form on the order detail. Fields: contents type (merchandise / gift / documents / return / sample), non-delivery option (return / abandon), declared currency (defaults to order currency), per-line items with HS code + origin country + value + weight, optional EEL / PFC for US-origin exports, paperless trade toggle and signer name.

Form edits persist to Order.customFields._customs and the buy-label flow merges them with rule hints + org-level customs profile. Adapters forward to DHL electronic invoice, FedEx ETD, UPS InternationalForms.

International tax registration numbers configured at /settings/customs: IOSS (low-value EU imports under €150), GB EORI, NI EORI, EU EORI, GB VAT.

At label-buy time, resolveTaxIds picks the right number per lane:

  • IOSS — non-EU origin → EU destination, value ≤ €150. Stamped on the invoice so VAT was collected at checkout; customs clears without buyer-side charge.
  • EORI — GB-origin → GB EORI; EU-origin → EU EORI.
  • VAT — GB-origin → GB VAT.

The resolver is B2C-shaped (DDP duty accounts and B2B VAT reverse-charge stay operator-driven via per-order overrides).

Run a US Shopify + UK Shopify + EU Etsy on one Shipnest org — every order keeps its native currency from the channel. Dashboards stack $2,000 · £900 · €800 side by side rather than collapsing to a fake FX-converted single number.

The defaultCurrency in /settings is only the fallback for manual orders without explicit currency and for the product catalog. It does NOT override the channel-set currency of synced orders.

Reports

Analytics

/analytics

/analytics is the deeper dashboard:

  • Spend (30d) per currency stack, with week-over-week delta.
  • Avg cost / label per currency.
  • Delivered ≤ 5d rate.
  • CO₂e (30d) aggregate (less = better, delta semantics flipped).
  • Carrier performance table — shipments + spend + delivered rate + avg days + claims per carrier.
  • Channel volume share + per-channel spend.
  • Top lanes — country-pair routes ranked by volume, with avg cost + total spend per currency.
  • Heatmap — orders by hour-of-day × day-of-week.

/reports manages CSV reports emailed on schedule (daily / weekly / monthly). Pick the kind: orders, shipments, voids-and-refunds, marketing-mix, custom carrier breakdown. Set recipients, send time and cadence; /api/cron/reports generates and sends.

Ad-hoc one-shot exports available at /api/reports/...csv for CSV download without scheduling.

Audit log

/audit

/audit is the full log of every mutation inside your org: label buys, voids, rule firings, store connects, webhook rotations, role changes. Filter by actor, action, entity. CSV export.

Rule audit rows include rule.matched (which rule fired against which order), rule.failed (rule threw — message + name aggregated in the notification bell), and rule.hint_overwritten when two rules target the same hint.

Every shipment gets a CO₂e estimate based on weight × transport mode (road ~100 g/kg, air ~600 g/kg, calibrated to DEFRA 2023 parcel-delivery factors). Aggregate on/analytics; column on /shipments (hidden by default — reveal via column chooser).

Mode classifier reads carrier + service name + lane. DHL is always classed as air. International cross-border defaults to air unless the service name explicitly says ground.

Settings

Org & team

/settings

/settings is the org admin surface. Under the Profile card: organization name, slug (used in public URLs like /returns/portal?org=<slug>), default currency, weight unit, dimension unit.

Team subsection invites Owner / Admin / Manager / Member / Viewer roles. Role rank: Owner > Admin > Manager > Member > Viewer. RBAC gates every mutation server-side.

Branding

/settings/branding

/settings/branding swaps the default Shipnest chrome on public surfaces (/track, /returns/portal) for your identity: logo, brand colour (drives the gradient + accent), support email (lands in the public footer), storefront URL (becomes the "Back to {store}" button). Live preview before save.

Security & 2FA

/settings/security

/settings/security enrols TOTP for the tenant's users. Required for any user with isPlatformAdmin = true (admin console gate). Optional for tenant users, but encouraged.

Privileged actions (buy label, void label, change markup, delete account) require role gates server-side regardless of TOTP state.

API keys

/settings/api

/settings/api issues sk_live_* keys for the public REST API. Show-once secret on creation; tenants store it themselves. Revoke any time — the key's SHA-256 hash is what we persist (never the plaintext), so a leaked key doesn't leak past its first authenticated call.

See /docs for the full REST API reference: rate-shop, label buy, void, list shipments, list orders, tracking, address validation, insurance quote, plus the carrier-accounts discovery endpoint.

Outbound webhooks

/settings/webhooks

/settings/webhooks registers HTTPS endpoints for Shipnest to POST events into. Subscribe per-event: shipment.label_purchased, shipment.voided, shipment.delivered, return.created, return.refunded, claim.created, claim.paid.

Every delivery is HMAC-SHA256 signed with the webhook's secret over a timestamp-prefixed body (timestamp.body). Verify on your side by recomputing the HMAC against the raw bytes — replay- safe because the timestamp is part of the signature input.

/settings/webhooks/deliveries is the per-delivery audit trail with retry button on failed attempts.

Printers (PrintNode)

/settings/printers

/settings/printers connects a PrintNode account so labels skip the browser print dialog and go straight to your warehouse Zebra / Dymo. Multi-warehouse orgs route per-warehouse so dock A's Zebra and dock B's Zebra both work. One-click retry on failed jobs.

Without PrintNode, labels open in a new browser tab (default). The integration is strictly additive — turning it off doesn't regress any flow.

Billing

/settings/billing

/settings/billing manages your subscription: current plan, usage meters (labels this month, users, warehouses), upgrade / downgrade via Stripe Customer Portal.

Five plans: Starter (free, 100 labels/mo) → Growth ($39, 1,000 labels) → Pro ($69, 3,000 labels, multi-warehouse, API) → Scale ($99, 5,000 labels, unlimited users) → Enterprise (custom). 14-day free trial on every paid tier.

Past-due payments show an amber banner during the 14-day grace window. After 14 days unpaid, label-buy is locked until you update your card. Day-3 / day-7 / day-14 email reminders fire automatically.

Operator (admin)

/admin is the operator-only surface (gated by User.isPlatformAdmin). TOTP required on every sign-in.

  • /admin/organizations — list every tenant org with Healthy / At risk / Critical badge derived from churn signals (sync failures, webhook failure rate, no shipments in 14d, billing past-due, label cap near, missing default warehouse).
  • /admin/users — every user across orgs.
  • /admin/stores — store health roll-up.
  • /admin/carriers — every carrier account across orgs + the platform-rates backfill button.
  • /admin/print-jobs — global PrintNode job feed.
  • /admin/setup-guide — step-by-step OAuth credential recipes per integration (Shopify, Amazon, eBay, Etsy, UPS, FedEx, USPS, Stripe, Shipsurance, XCover).
  • /admin/legal — registered legal identity, doc versions, compliance check.
  • /admin/impersonation — assume any org for support without being a member; banner renders across the app while the cookie is set.

Platform settings

/admin/settings

/admin/settings is the read-only env-var dashboard. Shows which secrets are wired (masked) and groups them by purpose: Platform rate source (monetisation), Email, Store OAuth, Other carrier OAuth, Sentry, Insurance, Address verification, Stripe billing, Webhook signing.

Billing health

/admin/billing

/admin/billing aggregates plan state across orgs: MRR, plan distribution, status distribution (active / trialing / past-due / canceled), renewing-in- 7-days list, plan bars with per-plan MRR, "Needs attention" (past-due + incomplete + canceled) and "Cap risk" (orgs ≥ 80% label usage).

Cron heartbeats

/admin/crons

/admin/crons shows the last successful run of each cron (sync, tracking, reports, catalog-sync, demo-reset, dunning) with status + duration + age. Read from CronHeartbeat rows written by thewithCronHeartbeat wrapper around each cron endpoint.

Public /status page surfaces the same data for monitoring tools — overall ok / degraded / down based on DB latency + 15-min error floor + cron freshness.