Connect Retool to AfterShip using a REST API Resource with API key authentication. Configure the base URL and API key in the Resources tab, then build queries to track shipments across 1,100+ carriers in a single unified Table. The setup takes about 15 minutes and gives your customer service and operations teams a universal tracking dashboard.
| Fact | Value |
|---|---|
| Tool | AfterShip |
| Category | Other |
| Method | REST API Resource |
| Difficulty | Beginner |
| Time required | 15 minutes |
| Last updated | April 2026 |
Why Connect Retool to AfterShip?
E-commerce and logistics operations teams spend significant time looking up package statuses across multiple carrier websites — FedEx, UPS, DHL, USPS, and dozens of regional carriers depending on the markets served. AfterShip solves this by aggregating tracking data from 1,100+ carriers into a single, consistent API. Connecting Retool to AfterShip lets you build a unified shipment tracking dashboard that shows all your shipments regardless of carrier, with standardized status labels, estimated delivery dates, and checkpoint histories — all in one interface your team can search, filter, and act on.
AfterShip's API is particularly valuable for customer service teams who handle 'where is my order?' inquiries. Rather than asking customers to check carrier websites or navigating between multiple carrier tracking portals, a Retool app powered by AfterShip gives customer service agents instant access to any shipment by order number or tracking number, with the full delivery timeline and latest checkpoint in a consistent format. Combined with your internal order management database, the Retool app can show the customer record alongside the tracking data — removing the need to switch between systems during a support call.
AfterShip also provides proactive shipment intelligence: the platform detects delayed shipments (those past their estimated delivery date without a delivery confirmation) and exception shipments (those with carrier alerts like failed delivery attempts or customs holds). A Retool Workflow can query AfterShip's API on a schedule, identify these problem shipments, and push alerts to Slack or trigger outreach workflows — turning reactive customer complaint handling into proactive delivery management.
Integration method
Retool connects to AfterShip through a REST API Resource configured in the Resources tab with API key header authentication. You set the base URL and API key once, and all subsequent queries are built visually using the endpoint path, HTTP method, and parameter fields. Retool proxies all requests server-side through its backend, keeping the API key off the browser. JavaScript transformers reshape AfterShip's nested tracking JSON into flat data structures suitable for Retool Table and Chart components.
Prerequisites
- A Retool account (Cloud or self-hosted) with permission to add Resources
- An AfterShip account with API access (API keys are available on all paid plans; the free plan has limited API calls)
- An AfterShip API key (created in AfterShip Admin → Settings → API → Create API Key)
- Basic familiarity with the Retool app builder and query editor
Step-by-step guide
Add an AfterShip REST API Resource
Navigate to the Resources tab in Retool — click Resources in the left sidebar on the home page or use the top navigation. Click the blue Add Resource button in the upper right corner. In the resource type selector, search for 'REST' and click REST API to open the configuration form. Fill in the resource configuration: - Name: Enter 'AfterShip API' to clearly identify this resource. - Base URL: Enter https://api.aftership.com/v4 — this is the base URL for AfterShip's v4 REST API (the current stable version as of 2026). For authentication, AfterShip uses an API key passed in a custom request header. In the Headers section, add: - Key: as-api-key - Value: {{ retoolContext.configVars.AFTERSHIP_API_KEY }} Also add: - Key: Content-Type - Value: application/json Before clicking Save, go to Settings → Configuration Variables in another tab. Click Add Variable, name it AFTERSHIP_API_KEY, paste your API key from AfterShip's settings page, and mark it as a secret. Return to the resource configuration and save. The AfterShip resource now appears in your Resources list. Unlike some APIs, AfterShip does not require OAuth token refresh or HMAC signing — the API key is a static credential that remains valid until you rotate it in AfterShip's settings.
Pro tip: Create separate AfterShip API keys for each environment (development, staging, production) in AfterShip's settings. Use descriptive key names like 'Retool Production Dashboard' so you can identify which key is being used and revoke it individually if needed without disrupting other integrations.
Expected result: An AfterShip REST API resource appears in your Resources list with the API key stored as a secret configuration variable. You can now select this resource in the query editor.
Query all trackings and build the main shipment table
Create your primary data query to fetch all trackings from AfterShip. In your Retool app, click + New in the query panel. Select your AfterShip API resource. Set the HTTP method to GET and the path to /trackings. AfterShip's trackings endpoint supports several URL parameters for filtering and pagination: - page: {{ (trackingsTable.pageIndex || 0) + 1 }} — AfterShip uses 1-based page numbers - limit: 100 — maximum results per page (AfterShip allows up to 100) - keyword: {{ searchInput.value || '' }} — search by tracking number, customer name, or order ID - tag: {{ statusFilter.value || '' }} — filter by AfterShip status tag: Pending, InTransit, OutForDelivery, AttemptFail, Delivered, Exception, Expired - courier_slug: {{ carrierFilter.value || '' }} — filter by carrier (e.g., 'ups', 'fedex', 'dhl') - created_at_min: {{ dateRange.start ? new Date(dateRange.start).toISOString() : '' }} - created_at_max: {{ dateRange.end ? new Date(dateRange.end).toISOString() : '' }} Set the query to Run automatically when inputs change and trigger On page load. Add a JavaScript transformer to reshape AfterShip's response. The response has the structure: data.trackings[] where each tracking has the slug, tracking_number, tag (status), title (customer name), shipment_status_message, estimated_delivery_date, last_updated_at, and a checkpoints array. Drag a Table component onto the canvas. Bind it to {{ trackingsQuery.data }}. Configure columns for: tracking number, carrier (slug), status (tag), last checkpoint message, estimated delivery date, and customer/order reference. Add Search, Status Select, and Carrier Select filter components above the Table.
1// JavaScript transformer for AfterShip trackings list2const trackings = data?.data?.trackings || [];34return trackings.map(t => {5 const lastCheckpoint = t.checkpoints?.[t.checkpoints.length - 1] || {};67 return {8 tracking_number: t.tracking_number,9 carrier: t.slug,10 status_tag: t.tag,11 status_message: t.shipment_status_message || lastCheckpoint.message || '',12 customer_name: t.title || t.customer_name || '',13 order_id: t.order_id || '',14 estimated_delivery: t.estimated_delivery_date15 ? new Date(t.estimated_delivery_date).toLocaleDateString()16 : 'Not available',17 last_updated: t.last_updated_at18 ? new Date(t.last_updated_at).toLocaleString()19 : '',20 checkpoint_count: (t.checkpoints || []).length,21 is_delayed: t.tag === 'Exception' || t.tag === 'AttemptFail'22 };23});Pro tip: AfterShip's 'tag' field uses PascalCase status values (InTransit, OutForDelivery, AttemptFail, Delivered, Exception). Use these exact values in the status filter Select component's options to ensure filters work correctly. Add a human-readable label mapping in the Select: { value: 'InTransit', label: 'In Transit' } etc.
Expected result: The Table shows all AfterShip trackings with carrier, status, estimated delivery, and last update. The search input and status/carrier filters control which trackings are displayed.
Add a tracking detail view with checkpoint history
When a customer service agent selects a shipment row, show the full checkpoint history in a detail panel. This gives agents the complete delivery timeline without navigating to AfterShip's interface. Create a second query using the AfterShip API resource. Set the method to GET and the path to /trackings/{{ trackingsTable.selectedRow.carrier }}/{{ trackingsTable.selectedRow.tracking_number }}. This endpoint returns the full tracking detail including all checkpoints. Set this query to trigger when the selected row changes (not on page load — only when a row is selected). In the query settings, set Trigger to Manual. In the Table's On Row Click event handler, add an action to Trigger this detail query. Drag a Container component to the right side of the canvas (or below the Table). Inside it, add: - A Text component showing the tracking number and carrier: 'Tracking: {{ trackingsTable.selectedRow.tracking_number }} via {{ trackingsTable.selectedRow.carrier }}' - A Timeline or List component for the checkpoints, bound to the transformer output of the detail query Add a JavaScript transformer on the detail query to format checkpoints for display: The checkpoint array includes: checkpoint_time (ISO datetime), country_iso3 (country code), city, state, zip, message, and tag (checkpoint status type). Format each checkpoint into an object with a formatted datetime, location string (city, state, country), and message. Drag a Table or JSON Editor component to display the formatted checkpoints. If using a Table, configure columns for: date/time, location, and event description. Sort checkpoints in reverse chronological order (most recent first) by reversing the array in the transformer.
1// JavaScript transformer for AfterShip tracking detail checkpoints2const tracking = data?.data?.tracking || {};3const checkpoints = tracking.checkpoints || [];45// Return checkpoints in reverse chronological order (latest first)6return [...checkpoints].reverse().map((cp, index) => ({7 index: index + 1,8 datetime: cp.checkpoint_time9 ? new Date(cp.checkpoint_time).toLocaleString()10 : 'N/A',11 location: [cp.city, cp.state, cp.country_iso3].filter(Boolean).join(', ') || 'N/A',12 message: cp.message || cp.tag || '',13 status_tag: cp.tag || ''14}));Pro tip: AfterShip checkpoints may have gaps in location data — some carriers report only country-level information, while others include full city and zip. Use .filter(Boolean) when building location strings to avoid empty commas in the display (the transformer above already does this).
Expected result: Clicking a shipment row in the main Table triggers the detail query and populates the checkpoint history list. The most recent checkpoint appears first, showing the datetime, location, and event message for each carrier scan.
Add carrier filter options and summary statistics
Enrich the dashboard with carrier breakdown statistics and a dynamically populated carrier filter. Create a query to fetch the list of active couriers in your AfterShip account: GET /couriers — this returns all carriers that have been added to your AfterShip account and have active trackings. Add a transformer to format the response into { value: courier.slug, label: courier.name } objects for the carrier Select filter component. For summary statistics, create a JavaScript query that processes the full trackings dataset (from the trackingsQuery.data array) to compute aggregate metrics: - Total trackings count - Count by status tag (InTransit, Delivered, Exception, etc.) - Count by carrier - Average time from creation to delivery (for delivered shipments) - Percentage delayed (Exception or AttemptFail tags) Drag Summary (Stat) components to the top of your dashboard canvas. Bind each Stat to the relevant field from the aggregates JavaScript query: - Total Shipments: {{ statsQuery.data.total }} - In Transit: {{ statsQuery.data.in_transit }} - Delayed / Exception: {{ statsQuery.data.exceptions }} - Delivered Today: {{ statsQuery.data.delivered_today }} Add a Bar Chart component below the stats using AfterShip data to show shipments by carrier — this gives logistics managers a quick overview of volume distribution across carriers, which is useful for carrier diversification decisions and contract negotiations.
1// JavaScript query: compute summary statistics from AfterShip trackings2const trackings = trackingsQuery.data || [];3const today = new Date().toDateString();45const stats = {6 total: trackings.length,7 in_transit: trackings.filter(t => t.status_tag === 'InTransit').length,8 out_for_delivery: trackings.filter(t => t.status_tag === 'OutForDelivery').length,9 delivered: trackings.filter(t => t.status_tag === 'Delivered').length,10 delivered_today: trackings.filter(t =>11 t.status_tag === 'Delivered' &&12 t.last_updated &&13 new Date(t.last_updated).toDateString() === today14 ).length,15 exceptions: trackings.filter(t =>16 t.status_tag === 'Exception' || t.status_tag === 'AttemptFail'17 ).length,18 pending: trackings.filter(t => t.status_tag === 'Pending').length19};2021stats.exception_rate = trackings.length > 022 ? ((stats.exceptions / trackings.length) * 100).toFixed(1) + '%'23 : '0%';2425return stats;Pro tip: The summary statistics are computed client-side from the loaded trackings dataset. If you have more trackings than the current page limit (100 per page), the stats will only reflect the loaded page. For accurate aggregate statistics across all trackings, create a Retool Workflow that fetches all pages and stores aggregated counts in your internal database for the dashboard to query.
Expected result: Summary Stat components at the top of the dashboard show real-time counts for total shipments, in-transit, exceptions, and delivered today. The carrier Select filter is populated with the real carrier names from your AfterShip account. The bar chart shows volume by carrier.
Create a Workflow for delayed shipment alerts
Set up automated monitoring for delayed and exception shipments using a Retool Workflow that runs on a schedule and alerts your logistics team via Slack when packages need attention. Navigate to Workflows in the Retool sidebar. Click New Workflow. Click Add Trigger → Schedule. Set the schedule to run twice daily: 9 AM and 2 PM in your operations timezone. This gives the team morning and afternoon visibility into shipment exceptions. Add the first block: a Resource Query block using the AfterShip API resource. Configure it as GET /trackings with URL parameters: tag=Exception&limit=100 to fetch all exception-status trackings. Add a second Resource Query block: GET /trackings with tag=AttemptFail&limit=100 for failed delivery attempts. Add a JavaScript Code block that combines the two result sets and deduplicates, then filters for trackings older than 24 hours that have not been flagged in your internal database (to avoid duplicate alerts). This requires a third Resource Query block querying your PostgreSQL database for already-flagged tracking numbers. Add a Branch block: if the filtered list has records, proceed to the Slack notification block. If empty, end the workflow (no alerts needed). For the Slack notification block, use your Slack resource (native connector). Set Action Type to Post Message and construct a message that lists each problematic shipment: tracking number, carrier, status, last checkpoint, and a deep link to AfterShip. For more than 5 affected shipments, use Slack Block Kit to format them as a structured list. Add a final Resource Query block that inserts the flagged tracking numbers into your PostgreSQL exceptions table with a timestamp, preventing duplicate alerts in the next Workflow run. Click Publish Release to activate the Workflow.
1// JavaScript Code block: filter and prepare exception shipments for alerting2const exceptionTrackings = exceptionQuery.data?.data?.trackings || [];3const attemptFailTrackings = attemptFailQuery.data?.data?.trackings || [];4const alreadyFlagged = new Set((flaggedQuery.data || []).map(r => r.tracking_number));56const allExceptions = [...exceptionTrackings, ...attemptFailTrackings];78// Deduplicate and filter out already-flagged trackings9const seen = new Set();10const toAlert = allExceptions.filter(t => {11 if (seen.has(t.tracking_number) || alreadyFlagged.has(t.tracking_number)) return false;12 seen.add(t.tracking_number);1314 // Only alert if the last update was more than 24 hours ago15 const lastUpdated = new Date(t.last_updated_at || t.created_at);16 const hoursSinceUpdate = (Date.now() - lastUpdated.getTime()) / (1000 * 60 * 60);17 return hoursSinceUpdate > 24;18});1920const alertMessage = toAlert.length > 021 ? `*${toAlert.length} shipment(s) need attention:*\n` +22 toAlert.slice(0, 10).map(t =>23 `• ${t.tracking_number} (${t.slug}): ${t.tag} — ${t.shipment_status_message || 'No message'}`24 ).join('\n') +25 (toAlert.length > 10 ? `\n_...and ${toAlert.length - 10} more_` : '')26 : null;2728return { toAlert, alertMessage, hasAlerts: toAlert.length > 0 };Pro tip: Store a flag in your PostgreSQL exceptions table when a shipment has been alerted (including the alert timestamp). In the Workflow's filter step, check this table before generating alerts. This prevents the same delayed shipment from generating a new alert every time the Workflow runs until the shipment status changes or a human resolves it.
Expected result: The Workflow runs twice daily, detects exception and failed-delivery shipments older than 24 hours, and posts a formatted Slack message listing affected tracking numbers. Flagged trackings are recorded in the internal database to prevent duplicate alerts.
Common use cases
Build a unified shipment tracking dashboard
Create a Retool panel that displays all active shipments across all carriers from AfterShip, filterable by status (in transit, delivered, delayed, exception, out for delivery), carrier, and date range. Operations teams can search by tracking number or customer name, click a shipment to see the full checkpoint history, and see aggregate metrics — percentage of on-time deliveries, delayed count, exception count — in Summary components at the top of the dashboard.
Build a shipment tracking dashboard that queries AfterShip for all trackings with pagination. Show tracking number, carrier, customer name, status, last checkpoint, and estimated delivery date in a Table. Add filters for status and carrier. Display summary stats at the top: total in transit, total delivered today, total delayed, and total exceptions.
Copy this prompt to try it in Retool
Create a customer service tracking lookup tool
Build a Retool customer service tool that lets agents search for shipments by tracking number or customer order ID. The tool shows the full checkpoint history in a Timeline component, the carrier's contact details for escalation, and joins AfterShip data with the internal orders database to display customer name, order value, and order date alongside tracking information. Agents can see at a glance if a package is delayed or has an exception requiring carrier contact.
Create a tracking lookup panel with a search input for tracking number or order ID that queries AfterShip for the matching shipment. Show the full checkpoint history as a vertical timeline with dates, locations, and status messages. Join with the internal PostgreSQL orders table on tracking number to show customer name, order date, and order value alongside the tracking data.
Copy this prompt to try it in Retool
Monitor shipment exceptions and trigger proactive alerts
Build a Retool Workflow that runs daily and queries AfterShip for shipments with 'exception' or 'failed_attempt' status that are more than 24 hours old. For each flagged shipment, the Workflow sends a Slack alert to the logistics team channel with the tracking number, carrier, exception description, and customer order details. This proactive monitoring catches delivery failures before customers report them, enabling the team to initiate carrier claims or arrange reshipment.
Build a Retool Workflow with a daily schedule trigger that queries AfterShip for all exception-status shipments older than 24 hours. For each result, post a Slack message to #logistics-alerts with the tracking number, carrier, exception type, and a link to the shipment in AfterShip. Also insert a record into a PostgreSQL exceptions table for tracking resolution.
Copy this prompt to try it in Retool
Troubleshooting
All API requests return 401 Unauthorized or 'Invalid API key' response
Cause: The AfterShip API key is not being passed in the correct header. AfterShip requires the key in an as-api-key header (not Authorization: Bearer or X-API-Key). If the header name is incorrect, even a valid API key will be rejected.
Solution: In the Retool Resources tab, click your AfterShip resource and verify the Headers section contains a header with key 'as-api-key' (exactly, including the hyphens). The value should reference your configuration variable: {{ retoolContext.configVars.AFTERSHIP_API_KEY }}. Also verify the API key in AfterShip Admin → Settings → API to confirm it has not been revoked or expired.
Trackings query returns an empty array even though shipments exist in AfterShip
Cause: The query may include filter parameters that exclude all results. A common issue is the keyword filter — if the search input component has a default value or placeholder text that is being passed as the keyword parameter, AfterShip filters for that text and returns no matches.
Solution: Remove all filter parameters from the query temporarily and run it without filters to confirm the endpoint is working. Then add filters back one at a time. Check that the keyword URL parameter is set to {{ searchInput.value || '' }} (not {{ searchInput.value }}) so an empty search returns all results rather than filtering by an empty string. Also verify that the status tag filter values use AfterShip's exact tag names (InTransit, not in_transit or IN_TRANSIT).
Tracking detail query returns 404 Not Found for the selected row
Cause: AfterShip's tracking detail endpoint path is /trackings/{slug}/{tracking_number} where slug is the carrier's slug identifier (e.g., 'ups', 'fedex', 'dhl-express'). If the slug field in your transformer maps incorrectly from the list response, or if the tracking_number contains special characters that need URL encoding, the path will be invalid.
Solution: Verify that the transformer's carrier field maps to t.slug (not t.courier or t.carrier_code). AfterShip uses short slug strings for carriers — check the AfterShip couriers documentation for the correct slug for each carrier. For tracking numbers with special characters (spaces, slashes), wrap the value in encodeURIComponent(): /trackings/{{ encodeURIComponent(trackingsTable.selectedRow.carrier) }}/{{ encodeURIComponent(trackingsTable.selectedRow.tracking_number) }}.
The estimated_delivery_date field is null for most trackings
Cause: AfterShip provides estimated delivery dates only for carriers and tracking numbers where the carrier's API returns ETA data. Many carriers do not provide estimated delivery date information in their tracking APIs, so this field is legitimately null for a significant portion of trackings.
Solution: Handle null estimated delivery dates gracefully in your transformer and Table column configuration. Display 'Not available' (as in the example transformer above) rather than showing null or empty cells. In the Table's column settings, set an empty state message for this column. For carriers that do provide ETAs, AfterShip may display the date in the checkpoints rather than at the tracking level — check the latest checkpoint with a 'forecasted_delivery_date' field.
Best practices
- Store your AfterShip API key as a secret configuration variable in Retool Settings — while tracking data is less sensitive than payment data, the API key controls your shipment management operations and should be protected.
- Use AfterShip's tag (status) field rather than trying to parse status messages for filtering — the tag field has a fixed set of values (InTransit, Delivered, Exception, etc.) that are reliable across all carriers, while status messages vary widely by carrier.
- Implement pagination in your trackings query using AfterShip's page and limit parameters — businesses with thousands of shipments cannot load them all at once, and AfterShip limits each response to 100 records.
- Cache the couriers list query aggressively (hourly TTL) since the carriers in your AfterShip account change rarely, reducing unnecessary API calls when the dashboard is refreshed frequently.
- Build the shipment detail view as a lazy-loaded panel (triggered by row click, not on page load) — fetching full checkpoint data for every tracking in the list would generate excessive API calls and slow the dashboard significantly.
- Use AfterShip's webhook feature (configured in AfterShip Admin → Notifications → Webhooks) alongside the Retool Workflow webhook trigger to receive real-time tracking updates rather than polling for status changes, improving operational responsiveness.
- Add a 'Days since last update' calculated field in your transformer to flag stagnant shipments (e.g., no checkpoint for more than 5 days while still in InTransit status), even for carriers that do not trigger AfterShip's Exception tag.
- Combine AfterShip tracking data with your internal orders database using the order_id field to show customer details alongside tracking information, giving customer service agents full context without switching between systems.
Alternatives
Choose ShipStation if you need multi-carrier label creation and order management in addition to tracking — ShipStation handles the full shipment lifecycle, while AfterShip focuses specifically on tracking aggregation.
Choose UPS API directly if you ship exclusively or primarily with UPS and need carrier-specific features like address validation, rate shopping, and label creation rather than multi-carrier tracking aggregation.
Choose FedEx API directly if you need FedEx-specific features like Freight, Custom Critical, or advanced service level selection, and do not need unified tracking across multiple carriers.
Frequently asked questions
Does AfterShip have a native Retool connector or does it require a REST API Resource?
AfterShip does not have a native Retool connector. You connect using a REST API Resource configured with AfterShip's as-api-key header authentication. The setup is straightforward — no OAuth flow or HMAC signing required — and AfterShip's consistent API response format makes it easy to build reliable Retool apps with predictable data structures across all 1,100+ supported carriers.
How does AfterShip normalize tracking data from different carriers for display in Retool?
AfterShip maps carrier-specific tracking events and statuses to a standardized set of tags: Pending, InfoReceived, InTransit, OutForDelivery, AttemptFail, Delivered, AvailableForPickup, Exception, and Expired. This normalization means you can filter and sort all shipments by a consistent status field regardless of whether the shipment is with UPS, DHL, or a regional Asian carrier — critical for building a unified dashboard in Retool that works across all your shipping carriers.
Can Retool receive AfterShip webhook notifications for real-time tracking updates?
Yes. Create a Retool Workflow with a Webhook trigger to receive AfterShip notifications. The Workflow provides a public HTTPS endpoint that you configure in AfterShip Admin → Notifications → Webhooks. AfterShip sends a POST request with tracking update data whenever a shipment status changes. The Workflow can validate the payload, update your internal database, send Slack alerts for exceptions, or trigger customer notification emails — enabling real-time response to delivery events without polling.
What is the rate limit for AfterShip's API and how should I handle it in Retool?
AfterShip rate limits API requests based on your plan tier — typically ranging from 10 to 600 requests per second depending on your subscription. For Retool dashboards used by many team members simultaneously, implement caching (query-level caching with a 1-5 minute TTL) to reduce API calls. For high-volume operations, use AfterShip's webhook integration rather than polling the trackings list endpoint repeatedly, which significantly reduces API call volume.
How do I add a new shipment tracking to AfterShip from Retool?
Create a POST query using your AfterShip REST API Resource targeting the /trackings endpoint. The request body requires the tracking_number and slug (carrier identifier — find the slug for each carrier in AfterShip's courier list at aftership.com/couriers). Optionally include title (customer name or order reference), order_id, and customer_name fields. Add a small form in your Retool app with TextInput components for tracking number and carrier Select, and a Submit button that triggers the POST query. AfterShip begins monitoring the shipment immediately upon successful creation.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation