Pine Labs CLI
Official command-line interface for the Pine Labs Plural platform. Create orders, listen for and replay webhooks, run deterministic API calls, and automate workflows from your shell or CI.
The Pine Labs CLI (pinelabs) is the official command-line interface for the Pine Labs Plural platform. It is a thin, deterministic, fully auditable client over the Pine Labs Online APIs — the same APIs your production code calls.
Use it to:
- Create and inspect orders, payments, refunds, payouts, subscriptions, and settlements.
- Receive Pine Labs webhook events on a local URL over a secure WebSocket relay.
- Sign, verify, and replay webhook events against your handlers.
- Drive the same workflows from CI with structured JSON output.
- Drive the same workflows through an MCP-backed
askcommand for assistive exploration.
Versioning. The CLI follows semver from
1.0.0. Breaking changes bump the major version with a deprecation cycle. Pin to an exact version in CI:npm install -g @pinelabs/cli@1.0.1.
This page is split in two:
- Get started fast — install, log in, first order, listen, trigger. Read this first if you have 2 minutes.
- CLI reference — flags, authentication, every command group, webhooks/events, MCP, security, troubleshooting. Deep dive.
Quickstart in 60 seconds
npm install -g @pinelabs/cli
export PINELABS_CLIENT_ID="your_client_id_here"
export PINELABS_CLIENT_SECRET="your_client_secret_here"
pinelabs login
pinelabs orders create --order-amount 100 --currency INR \
--merchant-order-reference quickstart_$(date +%s)
On Windows (PowerShell), use $env:PINELABS_CLIENT_ID = "..." and $env:PINELABS_CLIENT_SECRET = "..." instead of export.
Four lines from a clean shell to a real sandbox order. If npm isn't an option, see Install for alternative install paths. The longer Get started fast section adds webhooks, replay, and CI patterns.
Common tasks
Developers think in tasks, not commands. The most common ones map directly:
| Task | Command |
|---|---|
| Create a test order | pinelabs orders create --order-amount 49900 --currency INR --merchant-order-reference order_$(date +%s) |
| Look up an order | pinelabs orders get ord_abc |
| Refund an order | pinelabs refunds create --order-id ord_abc --amount 10000 |
| Listen for webhooks locally | pinelabs listen --forward-to http://localhost:3000/webhooks |
| Replay failed deliveries | pinelabs events replay-spool --forward-to http://localhost:3000/webhooks |
| Trigger a sandbox event | pinelabs trigger payment.success |
| Verify a webhook signature | pinelabs webhooks verify --webhook-id evt_x --webhook-timestamp 17… --webhook-signature … --body @payload.json |
| Check a settlement by UTR | pinelabs settlements get-by-utr <utr> |
| Confirm which environment you're on | pinelabs whoami |
| Self-diagnose a broken setup | pinelabs doctor |
Each row is documented in full in the CLI reference.
How the CLI works (mental model)
The CLI is intentionally a thin layer. Holding the right mental model makes every flag below obvious:
pinelabs <resource>calls Pine Labs APIs directly. No proxy, no local rewriting — the CLI is a faithful HTTP client with authentication, idempotency keys, and audit logging bolted on.pinelabs listenopens an outbound WebSocket to the Pine Labs relay and forwards events to your local URL. There is no inbound port, public tunnel, or localhost exposure.- State on disk is small and well-known:
~/.pinelabs/config.json(credentials, profiles),~/.pinelabs/audit.log(every command + request id), and~/.pinelabs/spool.jsonl(failed-delivery queue). Delete the directory and the CLI is reset. - Replay is real, not mocked.
events replay-spoolre-POSTs spool entries with the original body andwebhook-id, but with a freshly signedwebhook-timestamp/webhook-signatureso spec-compliant verifiers accept them. Your handler runs against signatures it would see in production.
Sandbox vs production
The CLI talks to whichever environment your active profile points at. There is no --live / --mode flag yet; the active api_base_url is the source of truth. Always verify before any destructive command:
pinelabs whoami
# look at "api_base_url"
# sandbox → https://pluraluat.v2.pinepg.in/...
# production → https://api.pluralpay.in/... (or your tenanted host)
A safer pattern is to keep separate named profiles and let the profile name carry the environment:
pinelabs --profile sandbox login
pinelabs --profile prod login
pinelabs --profile sandbox orders create … # daily testing
pinelabs --profile prod orders get ord_x # read-only investigation
Warning. Never run
pinelabs triggeragainst a production profile unless you fully understand the downstream side effects — see the warning undertrigger.
Why Pine Labs CLI is different
- Deterministic workflows. Every write carries an
Idempotency-Keyso retries are safe; the documented per-endpoint contract tells you exactly what replays vs. dedups vs. surfaces. - Signature-faithful webhook replay. Failed deliveries land in a local spool, are re-signed with a fresh timestamp on replay, and pass spec-compliant 5-minute tolerance verifiers. No "strip signatures to make it work" hacks.
- Audit log by default. Every command, request id, and response status goes to
~/.pinelabs/audit.logwith PCI/PII redacted before write — the artifact Support actually asks for. - AI-assisted with guardrails. The MCP-backed
askplanner proposes tool calls; the CLI validates, derives idempotency keys deterministically, and prompts before any non-readonly action. The planner cannot run payments directly. jq-safe output. Strict stdout/stderr split means prompts, warnings, and progress lines never leak into machine-readable JSON.
Idempotency behaviour by endpoint
The CLI sends an Idempotency-Key header on every write, but the server handles it differently per endpoint. Understand the contract before you retry:
| Endpoint | Dedupe primitive | Retry behaviour | What the CLI does |
|---|---|---|---|
POST /orders | merchant_order_reference | Returns HTTP 422 DUPLICATE_REQUEST on conflict — not a replay of the original response. | Auto-generates a unique reference per invocation. If you pass --idempotency-key, it is also used as the reference so header and body agree. |
POST /refunds | merchant_order_reference | Replays the original 2xx response on retry. Safe to retry on transport errors. | Auto-generates a key per invocation; override with --idempotency-key. |
POST /payouts | clientReferenceId | Replays the original 2xx response on retry. | Same as refunds. |
raw POST … | Idempotency-Key header | Depends on the underlying endpoint. | Sends the key you pass via --idempotency-key, or auto-generates one. |
Note. The CLI retries on
502,503, and504(transport/gateway errors). A500is surfaced immediately because the server may have already committed the side effect.
If you've been writing payments code with curl, Postman collections, or generic HTTP clients, this is what you're trading them for.
How it compares
| Capability | Pine Labs CLI | curl / Postman | Generic CLIs |
|---|---|---|---|
| Native auth + token refresh | ✅ | ❌ | varies |
| Deterministic idempotency keys per write | ✅ | manual | partial |
| Webhook relay over outbound WSS (no tunnels) | ✅ | ❌ | partial |
| On-disk spool with signature-faithful replay | ✅ | ❌ | partial |
| Audit log with PCI/PII redaction | ✅ | ❌ | partial |
AI-assisted (ask) with deterministic guardrails | ✅ | ❌ | ❌ |
Strict stdout/stderr split (… | jq always works) | ✅ | n/a | varies |
| First-class Pine Labs Plural endpoints | ✅ | manual | ❌ |
The table is qualitative — individual tools differ — but the design delta is real: this CLI treats reliability primitives (idempotency, audit, replay) as defaults, not opt-ins.
When to use the CLI — and when not to
Use it for:
- Building and testing integrations against the Plural sandbox.
- CI smoke tests and release verification (
pinelabs orders create | jq -e). - Debugging live integration issues — audit log,
whoami,doctor. - Replaying webhooks against your handler under realistic signatures.
- Ad-hoc operator tasks (refund a known order, check a settlement UTR).
Do NOT use it for:
- End-user authentication flows. The CLI uses OAuth2 client credentials — it is not an end-user login mechanism, has no browser/device-code flow, and is not designed to run in a customer's browser or app.
- High-frequency production traffic. The CLI is a thin client over HTTP with sequential semantics; production payment volume should go through your own server-to-server integration of the Plural APIs, not through shelling out to a binary.
- Storing long-lived credentials on shared / multi-user machines.
~/.pinelabs/config.jsonis0600; that's enough for a single-user workstation or ephemeral CI runner, not a shared bastion host.
Get started fast
Install
The CLI ships pre-built native binaries for macOS, Linux, and Windows via npm. No build tools, compilers, or runtime dependencies required.
# Recommended (any OS with Node.js 16+)
npm install -g @pinelabs/cli
# Zero-install (one-off commands and CI)
npx @pinelabs/cli version
# Yarn
yarn global add @pinelabs/cli
# pnpm
pnpm add -g @pinelabs/cli
The package downloads the correct binary for your platform and architecture automatically.
Supported platforms:
| Platform | Architectures |
|---|---|
| macOS | Apple Silicon (arm64), Intel (x64) |
| Linux | x64, arm64 |
| Windows | x64 |
Verify:
pinelabs version
# → pinelabs 1.0.1 (abc1234, built 2026-05-12T10:00:00Z)
Log in
The CLI authenticates with OAuth2 client credentials — the server-to-server flow. It is not designed for user login flows (there is no browser-based or device-code OAuth flow), so do not embed it in customer-facing sign-in paths.
You'll need your Client ID and Client Secret from the Pine Labs merchant dashboard (Developers → API Keys).
export PINELABS_CLIENT_ID="your_client_id_here"
export PINELABS_CLIENT_SECRET="your_client_secret_here"
pinelabs login
Expected output:
logged in. Token preview: eyJhbG…****
OAuth2 Client Credentials. The CLI uses OAuth2 client credentials. There is no browser-based or device-code flow today; this auth model is intended for server-to-server integrations and CI runners, not interactive end-user sign-in. See Authentication for details.
By default login performs a token exchange against the Plural API and persists only the short-lived access_token (and expires_at). The client secret is read from the environment, used in memory, and never written to disk. See Authentication for --direct, --store-secret, and CI patterns.
First order
pinelabs orders create \
--order-amount 49900 \
--currency INR \
--merchant-order-reference order_$(date +%s) \
--customer-email jane@example.com
Sample response (JSON, abbreviated):
{
"order_id": "ord_NQ4j8xK2pZmL1a",
"status": "CREATED",
"order_amount": {
"value": 49900,
"currency": "INR"
},
"merchant_order_reference": "order_1717420000",
"redirect_url": "https://pay.pluralonline.com/p/NQ4j8xK2pZmL1a"
}
Note that order_amount is a nested object ({ value, currency }), not a top-level amount/currency pair — pipe accordingly:
pinelabs orders create ... | jq '.order_id, .order_amount.value'
Add --open to launch the redirect URL in your default browser.
Listen for webhooks
# one-time: store the webhook secret from your Pine Labs dashboard
pinelabs config set webhook_secret "your_webhook_secret_here"
pinelabs listen --forward-to http://localhost:3000/webhooks
Expected output:
relay connected -> forwarding to http://localhost:3000/webhooks (verify=true)
payment.authorized evt_abc123 -> 200 (delivered)
The CLI dials a secure outbound WebSocket to the Pine Labs relay and forwards matching events to your local URL. listen verifies signatures by default and exits non-zero if no webhook_secret is configured — pass --skip-verify for local dev only. See listen for the full flag reference.
Trigger an event
pinelabs trigger payment.success
pinelabs trigger --list # see available fixtures
pinelabs trigger payment.success --add amount.value=15000
Triggered events are dispatched server-side; they flow back through the same relay and Pine Labs signature pipeline used by listen. --add overrides and --raw payloads are forwarded as the event body, so an active pinelabs listen will see exactly the payload you constructed.
CLI reference
Global flags
These flags are registered on the root command and are inherited by every subcommand:
| Flag | Default | Description |
|---|---|---|
--profile <name> | default | Config profile to use (see Profiles). |
--api-base <url> | profile value | Override the active profile's api_base_url for this invocation only. Does not persist. |
--timeout <duration> | 30s | Override the per-request HTTP timeout, for example 60s or 2m. |
Subcommands add their own flags — see each command. Common subcommand flags include --idempotency-key, --yes, and resource-specific identifiers.
--help is available on every command.
Environment variables
The CLI is conservative about implicit configuration. Every Pine Labs or LLM-specific environment variable it reads directly is listed below. Standard OS/runtime variables such as HOME and HTTP proxy settings may still affect file locations or network transport.
Authentication
| Variable | Used by | Description |
|---|---|---|
PINELABS_CLIENT_ID | login | OAuth client id (required for login). |
PINELABS_CLIENT_SECRET | login; runtime by every command in two cases | (a) --direct profile without --store-secret, or (b) OAuth profile whose access token has expired — the CLI does not store a refresh token, so it must re-exchange client_id + client_secret to mint a fresh bearer. Keep this exported in any shell that runs CLI commands beyond the access-token lifetime, or accept a one-time pinelabs login to re-mint. Never persisted unless --store-secret is passed. |
Webhooks
| Variable | Used by | Description |
|---|---|---|
PINELABS_WEBHOOK_SECRET | webhooks verify, webhooks sign | Default signing secret when --secret is omitted. Base64-encoded. |
MCP relay (opt-in, AI workflows only)
| Variable | Used by | Description |
|---|---|---|
PINELABS_MCP_ENDPOINT | mcp, ask | URL of the MCP server. |
PINELABS_MCP_CLIENT_ID | mcp, ask | MCP OAuth client id (falls back to PINELABS_CLIENT_ID). |
PINELABS_MCP_CLIENT_SECRET | mcp, ask | MCP OAuth client secret (falls back to PINELABS_CLIENT_SECRET). |
PINELABS_MCP_API_KEY | mcp, ask | Static API-key auth, used when client credentials are absent. |
ask planner (LLM selection)
| Variable | Used by | Description |
|---|---|---|
PINELABS_ASK_PROVIDER | ask | Override LLM provider (openai, anthropic, groq, ollama). |
PINELABS_ASK_MODEL | ask | Override model name. |
PINELABS_ASK_MAX_TOOLS | ask | Cap on the number of candidate tools sent to the planner per question. |
PINELABS_ASK_DEBUG | ask | When set to 1/true, prints planner trace to stderr. |
OPENAI_API_KEY | ask | API key for the OpenAI provider. |
ANTHROPIC_API_KEY | ask | API key for the Anthropic provider. |
GROQ_API_KEY | ask | API key for the Groq provider. |
OLLAMA_HOST | ask | Base URL for a local Ollama instance. |
Other settings (API base URL, relay URL, merchant id, webhook secret, client id/secret) live in ~/.pinelabs/config.json and are managed via pinelabs config set.
Authentication
The CLI supports two credential paths.
1. OAuth client credentials (default, recommended)
export PINELABS_CLIENT_ID="your_client_id_here"
export PINELABS_CLIENT_SECRET="your_client_secret_here"
pinelabs login
This performs a POST to the Plural token endpoint and stores only the resulting short-lived access_token plus expires_at in ~/.pinelabs/config.json (mode 0600). The client secret is never written to disk in this mode.
2. Direct header auth (X-Client-Id / X-Client-Secret)
For environments that prefer header-based auth and want to skip token exchange:
pinelabs login --direct # stores client_id only
pinelabs login --direct --store-secret # ALSO persists client_secret (CI only)
Without --store-secret, the client secret must be re-exported in every shell that runs the CLI. With --store-secret, the secret is written in plaintext to ~/.pinelabs/config.json (mode 0600) and the CLI prints a loud warning. Use this only on ephemeral CI runners.
Logout
pinelabs logout # clears tokens for the active profile
pinelabs logout --profile staging
Clears access_token, refresh_token, and expires_at but preserves client_id so pinelabs login can resume without re-entering credentials.
Profiles
The CLI supports multiple named profiles in a single config file.
pinelabs --profile staging login
pinelabs --profile staging orders create ...
Profiles are isolated: each has its own api_base_url, relay_url, client_id/client_secret, tokens, merchant_id, and webhook_secret.
Configuration
Config lives at ~/.pinelabs/config.json (mode 0600, parent dir 0700). The format is JSON, not TOML.
{
"profiles": {
"default": {
"api_base_url": "https://pluraluat.v2.pinepg.in/api/pay/v1",
"relay_url": "wss://relay.pinelabs.com/v1/listen",
"auth_mode": "oauth",
"client_id": "...",
"access_token": "...",
"expires_at": 1746630000,
"merchant_id": "...",
"webhook_secret": "..."
}
}
}
Manage it through the CLI (recommended) or hand-edit:
pinelabs config get <key>pinelabs config set <key> <value>pinelabs config list
Editable keys: api_base_url, relay_url, client_id, client_secret, merchant_id, webhook_secret. The session quartet (access_token, refresh_token, expires_at, auth_mode) is owned by login/logout and cannot be set directly.
Command groups
Every command supports --help. The lists below reflect what is wired up in the current CLI and in the generated command reference.
orders
Manage checkout orders — the top-level container for one or more payment attempts.
| Command | Endpoint | Description |
|---|---|---|
pinelabs orders create | POST /api/checkout/v1/orders | Create an order with redirect URL. Flags: --order-amount, --currency, --merchant-order-reference, --mode, --callback-url, --customer-email, --customer-mobile, --open, --idempotency-key |
pinelabs orders get <id> | GET /orders/{id} | Fetch order details |
pinelabs orders capture | POST /orders/{id}/capture | Capture a pre-authorized order. Flags: --order-id, --merchant-capture-reference, --capture-amount (0 = full capture), --currency, --idempotency-key |
pinelabs orders cancel | POST /orders/{id}/cancel | Cancel an order. Flags: --order-id, --yes, --idempotency-key |
pinelabs orders create \
--order-amount 49900 --currency INR \
--merchant-order-reference order_42 \
--callback-url https://example.com/return
payments
| Command | Endpoint | Description |
|---|---|---|
pinelabs payments list | GET /orders/{id} | List payments embedded in an order. Flags: --order-id |
pinelabs payments upi | POST /orders/{id}/payments | UPI Collect/Intent flow. Flags: --order-id, `--txn-mode INTENT |
Card payments and OTP flows are not yet first-class payment commands; use pinelabs raw against the corresponding endpoints.
refunds
| Command | Endpoint | Description |
|---|---|---|
pinelabs refunds create | POST /refunds/{order-id} | Refund an order (refunds are scoped to orders, not payments). Flags: --order-id, --amount, --currency, --merchant-order-reference (auto-generated if blank), --yes, --idempotency-key |
pinelabs refunds create --order-id ord_abc --amount 10000
Refunds prompt for confirmation by default. Pass --yes in scripts.
payouts
| Command | Endpoint | Description |
|---|---|---|
pinelabs payouts create | POST /payouts | Create a payout (HIGH RISK). Flags: --amount, --currency, --beneficiary-id, `--mode IMPS |
pinelabs payouts cancel <id> | POST /payouts/{id}/cancel | Cancel a payout. Flags: --yes, --idempotency-key |
pinelabs payouts get <id> | GET /payouts/{id} | Fetch payout details |
pinelabs payouts balance | GET /payouts/balance | Check payout balance |
subscriptions and plans
| Command | Endpoint | Description |
|---|---|---|
pinelabs subscriptions plans create | POST /subscriptions/plans | Create a plan. Flags: --name, `--frequency Day |
pinelabs subscriptions plans list | GET /subscriptions/plans | List plans. Flags: --page, --size |
pinelabs subscriptions plans delete <id> | DELETE /subscriptions/plans/{id} | Delete a plan (HIGH RISK). Flags: --yes, --idempotency-key |
pinelabs subscriptions create | POST /subscriptions | Create a subscription. Flags: --plan-id, --customer-id, --reference, --idempotency-key |
pinelabs subscriptions get <id> | GET /subscriptions/{id} | Fetch subscription details |
pinelabs subscriptions cancel <id> | POST /subscriptions/{id}/cancel | Cancel a subscription (HIGH RISK). Flags: --yes, --idempotency-key |
pinelabs subscriptions presentations create | POST /subscriptions/{id}/presentations | Create a presentation (HIGH RISK). Flags: --subscription-id, --due-date, --amount, --currency, --reference, --yes, --idempotency-key |
pinelabs subscriptions presentations get <id> | GET /presentations/{id} | Fetch presentation |
pinelabs subscriptions presentations retry <id> | POST /presentations/{id}/retry | Retry a presentation (HIGH RISK). Flags: --yes, --idempotency-key |
pinelabs subscriptions presentations delete <id> | DELETE /presentations/{id} | Delete a presentation (HIGH RISK). Flags: --yes, --idempotency-key |
settlements
| Command | Endpoint | Description |
|---|---|---|
pinelabs settlements get-by-utr <utr> | GET /settlements/utr/{utr} | Fetch settlement by UTR number |
analytics
| Command | Endpoint | Description |
|---|---|---|
pinelabs analytics success-rate | GET /analytics/success-rate | Get payment success rate. Flags: --since (default 24h, max 168h) |
search
| Command | Endpoint | Description |
|---|---|---|
pinelabs search transaction <id> | GET /transactions/{id} | Search for a transaction |
Diagnostics: whoami and doctor
pinelabs whoami — Print the active profile's identity as JSON. Includes profile, api_base_url, relay_url, auth_mode, masked client_id and access_token, token_expired, webhook_secret_set, merchant_id, and logged_out status. Read-only, no API call.
pinelabs doctor — Run self-checks and print PASS/WARN/FAIL with an actionable fix string. Exits non-zero if any check fails so CI can gate on it. Flags: --json for machine-readable output.
$ pinelabs doctor
[PASS] config.path ~/.pinelabs/config.json
[PASS] config.mode 0600
[PASS] auth.state oauth (bearer token present)
[PASS] auth.token valid (expires in 47m12s)
[WARN] webhook.secret no webhook_secret on profile
fix: `pinelabs config set webhook_secret <base64>` …
[PASS] api.reachable https://pluraluat.v2.pinepg.in/api/pay/v1 → HTTP 404
[PASS] relay.reachable TLS handshake ok (relay.pinelabs.com:443)
7 check(s): 0 failed, 1 warned
raw — escape hatch
For endpoints that don't yet have a first-class command, raw lets you call any path with the active profile's auth applied:
pinelabs raw GET /orders/ord_abc
pinelabs raw POST /refunds/ord_abc --data '{"order_amount":{"value":10000,"currency":"INR"}}'
pinelabs raw PUT /subscriptions/plans/plan_xyz --data @plan.json
Webhooks
The webhooks group covers signature operations and event-type discovery. For receiving live events on localhost, use listen.
| Command | Description |
|---|---|
pinelabs webhooks verify | Verify a webhook signature. Flags: --secret, --file/--body/stdin, --webhook-id, --webhook-timestamp, --webhook-signature, --now |
pinelabs webhooks sign | Produce signed headers for a payload. Flags: --secret, --webhook-id, --webhook-timestamp, --body |
pinelabs webhooks events | List supported event types. Flags: --prefix, --json |
pinelabs webhooks list and pinelabs webhooks get (endpoint management) are not implemented today.
listen
Open a secure WebSocket to the Pine Labs relay and forward events to a local URL.
pinelabs listen --forward-to http://localhost:3000/webhooks
| Flag | Default | Description |
|---|---|---|
--forward-to <url> | required | Local URL to POST events to. |
--events <list> | all events | Comma-separated event-type filter. Trailing .* is supported. |
--skip-verify | false | Skip signature verification (development only). |
--header, -H k:v | — | Extra headers to add on every forwarded request (repeatable). |
--print-secret | false | Print the signing secret on connect, then exit. |
--print-json | false | Mirror each accepted event as one JSON line on stdout. |
Signature scheme. Pine Labs webhooks use three headers — webhook-id, webhook-timestamp, webhook-signature — with HMAC-SHA256 over id.timestamp.body and a base64-encoded secret.
Fail-fast on missing secret. If signature verification is enabled (the default) and no signing secret is available, listen exits non-zero immediately with a remediation message.
Reliability. Failed deliveries are written to a local on-disk spool (see Events). On pinelabs events replay-spool:
- Body and
webhook-idare preserved verbatim. - When
webhook_secretis configured, replay re-signs with the current timestamp (old timestamps are rejected by the 5-minute tolerance window). - Without
webhook_secret, original headers are forwarded verbatim. - Sends at most one copy per
event_idper invocation (duplicates pruned before any POST).
trigger
Synthesize a webhook event by name. Triggers run server-side and the event flows back through the relay.
pinelabs trigger payment.success
pinelabs trigger --list # available fixtures
pinelabs trigger payment.success --add amount.value=15000
pinelabs trigger payment.success --raw '{"id":"evt_x","data":{"order_id":"ord_abc"}}'
| Flag | Default | Description |
|---|---|---|
--list | false | List available fixtures and exit. |
--add k=v | — | Add or override a field on the fixture. Dotted keys nest. Repeatable. |
--raw <json> | — | Replace the entire payload with this JSON (advanced). |
Use sandbox only.
pinelabs triggercalls the configured API's/events/triggerendpoint. Depending on the sandbox/backend fixture, that endpoint may create real sandbox transactions, refunds, or other objects. Never run against a live/production profile unless you fully understand the downstream side effects. Confirmpinelabs whoamishows a sandbox base URL first.
Events (replay)
Failed forward attempts during listen are appended to a local spool at ~/.pinelabs/spool.jsonl (JSON Lines, de-duplicated by event_id).
| Command | Description |
|---|---|
pinelabs events resend <event-id> | Ask Pine Labs to re-deliver a previously-sent event |
pinelabs events replay-spool | Re-POST spool entries to a URL with re-signed headers. Flags: --forward-to <url> |
Logs
Every CLI invocation appends a structured entry to ~/.pinelabs/audit.log. Bearer tokens, client secrets, card numbers, CVVs, and other PCI/PII are redacted before the entry is written.
| Command | Description |
|---|---|
pinelabs logs tail | Follow the local audit log. Flags: --remote, --method, --interval |
pinelabs logs path | Print the audit log path |
pinelabs logs show | Print the last 50 audit entries |
This is the artifact Pine Labs Support will ask for when triaging an integration issue.
MCP
Advanced feature for AI-assisted workflows. Optional — every command group above works without it.
The CLI ships an MCP (Model Context Protocol) JSON-RPC client for tooling integrations.
| Command | Description |
|---|---|
pinelabs mcp ping | Health-check the configured MCP server |
pinelabs mcp tools | List tools exposed by the MCP server |
pinelabs mcp call <tool-name> | Invoke a tool. Flags: --input, --yes |
ask (AI-assisted, advanced)
pinelabs ask "find the most recent failed payment for order ord_abc"
| Flag | Default | Description |
|---|---|---|
--plan | false | Show the planned tool calls without executing them. |
--yes | false | Skip confirmation for non-readonly tools (CI use). |
Confirmation required. The planner proposes tool calls; the CLI validates and you confirm before any non-readonly tool runs. Idempotency keys for write operations are deterministically derived so plans are safely re-runnable. Refunds, payouts, and subscription cancellation always require confirmation.
Output format
The CLI follows a strict stdout/stderr split:
- Resource commands emit pretty-printed JSON on stdout.
- Status / progress commands print a short status line to stderr.
- Confirmation prompts and warnings go to stderr.
This means … | jq is always safe on resource commands:
pinelabs orders create ... | jq '.order_id'
pinelabs refunds create --yes ... | jq '.refund_id'
pinelabs payments list --order-id ord_abc | jq '.[0]'
There is no --output/-o flag and no table/yaml/csv formatter today.
CI usage
- name: Smoke test against Pine Labs sandbox
env:
PINELABS_CLIENT_ID: ${{ secrets.PINELABS_CLIENT_ID }}
PINELABS_CLIENT_SECRET: ${{ secrets.PINELABS_CLIENT_SECRET }}
PINELABS_CLI_VERSION: 1.0.1
run: |
npm install -g @pinelabs/cli@${PINELABS_CLI_VERSION}
pinelabs login
pinelabs orders create \
--order-amount 100 --currency INR \
--merchant-order-reference ci_${{ github.run_id }} \
--idempotency-key ci_${{ github.run_id }} | jq -e '.order_id'
Alternatively, use npx for zero-install CI:
- name: Smoke test (npx, no install step)
env:
PINELABS_CLIENT_ID: ${{ secrets.PINELABS_CLIENT_ID }}
PINELABS_CLIENT_SECRET: ${{ secrets.PINELABS_CLIENT_SECRET }}
run: |
npx @pinelabs/cli@1.0.1 login
npx @pinelabs/cli@1.0.1 orders create \
--order-amount 100 --currency INR \
--merchant-order-reference ci_${{ github.run_id }} \
--idempotency-key ci_${{ github.run_id }} | jq -e '.order_id'
For non-interactive runs:
- Pass
--yesto skip confirmation prompts on destructive commands. - Pass
--idempotency-keyexplicitly so retries are safe. - Pin a specific version (
@1.0.1) so unexpected releases don't break your pipeline. - Log
pinelabs versionwith every run for an audit trail.
Security model
- Credentials. OAuth
client_secretis never persisted by default; only a short-livedaccess_tokenis.--directmode stores theclient_idonly;--direct --store-secretis opt-in and warns loudly. - File modes.
~/.pinelabs/config.jsonis0600; the parent~/.pinelabs/is0700. Writes are atomic (tmp+rename). - Secret redaction. The audit log masks bearer tokens, client secrets, card numbers, CVVs, and other PCI/PII before write.
- Egress-only network. The relay is outbound WSS — no inbound ports, no public tunnels, no localhost exposure.
- Idempotency. Write operations carry an
Idempotency-Key. The MCP executor derives keys deterministically from tool inputs. - Never commit your config directory. Add
~/.pinelabs/to.gitignore:
# Pine Labs CLI local state — never commit
.pinelabs/
Found a vulnerability? Email security@pinelabs.com.
Troubleshooting
Debug checklist
When something looks wrong, run these four in order:
pinelabs whoami— confirms the active profile, masked credentials, and environment.pinelabs doctor— 7-check self-diagnosis. Exits non-zero on failure.pinelabs config get webhook_secret— confirms the signing secret matches your dashboard.pinelabs logs tail— follow the local audit log.
pinelabs login fails with "set PINELABS_CLIENT_ID and PINELABS_CLIENT_SECRET"
Both env vars are required. Export them in the same shell:
export PINELABS_CLIENT_ID="your_client_id_here"
export PINELABS_CLIENT_SECRET="your_client_secret_here"
pinelabs login
token exchange failed on login
Your account may not support OAuth token exchange. Fall back to direct header auth:
pinelabs login --direct
Subsequent commands fail with auth errors after --direct
Without --store-secret, the secret is not on disk. Re-export PINELABS_CLIENT_SECRET in your shell, or re-run pinelabs login --direct --store-secret (CI use only).
listen drops events with signature errors
The webhook secret stored on the profile doesn't match what the relay is using. For local dev only, pass --skip-verify. In real environments:
pinelabs config set webhook_secret "your_webhook_secret_here"
Replay didn't drain my spool
pinelabs events replay-spool --forward-to ... only acknowledges entries that get a 2xx from your handler. Check ~/.pinelabs/spool.jsonl and pinelabs logs tail to see why entries are failing.
Limitations and roadmap
The Pine Labs CLI follows semver from 1.0.0. The underlying Plural APIs are stable; the deterministic command paths, webhook relay/replay flow, audit trail, and MCP guardrails are built for real integration workflows.
Versioning:
- Plural APIs: stable, governed by the upstream API versioning policy.
- CLI surface: semver from
1.0.0. Breaking changes bump the major version. Pin to a specific version in CI. - Audit log / spool format: considered internal, may change between minor versions.
What ships today:
- Pre-built native binaries (macOS, Linux, Windows) via npm
- Orders, payments, refunds, payouts, subscriptions, settlements
- Webhook relay with signature verification, spool, and replay
- OAuth + direct-header auth with profile isolation
- Audit log with PCI/PII redaction
- Shell completion via
pinelabs completion bash|zsh|fish|powershell - MCP-backed
askwith deterministic guardrules rawescape hatch for any Plural endpoint
What's next:
Upcoming work includes output formatting (--output table|csv), --verbose/--debug flags, and additional install channels (Homebrew, Docker). Email developer-tools@pinelabs.com to influence priority.
Resources
- npm package: @pinelabs/cli
- Developer portal: developer.pinelabsonline.com
- API documentation: Plural API reference
- Security: report vulnerabilities to security@pinelabs.com
- Support: developer-tools@pinelabs.com
License
Apache 2.0.
