Build your integrationTargets

Add a custom workflow

Build a resumable CLI workflow with prerequisites, polling, and human handoffs, then preserve it through regeneration.

A custom workflow combines generated API calls into a task your customers recognize. You own its business rules and recovery behavior. Typeship supplies the generated client and, for linked repository Deliveries, preserves non-overlapping package edits through regeneration.

This example adds parcel-label to a generated CLI for Parcel, a fictional delivery service. It checks a shipment's address, requests a label, and polls until the label is ready. The included local mock lets you run it without an account or a live shipping service.

Choose how to compose the task

ApproachUse it whenAuthentication and state
Wrap the generated CLIExtend the existing command experience and let customers log in once.Subprocess calls reuse saved profiles, refresh, and generated error handling. Your wrapper owns the workflow state.
Call the generated client directly, as belowCompose typed calls with control over deadlines, cancellation, and resume state.Your code supplies credentials. This recipe reads environment variables and does not load saved CLI profiles.

Prefer the wrapper approach when sharing the existing login is a requirement. Both approaches can implement multi-step workflows; direct client access does not automatically integrate with the generated CLI's credential store.

Generate and add the workflow

You need Node.js 22 or later, npm, and curl. Generate into a new directory:

npx -y @typeship-ax/cli@latest generate run \
  --definition '{"url":"https://typeship.dev/examples/parcel-workflow/openapi.yaml"}' \
  --target '{"generator":"cli"}' \
  --config '{"cli":{"command_name":"parcel"}}' \
  --out ./parcel-cli
cd parcel-cli

The example Definition declares three API operations. Its api.parcel.example server is fictional. Download the customer-authored workflow, command, tests, and mock into the generated package:

mkdir -p src tests
curl --fail -sS https://typeship.dev/examples/parcel-workflow/label-workflow.ts.txt -o src/label-workflow.ts
curl --fail -sS https://typeship.dev/examples/parcel-workflow/label-cli.ts.txt -o src/label-cli.ts
curl --fail -sS https://typeship.dev/examples/parcel-workflow/label-workflow.test.mjs -o tests/label-workflow.test.mjs
curl --fail -sS https://typeship.dev/examples/parcel-workflow/mock-server.mjs -o mock-server.mjs

Add the command and test script while retaining the existing parcel command and package scripts:

Register the workflow
npm pkg set 'bin.parcel-label=dist/label-cli.js' \
  'scripts.test:workflow=node --test tests/label-workflow.test.mjs'
npm install
npm run build
npm run test:workflow
node dist/label-cli.js --help

The tests exercise completion, prerequisites, handoffs, timeouts, cancellation, failure recovery, and stable creation keys. The command uses the generated client inside this CLI package; it does not require a separately generated SDK. Both compiled workflow files are included by the package's existing dist packaging rule.

Run the complete task locally

In one terminal, start the mock from the package directory:

node mock-server.mjs

In a second terminal, enter the same directory and run:

export PARCEL_BASE_URL=http://127.0.0.1:4318
export PARCEL_TOKEN=example-token
node dist/label-cli.js start shp_123 label-for-shp_123

After about two seconds, expect:

{"status":"ready","label_id":"lbl_1","download_url":"https://files.parcel.example/label.pdf"}

The download URL is illustrative; the mock creates no real label or file. It stores labels and idempotency keys in memory, so restarting it clears them. Set PARCEL_MOCK_PORT before starting the mock if port 4318 is occupied, and use the printed URL for PARCEL_BASE_URL.

The custom command reads PARCEL_TOKEN and PARCEL_BASE_URL explicitly. It does not load the generated CLI's saved login profiles. A published package installs both parcel and parcel-label; before publishing, use the node dist/... commands above.

Handle each outcome

The workflow's steps are explicit:

  1. Read the shipment. An unverified address returns a review URL before any label is requested.
  2. Request one label with the caller's idempotency key.
  3. Poll its ID once per second, with a 30-second deadline across the workflow. The deadline and Ctrl-C also cancel an active request.
  4. Return the terminal state, a browser handoff, or a resumable label ID.
ResultExit codeYour next step
ready0Use download_url. The command does not download it.
action_required2Review message and action_url. After completing the browser step, resume a returned label ID; an address handoff requires starting again with the same key.
pending2Save label_id. Resume when ready; reason distinguishes timeout from cancellation.
failed1Inspect the provider's message. Starting a replacement is a new business decision.
Error envelope1 or 2Read issues[].code and next_steps. Workflow context lives under detail, including stage, label_id, idempotency_key, and request_id when available.

Workflow results are JSON on stdout; error envelopes are JSON on stderr. The command reuses the generated CLI's issues[] and next_steps convention. Invalid arguments or a missing base URL return INVALID_USAGE with exit 2; a missing credential returns NO_AUTH with status: action_required and exit 1, before HTTP. Other workflow errors use CALL_FAILED with exit 1. Neither handoffs nor pending work count as success.

node dist/label-cli.js resume lbl_1

Resume only reads the existing label. It never repeats creation. The workflow disables automatic HTTP retries; the example API's contract lets you retry an uncertain create explicitly with the same shipment and key. For your API, verify its idempotency guarantees and retention period before adopting that recovery strategy. Persist the key and returned ID in your application's durable state when a process restart must be recoverable.

The mock demonstrates completion and idempotency. The included tests supply the handoff and failure responses. Adapt the workflow's states to your real API: hosted payment frames, authentication challenges, and approvals remain explicit browser or human steps. An OpenAPI description alone cannot determine that an end-to-end business task has completed.

Preserve it through regeneration

For a linked repository Delivery, add these files and the manifest edits to the Target's rolling Draft. Follow package check configuration to add a customer check named parcel-label-workflow with command npm run test:workflow, preserving existing checks. Keep the generated build, package, and public-entrypoint checks enabled.

Try a Definition update: add an optional string property named carrier to the Label schema and regenerate. Pull the updated Draft, rebuild, and run the workflow tests. The new model field, custom files, parcel-label executable, and test script should all remain present. Check the current Draft head's results before merging.

Preserving source does not establish that it still works with a changed API. Removing the label-read operation, for example, leaves a workflow that cannot compile. Resolve incompatible API changes and overlapping edits before accepting the updated package. See regeneration and conflicts.

Follow Review an API update to carry this workflow through an additive change, an overlapping edit, and a client method rename in a linked repository, then inspect the accepted Releases.

The initial download above is stateless and has no retained repository baseline. Its extraction process owns later updates. Use a linked repository Delivery for Typeship's preservation and review workflow; see adding Typeship to an API repository.

On this page