Tray.ai (rebranded from Tray.io in 2024) is a cloud-native iPaaS built on the Universal Automation Cloud (UAC) architecture. It targets technical teams that need full control over integration logic while also enabling business technologists through low-code and AI-assisted build experiences. Unlike MuleSoft's API-led connectivity model or Workato's recipe-centric approach, Tray takes a workflow-first approach with deep support for branching, looping, conditional logic, and parallel execution.
The platform operates across three tiers: Pro (entry-level), Team (mid-market), and Enterprise (full governance). All tiers share the same runtime engine but differ in task credits, workspace count, log retention, and governance controls. Tray does NOT offer an on-premise deployment option — it is cloud-only, hosted on AWS.
| Property | Value |
|---|---|
| Vendor | Tray.ai (formerly Tray.io) |
| System | Universal Automation Cloud (UAC) 2025 |
| API Surface | REST, GraphQL (Embedded API), Webhook triggers, Connector SDK (TypeScript) |
| Current Platform Version | UAC 2025 (continuous release) |
| Editions Covered | Pro, Team, Enterprise |
| Deployment | Cloud (AWS-hosted, multi-tenant) |
| API Docs | Tray.ai Documentation |
| Status | GA |
Tray.ai exposes multiple API surfaces for building integrations, managing workflows programmatically, and embedding integration capabilities into external products.
| API Surface | Protocol | Best For | Key Capability | Rate Limit | Real-time? | Bulk? |
|---|---|---|---|---|---|---|
| Workflow REST Triggers | HTTPS/JSON Webhook | Inbound event-driven integrations | Trigger workflows from external systems | 429 at high volume | Yes | No |
| Tray Code APIs (Connectivity, Trigger, Auth) | HTTPS/JSON | Programmatic connector access | Single interface to 700+ apps | 30 req/s | Yes | No |
| GraphQL Embedded API | HTTPS/GraphQL | White-label integration UX | Manage workflows, auth, users programmatically | 30 req/s | Yes | No |
| Call Connector API | HTTPS/JSON | Direct connector operation calls | Execute connector ops from external code | 1,000 concurrent | Yes | No |
| HTTP Client (Universal) | HTTPS/JSON/XML | Any REST/SOAP API without native connector | 15-min timeout, custom auth | Per-target limits | Yes | Yes |
| Connector Builder (CDK) | TypeScript SDK | Custom connectors for unsupported systems | Import OpenAPI specs or build from scratch | N/A (build-time) | N/A | N/A |
| CSV/File Connectors | HTTPS/CSV/XML | Bulk file-based data movement | Up to 1 GB CSV processing | 60-min timeout | No | Yes |
| Limit Type | Value | Applies To | Notes |
|---|---|---|---|
| Data between workflow steps | 6 MB | All connectors | Chunk larger payloads using Data Storage or file connectors |
| Webhook trigger payload | 1 MB | Inbound webhooks | 10 MB for multipart file uploads |
| Trigger event reply body | 1 MB | Synchronous webhook responses | Response to calling system |
| Webhook response size | 2 MB | Outbound responses | Applies to "await workflow and respond" mode |
| CSV row size | 8 KB | CSV Editor | Per-row limit within CSV processing |
| CSV max columns | 4,096 | CSV Editor | Per-file column limit |
| Data Storage single key | 400 KB | Key-value data storage | 32 levels max nesting depth |
| Limit Type | Value | Window | Edition Differences |
|---|---|---|---|
| API rate limit | 30 req/s (1,800/min) | Per second, rolling | All editions; burst to 50 req/s momentarily |
| Call Connector concurrency | 1,000 concurrent requests | Concurrent | Concurrency-limited, not rate-limited |
| Task credits (Pro) | 250,000 starter credits | Monthly | Overage charged per credit |
| Task credits (Team) | 500,000 starter credits | Monthly | Overage charged per credit |
| Task credits (Enterprise) | 750,000 starter credits | Monthly | Custom negotiation available |
| Workspaces (Pro) | 3 | Per account | -- |
| Workspaces (Team) | 20 | Per account | -- |
| Workspaces (Enterprise) | Unlimited | Per account | -- |
| Component | Timeout | Notes |
|---|---|---|
| Standard connectors | 45 seconds | 120 seconds for CDK-built connectors |
| HTTP Client (universal) | 15 minutes | Response wait time for custom API calls |
| CSV/Redshift connectors | 60 minutes | Exception for bulk data operations |
| Webhook trigger (sync) | 5 minutes | When using trigger event reply |
| Callable workflow response | No timeout | Can run indefinitely |
| Authentication refresh | Immediate 1st retry | 2nd-3rd retries wait ~20 seconds |
Tray.ai provides a centralized authentication store that manages credentials for all connected services. Workflows reference stored auth objects rather than embedding credentials directly.
| Flow | Use When | Token Lifetime | Refresh? | Notes |
|---|---|---|---|---|
| OAuth 2.0 Authorization Code | User-context operations, most SaaS apps | Per-service default | Yes (automatic) | Default and preferred flow; supports PKCE |
| OAuth 2.0 Client Credentials | Server-to-server, machine-to-machine | Per-service default | Yes (automatic) | For APIs that don't need user context |
| API Key | Simple service auth (Stripe, SendGrid, etc.) | Until revoked | N/A | Stored encrypted in Tray vault |
| Basic Auth | Legacy systems, on-premise APIs | Per-session | N/A | Username/password stored encrypted |
| Custom Service Auth | Non-standard auth flows | Variable | Configurable | Build custom auth via Connector Builder |
<org_name>.integration-authentication.com/oauth2/token — you must register this URL with each OAuth provider. [src3]START -- User needs to integrate ERP systems using Tray.ai
|-- What's the integration pattern?
| |-- Real-time (individual records, <1s latency)
| | |-- Target system has native Tray connector?
| | | |-- YES --> Use native connector in workflow with webhook trigger
| | | |-- NO --> Use HTTP Client connector with custom auth
| | |-- Data payload < 6 MB per step?
| | | |-- YES --> Direct step-to-step data passing
| | | |-- NO --> Use Data Storage or file intermediary
| | |-- Need synchronous response?
| | |-- YES --> Webhook with "Await workflow and respond" (5-min timeout)
| | |-- NO --> Webhook with "Auto respond 200" + async processing
| |-- Batch/Bulk (scheduled, high volume)
| | |-- Data volume < 1,000 records?
| | | |-- YES --> Loop connector with native connector steps
| | | |-- NO --> CSV connector (up to 1 GB) + callable workflow for parallel
| | |-- Need to stay within task credit budget?
| | |-- YES --> Minimize loop iterations; use bulk API calls
| | |-- NO --> Standard loop + individual record operations
| |-- Event-driven (webhook, CDC)
| | |-- Source system can push webhooks?
| | | |-- YES --> Webhook trigger on Tray workflow
| | | |-- NO --> Scheduled polling workflow
| | |-- Need guaranteed delivery?
| | |-- YES --> Add Data Storage queue + dead letter workflow
| | |-- NO --> Direct webhook trigger processing
| |-- File-based (CSV/XML import/export)
| |-- File size < 1 GB?
| | |-- YES --> CSV/File connector with 60-min timeout
| | |-- NO --> Split files externally before ingestion
| |-- Need transformation?
| |-- YES --> CSV Editor step (8 KB/row, 4,096 col max)
| |-- NO --> Direct file pass-through
|-- Which direction?
| |-- Inbound --> Check target system rate limits + Tray 30 req/s limit
| |-- Outbound --> Check source system pagination + 6 MB step limit
| |-- Bidirectional --> Design conflict resolution in workflow logic FIRST
|-- Error tolerance?
|-- Zero-loss required --> Data Storage queue + callable retry + error workflow
|-- Best-effort --> Standard retry policy (3 retries, 40s interval)
| Capability | Tray.ai | Workato | MuleSoft | Celigo |
|---|---|---|---|---|
| Approach | Workflow-first, visual + code | Recipe-based, low-code | API-led connectivity | ERP-centric templates |
| Connectors | 700+ | 1,000+ | 400+ (Exchange) | 200+ (ERP-focused) |
| AI Assistant | Merlin AI (NLP to workflow) | Workato Copilot | Einstein AI | Auto-mapping |
| Code Access | Tray Code (3 APIs), JS in steps | Limited custom code | Full Java/Mule DSL | JavaScript transforms |
| ERP Depth | Medium -- strong NetSuite, weak SAP | Strong -- broad ERP coverage | Deep -- Salesforce-native | Deep -- NetSuite/Salesforce-native |
| Pricing Model | Task credits (per step) | Recipes + connections | vCores + API calls | Flows + connections |
| Starting Price | ~$600/mo (Pro) | ~$10K/year | ~$15K/year | ~$500/mo |
| Deployment | Cloud-only | Cloud-only | Cloud + on-premise | Cloud-only |
| Embedded/OEM | Yes (GraphQL Embedded API) | Yes (Embedded platform) | Limited (API Manager) | No |
| Security | SOC 2 Type 2, HIPAA, GDPR | SOC 2 Type 2, HIPAA | SOC 2, ISO 27001 | SOC 2 Type 2 |
Set up the workflow entry point that will receive events from an ERP system. [src3]
// In Tray Build: Create new workflow
// 1. Click "Create Workflow"
// 2. Select "Webhook" trigger
// 3. Choose "Auto respond with status 200" for high-volume
// or "Await workflow and respond" for sync responses
// 4. Copy the generated webhook URL
const webhookUrl = "https://YOUR_TRAY_WEBHOOK_URL";
const response = await fetch(webhookUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
event: "order_created",
order_id: "ORD-12345",
customer: { name: "Acme Corp", erp_id: "CUST-001" },
line_items: [
{ sku: "WIDGET-A", qty: 100, unit_price: 25.00 }
]
})
});
Verify: Check Tray workflow logs for the incoming event --> expected: trigger received with payload visible in step output.
Configure the target ERP connector (e.g., NetSuite, Salesforce) to process the incoming data. [src5]
// In Tray Build: Add connector step after trigger
// 1. Search connector library for target system (e.g., "NetSuite")
// 2. Select operation (e.g., "Create Record" or "Search Records")
// 3. Map input fields from trigger payload using JSONPath:
// $.steps.trigger.body.customer.erp_id --> NetSuite Customer Internal ID
// $.steps.trigger.body.line_items --> Loop connector input
// For systems without native connector (e.g., SAP):
// Use HTTP Client connector:
// - Method: POST
// - URL: https://your-sap-instance.com/sap/opu/odata/sap/API_SALES_ORDER_SRV/A_SalesOrder
// - Headers: { "Authorization": "Bearer {auth.token}" }
// - Body: Map from trigger payload with Data Mapper
Verify: Step output shows the created/updated record ID from the target ERP system.
Add conditional logic to handle partial failures and route errors. [src1]
// In Tray Build: Add error handling
// 1. After each connector step, add a "Boolean" step
// Condition: $.steps.previous.error IS NOT NULL
// 2. TRUE branch: Route to error handler
// - Log error to Data Storage (for dead letter queue)
// - Send alert via Slack/email connector
// 3. FALSE branch: Continue to next step
// For retry logic:
// - Use "Callable Workflow" trigger for the retry workflow
// - Store failed records in Data Storage with key = record_id
// - Scheduled workflow polls Data Storage every 15 min for retries
// - Max 3 retries before routing to manual review queue
Verify: Trigger a test failure (invalid record ID) --> expected: error branch executes, record stored in Data Storage.
Process bulk records while staying within Tray platform limits. [src1]
// In Tray Build: Bulk processing pattern
// 1. Use "List Helpers - Chunk" step to split large arrays
// Input: $.steps.trigger.body.records (e.g., 5,000 items)
// Chunk size: 200 (to stay well under 6 MB step limit)
//
// 2. Use "Loop" connector over chunks
// For each chunk:
// a. Call target ERP connector with batch operation
// b. Collect results
// c. Add "Delay" step (1-2 seconds) to avoid target system rate limits
//
// 3. Alternatively: Use "Callable Workflow" for parallel processing
// Main workflow: chunks data, calls child workflow per chunk
// Child workflow: processes single chunk, returns results
// IMPORTANT: Each loop iteration = 1 task credit per step inside the loop
// 5,000 records / 200 per chunk = 25 chunks
// 5 steps per chunk = 125 task credits
Verify: Monitor workflow execution logs --> expected: all chunks processed, total task credits match estimate.
# Input: Webhook URL from Tray workflow, ERP event payload
# Output: HTTP 200 (async) or workflow result (sync)
import requests
import json
TRAY_WEBHOOK_URL = "https://YOUR_TRAY_WEBHOOK_URL"
def trigger_tray_workflow(event_type: str, payload: dict) -> dict:
"""Trigger a Tray.ai workflow via webhook with ERP event data."""
headers = {"Content-Type": "application/json"}
response = requests.post(
TRAY_WEBHOOK_URL,
headers=headers,
json={"event": event_type, "data": payload},
timeout=30
)
if response.status_code == 429:
import time
time.sleep(2)
return trigger_tray_workflow(event_type, payload)
response.raise_for_status()
return response.json() if response.text else {"status": "accepted"}
result = trigger_tray_workflow("order_created", {
"order_id": "ORD-12345",
"customer_id": "CUST-001",
"total": 2500.00,
"currency": "USD"
})
// Input: Tray user token, workflow ID
// Output: Workflow list and management
import { GraphQLClient, gql } from "graphql-request"; // v6.x
const client = new GraphQLClient("https://tray.io/graphql", {
headers: { Authorization: `Bearer ${process.env.TRAY_USER_TOKEN}` },
});
const LIST_WORKFLOWS = gql`
query {
viewer {
workflows {
edges {
node { id name enabled triggerUrl }
}
}
}
}
`;
async function listWorkflows() {
const data = await client.request(LIST_WORKFLOWS);
return data.viewer.workflows.edges.map((e) => e.node);
}
const workflows = await listWorkflows();
console.log(`Found ${workflows.length} workflows`);
# Input: Tray webhook URL
# Output: HTTP 200 + workflow trigger confirmation
# Trigger a workflow via webhook
curl -X POST "https://YOUR_TRAY_WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d '{"event": "invoice_created", "invoice_id": "INV-2026-001", "amount": 5000.00}'
# List workflows via GraphQL API
curl -X POST "https://tray.io/graphql" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TRAY_USER_TOKEN" \
-d '{"query": "{ viewer { workflows { edges { node { id name enabled } } } } }"}'
When connecting ERP systems through Tray.ai, field mapping happens in the Data Mapper step, which supports JSONPath expressions, JavaScript transforms, and lookup tables.
| Source Field | Target Field | Type | Transform | Gotcha |
|---|---|---|---|---|
| Salesforce: Account.Name | NetSuite: customer.companyName | String | Direct mapping | NetSuite max 83 chars vs Salesforce 255 |
| Salesforce: Opportunity.Amount | NetSuite: salesOrder.total | Currency | Currency conversion if multi-currency | Must set exchange rate subsidiary |
| SAP: VBAK-NETWR | Salesforce: Opportunity.Amount | Decimal | Divide by 100 (SAP minor currency units) | SAP CURR fields store in minor units |
| NetSuite: transaction.tranDate | Salesforce: Opportunity.CloseDate | DateTime | ISO 8601 to YYYY-MM-DD | NetSuite timezone = subsidiary timezone |
| Any: Multi-value field | Tray: JSON array | Array | Split/join with List Helpers | Step payload limit 6 MB for large arrays |
null, NetSuite returns "" for empty fields. Normalize before comparison. [src5]| Code | Meaning | Cause | Resolution |
|---|---|---|---|
| HTTP 429 | Rate limit exceeded | >30 req/s to Tray API or high-volume webhooks | Exponential backoff; use "Auto respond 200" for webhooks |
| STEP_TIMEOUT | Connector operation timed out | Standard connector exceeded 45s timeout | Switch to HTTP Client (15-min timeout) |
| PAYLOAD_TOO_LARGE | Step data exceeds 6 MB | Large dataset between workflow steps | Use CSV connector, Data Storage, or chunk payloads |
| AUTH_REFRESH_FAILED | OAuth token refresh failed after 3 retries | Expired refresh token or revoked access | Re-authenticate connector in Tray UI |
| CONNECTOR_ERROR | Target system returned error | Invalid data, missing fields, permission denied | Check step input mapping and auth permissions |
| WORKFLOW_DISABLED | Workflow triggered but not enabled | Workflow was disabled manually or via API | Enable workflow in Tray UI or via GraphQL |
Set credit usage alerts at 80% threshold; design workflows to minimize step count per record. [src7]Use CSV connector for datasets >1,000 records; chunk payloads with List Helpers. [src1]Pre-refresh tokens at workflow start; use Client Credentials for batch workflows. [src1]Implement idempotency keys; use Data Storage to track processed event IDs. [src1]Implement field-existence checks with Boolean steps; test after connector update notifications. [src5]// BAD -- each record = N steps = N task credits
// 10K records * 5 steps = 50K credits; likely hits 6 MB payload limit
// Loop over 10,000 items with 4 connector steps per iteration
// Total: 40,000 task credits, likely hits payload limit
// GOOD -- chunk into 200-record batches, process in parallel callable workflows
// Main: List Helpers Chunk (200) --> 50 chunks --> Loop --> Call child workflow
// Child: Bulk Create/Update (single API call) --> Return results
// Total: ~250 credits (160x more efficient)
// BAD -- "Await workflow and respond" rate-limits at high volume
// Hundreds of events/second triggers 429 errors, dropped events
// GOOD -- "Auto respond with status 200" (no rate limit) + async queue
// Webhook: Auto-respond 200 --> Data Storage queue --> Done
// Separate scheduled workflow: Read queue --> Process batch --> Cleanup
// BAD -- assumes all fields exist and have expected types
// target.amount = $.steps.salesforce.Amount (fails if null)
// target.name = $.steps.salesforce.Account.Name (fails if deleted ref)
// GOOD -- null checks + type conversion + fallback values
// Boolean: Amount not null? --> Type Converter (to Number) : default 0.00
// Boolean: Account not null? --> Map Name : "Unknown Customer" + flag
// Script: const d = steps.sf.CloseDate; return d ? new Date(d).toISOString() : now;
Calculate (steps_per_record * daily_volume * runs_per_day) before deploying; use bulk connector operations. [src7]Use HTTP Client connector (15-min timeout) for slow endpoints. [src1]Explicitly delete keys at workflow end; use unique run-scoped keys. [src1]Plan workspace architecture before building; re-authenticate after moves. [src6]Split wide records into multiple CSVs; use JSON-based Data Storage instead. [src1]Always review and augment Merlin-generated workflows manually. [src4]# Check Tray API rate limit status (look for 429 responses)
curl -X POST "https://tray.io/graphql" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TRAY_USER_TOKEN" \
-d '{"query": "{ viewer { name } }"}' \
-w "\nHTTP Status: %{http_code}\n"
# List all workflows and their status
curl -X POST "https://tray.io/graphql" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TRAY_USER_TOKEN" \
-d '{"query": "{ viewer { workflows { edges { node { id name enabled triggerUrl } } } } }"}'
# Test a webhook trigger endpoint
curl -X POST "$TRAY_WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d '{"test": true, "timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}'
# Monitor task credit usage: Settings > Usage > Task Credits in Tray UI
# No API endpoint for credit balance (UI-only as of 2026)
| Platform Version | Release Date | Status | Breaking Changes | Migration Notes |
|---|---|---|---|---|
| UAC 2025 (Tray Code/Build/Chat) | 2024-09 | Current | Rebrand to Tray.ai; 3 new APIs | Existing workflows unaffected; new APIs are additive |
| Merlin AI Integration | 2023-05 | Current | None | NLP workflow generation added; optional feature |
| Enterprise Core Refresh | 2023-01 | Current | SSO configuration changes | Requires SSO re-setup for existing Enterprise |
| Embedded Platform v2 | 2022-06 | Supported | GraphQL schema changes | Update GraphQL queries; old mutations deprecated |
| Classic Platform | Pre-2022 | Deprecated | -- | Migrate to UAC; classic workflows remain functional |
| Use When | Don't Use When | Use Instead |
|---|---|---|
| Complex multi-system orchestration with branching, loops, and conditional logic | Simple point-to-point data sync between 2 systems | Celigo or Zapier |
| Need embedded/white-label integration UX for your SaaS product | Need deep SAP-native integration with IDoc/BAPI/RFC | SAP Integration Suite or MuleSoft |
| Development team comfortable with JavaScript and API concepts | Team needs purely no-code drag-and-drop with minimal training | Workato |
| Need AI-assisted workflow generation for rapid prototyping | Need full API lifecycle management (design, mock, test, version) | MuleSoft Anypoint Platform |
| Budget is $500-$5K/month and need enterprise-grade security | Budget is <$500/month or need free tier | n8n (self-hosted) or Make.com |
| High-volume webhook-driven event processing (async pattern) | Need sub-100ms latency for real-time API proxying | MuleSoft or AWS API Gateway |
| Capability | Tray.ai | Workato | MuleSoft | Celigo |
|---|---|---|---|---|
| Architecture | Workflow-first, visual + code | Recipe-based, low-code | API-led connectivity | ERP template-first |
| Connectors | 700+ | 1,000+ | 400+ (Exchange) | 200+ (ERP-focused) |
| AI Copilot | Merlin AI (NLP to full workflow) | Workato Copilot | Einstein AI | Auto-mapping |
| Developer API | GraphQL + 3 REST APIs | REST Admin API | Anypoint CLI + REST | REST Admin API |
| Connector SDK | CDK (TypeScript) | SDK (Ruby) | Custom Connector SDK (Java) | JavaScript transforms |
| SAP Support | No native connector (HTTP only) | Native SAP connector | Deep SAP connector | Native SAP connector |
| NetSuite Support | Native connector (strong) | Native connector (strong) | Via connector | Native connector (deep) |
| Bulk Processing | CSV connector (1 GB), 60-min timeout | Batch recipes, 200 records/batch | Batch processing, DataWeave | Smart Map bulk sync |
| Embedded/OEM | Yes (Embedded API, white-label) | Yes (Embedded platform) | Limited (API Manager) | No |
| Max Payload/Step | 6 MB (between steps) | 100 MB (recipe data) | No hard limit (JVM-dependent) | 5 MB (default) |
| Deployment | Cloud-only (AWS) | Cloud-only | Cloud + on-premise | Cloud-only |
| Compliance | SOC 2 Type 2, HIPAA, GDPR | SOC 2 Type 2, HIPAA | SOC 2, ISO 27001, PCI | SOC 2 Type 2 |