Skip to content

Scripting

Session caching for pipelines

When running multiple CLI commands in a pipeline, enable session caching to avoid redundant API calls:

#!/bin/bash
export AFFINITY_SESSION_CACHE=$(xaffinity session start) || exit 1
trap 'xaffinity session end; unset AFFINITY_SESSION_CACHE' EXIT

# Commands share cached metadata (field definitions, list resolution)
xaffinity list export "My List" | xaffinity person get

See Pipeline Optimization for details.

JSON output

Use --json (or --output json) for machine-readable output:

xaffinity whoami --json | jq

Response envelope

All JSON responses use a standard envelope structure:

{
  "ok": true,
  "command": {"name": "person get", "inputs": {"personSelector": "123"}, "modifiers": {}, "resolved": null},
  "data": {"person": {"id": 123, "firstName": "Alice", ...}},
  "meta": {"rateLimit": {...}, "pagination": {...}},
  "error": null
}
Field Description
ok true if command succeeded, false on error
command Object with name, inputs, modifiers, and resolved fields
data The entity or entities returned (keyed by type)
meta Rate limit info, pagination cursors, timing
error Error details when ok is false

Extracting entity data

Entity data is nested under .data.<entityType>:

# Get a person's name
xaffinity person get 123 --json | jq '.data.person.firstName'

# Get company ID after creation
xaffinity company create --name "Acme" --json | jq '.data.company.id'

# List all person IDs
xaffinity person ls --json | jq '.data.persons[].id'

Error handling in scripts

Check ok before processing data:

result=$(xaffinity person get 123 --json)
if echo "$result" | jq -e '.ok' > /dev/null; then
    echo "$result" | jq '.data.person'
else
    echo "Error: $(echo "$result" | jq -r '.error.message')" >&2
    exit 1
fi

Or use exit codes (non-zero on error):

if xaffinity person get 123 --json > /tmp/result.json 2>&1; then
    jq '.data.person' /tmp/result.json
else
    echo "Command failed" >&2
fi

Common patterns

Extract and assign to variable:

PERSON_ID=$(xaffinity person create --first-name "Test" --last-name "User" --json | jq -r '.data.person.id')

Process list results:

xaffinity person ls --all --json | jq -r '.data.persons[] | "\(.id)\t\(.firstName) \(.lastName)"'

Create shell alias for frequent access patterns:

# Add to ~/.bashrc or ~/.zshrc
alias xaff-person='xaffinity person get --json | jq .data.person'

# Usage: xaff-person 123

Timezone-safe datetime handling

Date/time flags (--after, --before, etc.) interpret naive strings as local time. For reproducible scripts across timezones, always use explicit UTC:

# Use Z suffix for explicit UTC
xaffinity interaction ls --type meeting --person-id 123 --after 2024-01-01T00:00:00Z --json

# Or use explicit timezone offset
xaffinity interaction ls --type meeting --person-id 123 --after 2024-01-01T00:00:00-05:00 --json

# Or use --days for relative date ranges
xaffinity interaction ls --type meeting --person-id 123 --days 30 --json

JSON output always returns datetimes in UTC (with +00:00 suffix). See the Datetime Handling Guide for full details on SDK vs CLI behavior.

Note: interaction ls auto-chunks date ranges > 1 year into API-compatible segments. For multi-year queries, dates are split transparently.

Pagination and resume

Some commands include resume tokens in meta.pagination.

  • meta.pagination is keyed by section name.
  • Resume cursor: meta.pagination.<section>.nextCursor (resume with --cursor <cursor>)
  • Treat cursors as opaque strings (some may look like URLs); don't parse them.

Note: interaction ls does not use pagination cursors. Instead, it auto-fetches all results within the specified date range, chunking as needed for ranges > 1 year.

Example (person list with query):

xaffinity person ls --query "alice" --json | jq -r '.meta.pagination.persons.nextCursor'
xaffinity person ls --query "alice" --cursor "$CURSOR" --json

Example (list inventory):

xaffinity list ls --json | jq -r '.meta.pagination.lists.nextCursor'
xaffinity list ls --cursor "$CURSOR" --json

Note: if you use --max-results and it truncates results mid-page, the CLI may omit pagination to avoid producing an unsafe resume token.

CSV Output

The --csv flag outputs CSV to stdout, making it composable with UNIX tools:

# Save to file
xaffinity list export 123 --csv > out.csv

# Pipe to other tools
xaffinity person ls --all --csv | wc -l
xaffinity list export 123 --csv | head -10

Note: --csv and --json are mutually exclusive. Use one or the other.

Machine-Readable Help

Use --help --json to get machine-readable command documentation. This is useful for:

  • Building automation tools that discover CLI capabilities
  • Generating command registries for AI integrations (like MCP servers)
  • Validating command arguments programmatically
xaffinity --help --json

Output Format

{
  "commands": [
    {
      "name": "person create",
      "description": "Create a person.",
      "category": "write",
      "destructive": false,
      "parameters": {
        "--first-name": {"type": "string", "required": false},
        "--last-name": {"type": "string", "required": false},
        "--email": {"type": "string", "required": false, "multiple": true}
      },
      "positionals": []
    },
    {
      "name": "person delete",
      "description": "Delete a person.",
      "category": "write",
      "destructive": true,
      "parameters": {
        "--yes": {"type": "flag", "required": false}
      },
      "positionals": [
        {"name": "PERSON_ID", "type": "int", "required": true}
      ]
    }
  ]
}

Command Metadata

Field Description
name Full command path (e.g., "person create", "list entry add")
description Human-readable description
category "read", "write", or "local" (no-network)
destructive true for delete commands
parameters Named options with type info
positionals Positional arguments with type info

Parameter Types

Type Description
string Text value
int Integer value
flag Boolean flag (no value)
choice One of allowed values (see choices array)

Parameters with "multiple": true can be specified multiple times.