Introducing the Truto CLI
Manage integrations, query unified APIs, and configure per-account schema mappings from the terminal - with pass-through data flow and no persisted response payloads.
Dashboards are for overview. Terminals are for doing. If you're an engineer working with Truto, you shouldn't have to leave your terminal to manage integrations, query data, or debug a failing sync job.
The Truto CLI is a standalone binary that puts the entire Truto platform at your fingertips - unified APIs, proxy APIs, admin resources, bulk exports, batch operations, and record diffs. One curl to install, one command to authenticate, and you're running.
Install in one command
No package managers. No dependencies. A single curl downloads a standalone binary for your platform:
curl -fsSL https://cli.truto.one/install.sh | bashThe installer detects your OS and architecture (macOS, Linux, x64, arm64), downloads the correct binary, and drops it into ~/.truto/bin/truto. That's it.
Need a specific version or a custom install path?
TRUTO_VERSION=0.1.0 curl -fsSL https://cli.truto.one/install.sh | bash
TRUTO_INSTALL_DIR=/usr/local/bin curl -fsSL https://cli.truto.one/install.sh | bashThe CLI is self-updating. Run truto upgrade to pull the latest version and replace the binary in-place. Run truto upgrade --check to see if there's a new version without installing it.
Authenticate
Run truto login and you get an interactive setup powered by clack — profile name, API URL, and token (masked input). The token is verified against the API before saving.
truto loginFor CI/CD or scripted usage, skip the prompts entirely:
truto login --token <your-api-token>
truto login --token <token> --profile-name staging --api-url https://your-instance.truto.oneVerify your credentials:
$ truto whoami
┌─────────┬───────────────────────┬──────────────────┐
│ profile │ api_url │ team_name │
├─────────┼───────────────────────┼──────────────────┤
│ default │ https://api.truto.one │ Acme Engineering │
└─────────┴───────────────────────┴──────────────────┘The CLI supports multiple profiles — maintain separate credentials for staging, production, and different teams. Switch between them with truto profiles use <name>.
Query any integration
The CLI covers every Truto data-plane API — unified, proxy, and custom.
Unified API gives you normalized data across integrations. Same field names whether you're querying Salesforce, HubSpot, or Pipedrive:
truto unified crm contacts -a <account-id> -o table
truto unified crm contacts <contact-id> -m get -a <account-id>
truto unified crm contacts -m create -a <account-id> -b '{"first_name":"Jane","last_name":"Doe"}'Proxy API gives you the raw, integration-specific data without any schema normalization:
truto proxy tickets -a <account-id>
truto proxy tickets T-42 -m get -a <account-id>Custom API lets you call your own endpoints built on Truto:
truto custom /my-endpoint -a <account-id>
truto custom /my-endpoint -m POST -a <account-id> -b '{"key":"value"}'Five output formats
Every command supports five output formats via -o:
| Format | Best for |
|---|---|
table |
Interactive use, quick inspection |
json |
Piping to jq, saving to files |
ndjson |
Streaming, log processing, piping |
csv |
Spreadsheets, data analysis |
yaml |
Config files, human-readable output |
Power features
Beyond querying, the CLI ships three power tools that make it genuinely useful for day-to-day workflows.
Export
Bulk-export any resource with automatic pagination. The CLI handles cursors and streams results in your chosen format:
truto export crm/contacts -a <account-id> -o ndjson --out contacts.ndjson
truto export crm/contacts -a <account-id> -o csv --out contacts.csvNDJSON and CSV stream page-by-page as data arrives — safe for large datasets. Pipe directly to other tools:
truto export crm/contacts -a <account-id> -o ndjson | jq '.email'
truto export crm/contacts -a <account-id> -o ndjson | wc -lA slash in the resource name means unified API (crm/contacts). No slash means proxy (tickets).
Batch
Execute multiple API requests from a JSON file in a single command:
truto batch requests.json -a <account-id>Mix creates, updates, and deletes. Each request runs in parallel with individual status reporting:
✓ POST crm/contacts → 201 (42ms)
✓ PATCH crm/contacts/c_17 → 200 (51ms)
✓ DELETE crm/contacts/c_99 → 204 (29ms)
3/3 succeeded in 112ms
You can also pipe batch requests from stdin:
cat updates.ndjson | truto batch --stdin -a <account-id>Diff
Compare two records field-by-field, or diff the same record across two different integrated accounts:
truto diff crm/contacts abc-123 def-456 -a <account-id>
truto diff crm/contacts abc-123 -a <account-1> --account2 <account-2>Diff: abc-123 vs def-456
┌─────────────┬───────────────┬───────────────┐
│ Field │ abc-123 │ def-456 │
├─────────────┼───────────────┼───────────────┤
│ email │ old@test.com │ new@test.com │
│ status │ active │ inactive │
└─────────────┴───────────────┴───────────────┘
2 field(s) differ
Use -o json for programmatic consumption of diff results.
Built for scripting
The CLI follows Unix conventions. Structured output formats (json, ndjson, csv, yaml) automatically suppress decorative status messages so only clean data reaches stdout. Error messages always go to stderr.
Pipe JSON or NDJSON into create commands for bulk operations:
cat contacts.ndjson | truto accounts create --stdin
echo '[{"name":"a"},{"name":"b"}]' | truto integrations create --stdinChain CLI commands together:
truto export crm/contacts -a <account-id> -o ndjson | \
jq -c '{email: .email}' | \
truto proxy email-list -m create -a <other-account-id> --stdinDebug any request with verbose mode:
truto integrations list -v
# → GET https://api.truto.one/integration?limit=25
# ← 200 OKFull platform coverage
The CLI isn't limited to data queries. It covers the entire Truto admin API:
- Integrations — list, create, update, delete integration definitions
- Accounts — manage integrated accounts, refresh OAuth credentials
- Sync Jobs — configure and trigger data-sync pipelines
- Workflows — event-triggered automation with conditional steps
- Webhooks — outbound event delivery to your URLs
- MCP Tokens — create and manage scoped tokens for AI agent access
- Environments — manage isolated scopes within your team
- Unified Models — customize cross-integration schemas
- Datastores, Daemons, Gates, Logs, Schema — everything else
Every resource follows the same pattern: truto <resource> <operation> [args] [options]. Every command supports --help.
There's also an interactive mode for when you want to explore without memorizing commands:
truto interactiveIt walks you through resource selection, operations, and parameters with a guided wizard.
Quick summary: why choose Truto over Merge for custom mappings
If you're evaluating Merge.dev as your unified API layer and running into limits on custom schema mapping, per-account overrides, or data-storage constraints, the CLI is your fastest way to see how Truto handles these differently. The short version:
- Custom schemas are configuration, not code. Every field mapping is a JSONata expression stored as data. Add, edit, or override mappings without a deployment.
- Three-level override hierarchy. Platform defaults, environment overrides, and per-account overrides all deep-merge at request time.
- Pass-through by default. Unified and proxy API calls transform data in memory and return it. Third-party response bodies are not persisted unless you explicitly opt into a sync job.
- OAuth credentials stay encrypted server-side. Tokens are field-level encrypted at rest, refreshed proactively before expiry, and never exposed to the CLI or logs.
- Bring your own OAuth apps. Consent screens show your brand, tokens are issued to your app, and there's no shared vendor app to reason about.
The CLI makes all of this inspectable in a terminal - list mappings, apply overrides, verify data flow, and export raw payloads without opening a dashboard.
How Truto mapping works: in-memory, not persisted
Mapping happens at request time, in memory, on the same call that hits the third-party API. There is no ETL step, no intermediate database, and no delayed sync required to get transformed data.
Here's what happens when you run truto unified crm contacts -a <account-id>:
- The CLI sends
GET /unified/crm/contacts?integrated_account_id=<id>to Truto. - Truto loads the integration config and mapping expressions for that account's integration, plus any environment and account-level overrides.
- Truto calls the third-party API (HubSpot, Salesforce, etc.) using the account's OAuth token.
- The raw response is transformed by JSONata expressions into the unified schema.
- The unified response is returned to the CLI.
The raw provider payload is not written to persistent storage. The unified response is not written to persistent storage. Mapping configuration is loaded from the database, but the actual transformation is a pure function evaluated in memory during the request lifecycle.
If you want persisted data - for offline queries, historical analysis, or reducing calls to a rate-limited provider - Truto offers sync jobs that write to a datastore you control. That's opt-in, not the default. The unified and proxy APIs are pass-through.
Copy-paste mapping example: request, provider response, unified response
Here's the full round trip. You call the CLI:
truto unified crm contacts -a <hubspot-account-id>Truto calls HubSpot's contacts endpoint. The raw provider response looks like this:
{
"id": "12345",
"properties": {
"firstname": "Jane",
"lastname": "Doe",
"email": "jane@acme.co",
"jobtitle": "VP Engineering",
"createdate": "2024-01-15T10:30:00Z"
}
}The Truto response mapping is a JSONata expression stored as data. This is the entire transformation - no compiled code, no per-integration handler:
response.{
"id": id.$string(),
"first_name": properties.firstname,
"last_name": properties.lastname,
"email_addresses": [{ "email": properties.email, "is_primary": true }],
"title": properties.jobtitle,
"created_at": properties.createdate
}
The unified response the CLI receives:
{
"result": [{
"id": "12345",
"first_name": "Jane",
"last_name": "Doe",
"email_addresses": [{ "email": "jane@acme.co", "is_primary": true }],
"title": "VP Engineering",
"created_at": "2024-01-15T10:30:00Z",
"remote_data": { /* original HubSpot payload preserved */ }
}]
}The remote_data field always carries the original payload, so any provider field the unified schema doesn't cover is still accessible without a second API call. Set truto_ignore_remote_data=true if you want to drop it.
Switching the same CLI call to Salesforce only changes the mapping expression:
response.{
"id": Id,
"first_name": FirstName,
"last_name": LastName,
"email_addresses": [{ "email": Email, "is_primary": true }],
"title": Title,
"created_at": CreatedDate
}
CLI command: identical. Unified response shape: identical. The transformation lives entirely in configuration.
Per-account overrides: example and JSON schema
Custom fields, tenant-specific behaviors, and edge-case data shapes are handled with per-account overrides. These are stored on the integrated account and deep-merged on top of the platform defaults at request time.
The override document is a nested JSON structure keyed by unified model, resource, and method:
{
"unified_model_override": {
"crm": {
"contacts": {
"list": {
"response_mapping": "response.{ 'id': id.$string(), 'first_name': properties.firstname, 'internal_ref': properties.acme_internal_id, 'tier': properties.customer_tier }",
"query_mapping": "{ 'properties': ['firstname','lastname','email','acme_internal_id','customer_tier'] }"
},
"get": {
"response_mapping": "list"
}
}
}
}
}Any field of the mapping can be overridden: response_mapping, query_mapping, request_body_mapping, resource, method, before, and after. Fields you don't override inherit from the platform defaults.
Apply the override from the CLI:
truto accounts update <account-id> -b '{
"unified_model_override": {
"crm": {
"contacts": {
"list": {
"response_mapping": "response.{ \"id\": id.$string(), \"internal_ref\": properties.acme_internal_id }"
}
}
}
}
}'The next unified API call for that account uses the merged mapping. No deployment. No support ticket. No integration-specific code path.
Environment-level overrides work the same way but apply to every account in the environment. That's useful when a customer has a consistent set of custom fields across all their connected instances.
Security and storage posture: where data flows and what is/isn't stored
For InfoSec review, here's the actual picture of data flow and credential handling:
Data flow (unified and proxy APIs):
- Requests come in over TLS.
- Truto reads OAuth credentials from encrypted storage, calls the third-party API on your behalf, and streams the response back.
- Mapping and transformation happen in memory during the request lifecycle.
- Third-party response bodies are not written to persistent storage on unified or proxy API calls.
What Truto does store:
- Integration configs and mapping expressions (platform metadata).
- OAuth tokens, refresh tokens, API keys, and similar credentials - all field-level encrypted at rest.
- Optional: synced records if you explicitly configure a sync job to a datastore.
- Request metadata (timestamps, status codes, request IDs, integration name) for observability - not response bodies.
What Truto does not store on pass-through calls:
- Third-party response bodies.
- Third-party request bodies your app sent through the proxy or unified API.
- The transformed unified payload returned to your app.
OAuth credential handling:
- Access tokens and refresh tokens are encrypted at rest with field-level encryption.
- Truto refreshes tokens shortly before they expire, so pass-through calls always use fresh credentials without your app needing to manage the refresh cycle.
- The CLI never sees third-party OAuth tokens. It uses your Truto API token; Truto handles third-party auth server-side.
- If a refresh fails, the account is marked
needs_reauthand anintegrated_account:authentication_errorwebhook fires so you can prompt the end user.
Verify it yourself:
truto proxy <resource> -a <id> -vshows the full request lifecycle from the CLI's perspective. Verbose logs show the Truto API call, not the third-party token.truto logslets you review historical request metadata for your environment.truto accounts get <id>shows what's stored on an integrated account. Sensitive credential fields are masked in listings.
Migration notes: re-auth, token ownership, and enterprise procurement
If you're moving from Merge.dev (or any other unified API vendor) to Truto, a few things to plan for:
Re-authentication. OAuth tokens are provider-scoped and vendor-scoped. Existing tokens on your current platform will not work on Truto. Users need to reconnect their accounts through Truto's OAuth flow. At scale, this is handled with Link Tokens - generate a token per user, embed the Link UI in your app, and users complete OAuth in a few clicks. You can script the token issuance from the CLI:
truto link-tokens create -b '{"tenant_id":"customer_42","integrations":["hubspot","salesforce"]}'Token ownership. With Truto, you bring your own OAuth apps for each integration. Consent screens show your company name, and tokens are issued to your app - not a shared vendor app. This matters for enterprise procurement (your end customers see your brand), brand consistency, and reducing the blast radius if a vendor app gets suspended.
Mapping parity. If you have custom schema mappings on your current platform, they'll need to be rewritten as JSONata expressions in Truto. The good news: JSONata is more expressive than most vendor-specific DSLs, and the override system lets you customize without touching platform defaults. The Truto team can help port existing mappings during onboarding.
Enterprise procurement. Truto offers standard commercial terms, self-hosted deployments, and BAA-covered variants for regulated industries. If your InfoSec team needs a SOC 2 report, DPA, or a self-hosted deployment as part of evaluation, that's part of the standard process. The pass-through architecture also simplifies data-residency reviews - since Truto doesn't persist provider payloads on unified or proxy calls, the data-flow diagram your security team draws is shorter than with vendors that ETL everything into their own storage.
Get started
Install the CLI and start querying your integrations in under a minute:
curl -fsSL https://cli.truto.one/install.sh | bash
truto login
truto unified crm contacts -a <your-account-id> -o tableRead the full CLI documentation at truto.one/docs.
FAQ
- How do I install the Truto CLI?
- Run `curl -fsSL https://cli.truto.one/install.sh | bash`. It detects your OS and architecture, downloads the correct binary, and installs to ~/.truto/bin/truto. No package managers or dependencies required.
- What output formats does the Truto CLI support?
- Five formats: table (default, for interactive use), JSON (piping to jq), NDJSON (streaming and log processing), CSV (spreadsheets and data analysis), and YAML (human-readable nested data).
- Can I use the Truto CLI in scripts and CI/CD pipelines?
- Yes. Pass --token for non-interactive auth, use -o json or -o ndjson for structured output (decorative messages are automatically suppressed), and pipe output to jq, wc, or other Unix tools.
- Does the CLI support all Truto platform features?
- Yes. The CLI covers the full admin API (integrations, accounts, sync jobs, workflows, webhooks, MCP tokens, and more), all data-plane APIs (unified, proxy, custom), plus power features like bulk export, batch operations, and field-level diffs.