---
title: "updateUseCase"
method: PUT
path: "/v1/integrations/{integrationId}/use-cases/{useCaseId}"
tags: ["integrations"]
---

# updateUseCase

`PUT /v1/integrations/{integrationId}/use-cases/{useCaseId}`

Update an existing use case configuration

## Path parameters

- `integrationId` string, uuid, required
- `useCaseId` string, uuid, required

## Request body

- union
  - UpdateInboundUseCaseRequest
    - `name` string — Use case name
    - `slug` string — URL-safe identifier for the use case. Recommended for portable cross-environment referencing. Can only be set once on use cases that don't have a slug yet. Immutable after being set.
    - `enabled` boolean — Whether the use case is enabled
    - `change_description` string — Optional description of this change (like a commit message)
    - `type` 'inbound' — Use case type
    - `configuration` InboundIntegrationEventConfiguration — Configuration for inbound use cases (ERP to epilot)
      - `entities` IntegrationEntity[] — Array of entity configurations for this event
        - `entity_schema` string, required — Target entity schema (e.g., 'contact', 'contract')
        - `unique_ids` string[], required — Array of attribute names that uniquely identify this entity. The _type hint for repeatable fields (e.g., email, phone) should be specified on the corresponding field definition in the fields array.
        - `jsonataExpression` string — Optional JSONata expression to pre-process the event data before field mapping
        - `enabled` union — Controls whether this entity mapping should be processed. Can be a boolean or a JSONata expression (string) that evaluates to a boolean.
          - boolean
          - string
        - `mode` 'upsert' | 'delete' | 'purge' | 'upsert-prune-scope-purge' | 'upsert-prune-scope-delete' — Operation mode for entity mapping: - 'upsert': Create or update the entity (default) - 'delete': Soft delete the entity (marks as deleted) - 'purge': Hard delete the entity (permanent removal) - 'upsert-prune-scope-purge': Upsert entities from array, then purge entities in scope that weren't upserted - 'upsert-prune-scope-delete': Upsert entities from array, then soft delete entities in scope that weren't upserted
        - `scope` PruneScopeConfig — Scope configuration for upsert-prune-scope modes. Defines how to find entities that should be pruned if not in the upsert payload. The scope is resolved against the original event payload (not individual array items).
          - `scope_mode` 'relations' | 'query', required — Scope mode for finding entities to prune: - 'relations': Find scope by looking at all entities related to a specific entity (both direct and reverse relations) - 'query': Find scope entities directly via query parameters
          - `schema` string — For 'relations' mode: The schema of the entity to find (e.g., 'billing_account'). Not used for 'query' mode.
          - `unique_ids` RelationUniqueIdField[] — For 'relations' mode: How to identify the scope entity from the payload. Not used for 'query' mode.
            - `attribute` string, required — Target attribute name in the related entity
            - `_type` 'email' | 'phone' — Type hint for repeatable fields that require special search handling. These fields are stored as arrays of objects (e.g., email: [{ email: "value" }]).
            - `field` string — Source field name from the event data
            - `jsonataExpression` string — JSONata expression to compute the value
            - `constant` unknown
          - `query` RelationUniqueIdField[] — For 'query' mode: Direct query parameters to find scope entities. Not used for 'relations' or 'reverse-relations' modes.
            - `attribute` string, required — Target attribute name in the related entity
            - `_type` 'email' | 'phone' — Type hint for repeatable fields that require special search handling. These fields are stored as arrays of objects (e.g., email: [{ email: "value" }]).
            - `field` string — Source field name from the event data
            - `jsonataExpression` string — JSONata expression to compute the value
            - `constant` unknown
        - `fields` IntegrationEntityField[], required — Field mapping definitions
          - `attribute` string, required — Target attribute name
          - `field` string — Source field name or JSONPath expression (if starts with $)
          - `jsonataExpression` string — JSONata expression for transformation
          - `constant` unknown
          - `_type` 'email' | 'phone' — Type hint for repeatable fields that require special search handling. These fields are stored as arrays of objects (e.g., email: [{ email: "value" }]).
          - `enabled` union — Controls whether this field mapping should be processed. Can be a boolean or a JSONata expression (string) that evaluates to a boolean. Defaults to true if omitted.
            - boolean
            - string
          - `relations` RelationConfig
            - `operation` '_set' | '_append' | '_append_all', required — Relation operation: - '_set': Replace all existing relations with the specified items - '_append': Add new unique items to existing relations (deduplicates by entity_id) - '_append_all': Add all items to existing relations (no deduplication, allows duplicates)
            - `items` RelationItemConfig[] — Array of relation item configurations
              - …
            - `jsonataExpression` string — JSONata expression that returns relation items array (alternative to 'items')
          - `relation_refs` RelationRefsConfig — Configuration for relation references ($relation_ref). Relation references link to a specific item within a repeatable attribute on a related entity. Common use case: referencing a specific address within a contact's address list.
            - `operation` '_set' | '_append' | '_append_all', required — Relation reference operation: - '_set': Replace all existing relation_refs with the specified items - '_append': Add new unique items to existing relation_refs (deduplicates by entity_id + _id) - '_append_all': Add all items to existing relation_refs (no deduplication, allows duplicates)
            - `items` RelationRefItemConfig[] — Array of relation reference item configurations
              - …
            - `jsonataExpression` string — JSONata expression that returns relation_ref items array (alternative to 'items')
          - `file_proxy_url` union — Auto-constructs a file proxy download URL. orgId and integrationId are injected from context. Exactly one of use_case_id or use_case_slug must be provided. Using use_case_slug is recommended as it is portable across environments.
            - object
              - …
            - object
              - …
          - `portal_ref` union — Resolves to a property of one of the calling organization's epilot portal configurations at runtime, replacing hard-coded environment-specific portal UUIDs in inbound mappings. Matched portals are sorted ascending by `(_created_at, portal_id)`; portals without `_created_at` sort first (treated as oldest). When `select: "single"` matches more than one portal, the resolver still returns the oldest match and emits a `PORTAL_REF_AMBIGUOUS` warning.
            - object
              - …
            - object
              - …
          - `env_var_ref` union — Resolves to an org-scoped environment variable from the epilot environments-api service at runtime, replacing hard-coded environment-specific values (URLs, prefixes, identifiers) in inbound mappings. Secrets (`SecretString` values) are never exposed; the runtime treats both "missing key" and "secret-typed key" as identical `undefined` outcomes (no info disclosure via error code). For secret-resolving contexts (e.g. authorization headers in managed-call or file-proxy step configurations), use the templated `{{ env.<key> }}` syntax instead — that mechanism does decrypt secrets.
            - object
              - …
            - object
              - …
      - `meter_readings` IntegrationMeterReading[] — Array of meter reading configurations for this event
        - `jsonataExpression` string — Optional JSONata expression to extract meter reading items from the event data. If not provided, the entire payload is used as the reading data. Useful when you need to extract an array of readings from a nested structure (e.g., "$.readings").
        - `reading_matching` 'external_id' | 'strict-date' — Strategy for matching incoming readings against existing readings. - 'external_id': Match readings by external_id attribute (default behavior) - 'strict-date': Match by meter_id + counter_id + direction + date (German timezone). Useful when readings originate from ECP and are echoed back by the ERP with truncated timestamps.
        - `mode` 'upsert' | 'delete' | 'upsert-prune-scope' — Operation mode for meter reading mapping: - 'upsert': Create or update meter readings (default) - 'delete': Delete the meter reading - 'upsert-prune-scope': Upsert readings from array, then delete all other readings for the same meter+counter that weren't upserted
        - `scope` MeterReadingPruneScopeConfig — Scope configuration for meter reading upsert-prune-scope mode. The scope is all readings for the same meter + counter.
          - `source` string — Optional source filter. When set, only readings with this source are eligible for pruning (e.g., 'ERP' to only prune ERP-synced readings).
        - `meter` MeterUniqueIdsConfig, required
          - `unique_ids` RelationUniqueIdField[], required — Array of unique identifier field mappings
            - `attribute` string, required — Target attribute name in the related entity
            - `_type` 'email' | 'phone' — Type hint for repeatable fields that require special search handling. These fields are stored as arrays of objects (e.g., email: [{ email: "value" }]).
            - `field` string — Source field name from the event data
            - `jsonataExpression` string — JSONata expression to compute the value
            - `constant` unknown
        - `meter_counter` MeterUniqueIdsConfig
          - `unique_ids` RelationUniqueIdField[], required — Array of unique identifier field mappings
            - `attribute` string, required — Target attribute name in the related entity
            - `_type` 'email' | 'phone' — Type hint for repeatable fields that require special search handling. These fields are stored as arrays of objects (e.g., email: [{ email: "value" }]).
            - `field` string — Source field name from the event data
            - `jsonataExpression` string — JSONata expression to compute the value
            - `constant` unknown
        - `fields` IntegrationEntityField[], required — Field mapping definitions for meter reading attributes
          - `attribute` string, required — Target attribute name
          - `field` string — Source field name or JSONPath expression (if starts with $)
          - `jsonataExpression` string — JSONata expression for transformation
          - `constant` unknown
          - `_type` 'email' | 'phone' — Type hint for repeatable fields that require special search handling. These fields are stored as arrays of objects (e.g., email: [{ email: "value" }]).
          - `enabled` union — Controls whether this field mapping should be processed. Can be a boolean or a JSONata expression (string) that evaluates to a boolean. Defaults to true if omitted.
            - boolean
            - string
          - `relations` RelationConfig
            - `operation` '_set' | '_append' | '_append_all', required — Relation operation: - '_set': Replace all existing relations with the specified items - '_append': Add new unique items to existing relations (deduplicates by entity_id) - '_append_all': Add all items to existing relations (no deduplication, allows duplicates)
            - `items` RelationItemConfig[] — Array of relation item configurations
              - …
            - `jsonataExpression` string — JSONata expression that returns relation items array (alternative to 'items')
          - `relation_refs` RelationRefsConfig — Configuration for relation references ($relation_ref). Relation references link to a specific item within a repeatable attribute on a related entity. Common use case: referencing a specific address within a contact's address list.
            - `operation` '_set' | '_append' | '_append_all', required — Relation reference operation: - '_set': Replace all existing relation_refs with the specified items - '_append': Add new unique items to existing relation_refs (deduplicates by entity_id + _id) - '_append_all': Add all items to existing relation_refs (no deduplication, allows duplicates)
            - `items` RelationRefItemConfig[] — Array of relation reference item configurations
              - …
            - `jsonataExpression` string — JSONata expression that returns relation_ref items array (alternative to 'items')
          - `file_proxy_url` union — Auto-constructs a file proxy download URL. orgId and integrationId are injected from context. Exactly one of use_case_id or use_case_slug must be provided. Using use_case_slug is recommended as it is portable across environments.
            - object
              - …
            - object
              - …
          - `portal_ref` union — Resolves to a property of one of the calling organization's epilot portal configurations at runtime, replacing hard-coded environment-specific portal UUIDs in inbound mappings. Matched portals are sorted ascending by `(_created_at, portal_id)`; portals without `_created_at` sort first (treated as oldest). When `select: "single"` matches more than one portal, the resolver still returns the oldest match and emits a `PORTAL_REF_AMBIGUOUS` warning.
            - object
              - …
            - object
              - …
          - `env_var_ref` union — Resolves to an org-scoped environment variable from the epilot environments-api service at runtime, replacing hard-coded environment-specific values (URLs, prefixes, identifiers) in inbound mappings. Secrets (`SecretString` values) are never exposed; the runtime treats both "missing key" and "secret-typed key" as identical `undefined` outcomes (no info disclosure via error code). For secret-resolving contexts (e.g. authorization headers in managed-call or file-proxy step configurations), use the templated `{{ env.<key> }}` syntax instead — that mechanism does decrypt secrets.
            - object
              - …
            - object
              - …
  - UpdateOutboundUseCaseRequest
    - `name` string — Use case name
    - `slug` string — URL-safe identifier for the use case. Recommended for portable cross-environment referencing. Can only be set once on use cases that don't have a slug yet. Immutable after being set.
    - `enabled` boolean — Whether the use case is enabled
    - `change_description` string — Optional description of this change (like a commit message)
    - `type` 'outbound' — Use case type
    - `configuration` OutboundIntegrationEventConfiguration — Configuration for outbound use cases. Defines the event that triggers the flow and the webhook mappings.
      - `event_catalog_event` string, required — The Event Catalog event name that triggers this outbound flow
      - `event_filter` string — JSONata boolean predicate over the hydrated event payload. The use case handles the event only when this evaluates truthy; when absent it handles every event of its name, which is the behaviour of every pre-existing configuration. This is where event scoping belongs — narrowing to certain ticket purposes, contract types or channels. Evaluation input is the full hydrated event, so relation nodes such as `ticket` and `contact` are populated. An expression that throws is treated as no match and logged, so one malformed filter cannot block the other use cases subscribed to the same event.
      - `mappings` OutboundMapping[], required — List of mappings that transform and deliver the event
        - `id` string, uuid — Unique identifier for this mapping
        - `name` string, required — Human-readable name for this mapping
        - `jsonata_expression` string — JSONata expression to transform the event payload. Required for webhook delivery, ignored for poll delivery, and rejected for file_proxy delivery — a file_proxy payload is built by the referenced use case's `params_mapping`, so accepting an expression here would silently do nothing.
        - `enabled` boolean, required — Whether this mapping is active
        - `delivery` union, required — Configuration for how the event should be delivered. webhook = push delivery via svc-webhooks (JSONata-transformed payload); poll = pull-based queue delivery where the consumer fetches items via the poll API (raw event payload); file_proxy = one push per event attachment to an external document system, through a file_proxy use case (JSONata-transformed payload carrying the file bytes)
          - WebhookDeliveryConfig — Push delivery of the transformed event to a webhook via svc-webhooks
            - `type` 'webhook', required — Delivery mechanism type
            - `webhook_id` string, required — Reference to the webhook configuration in svc-webhooks
            - `webhook_name` string — Cached webhook name for display purposes
            - `webhook_url` string — Cached webhook URL for display purposes
          - PollDeliveryConfig — Pull-based queue delivery. Items carry the raw standardized event-catalog payload; no JSONata mapping is applied in poll mode. Consumers fetch and acknowledge items via the poll API.
            - `type` 'poll', required — Delivery mechanism type
            - `retention_days` integer — How long undelivered queue items are retained before expiry
            - `poison_policy` 'dead_letter' | 'block' — What happens when an item exhausts max_delivery_attempts: dead_letter routes the exhausted item to the dead-letter queue and advances past it so the stream keeps flowing; block halts the queue at that item until operator/consumer action removes it.
            - `max_delivery_attempts` integer — Maximum delivery attempts before the poison_policy is applied
          - FileProxyDeliveryConfig — Push delivery to an external document system through a `file_proxy` use case. A pure pointer, deliberately. The outbound use case decides WHEN to deliver — its event name plus `event_filter` — and the referenced `file_proxy` use case decides WHAT and HOW: which items to fan out over (`fan_out`), what values to build (`params_mapping`, `lookups`, `constants`), and the steps that send them. Keeping every file concern on the file_proxy side is what lets one upload recipe be shared by several event subscriptions without duplicating any of it. `jsonata_expression` on the mapping is NOT used for this delivery type and is rejected. The slug is resolved at delivery time, not at save time, so use cases can be saved in any order; a dangling reference surfaces as a `USE_CASE_NOT_FOUND` monitoring event naming the referencing mapping.
            - `type` 'file_proxy', required — Delivery mechanism type
            - `use_case_slug` string, required — Slug of a `file_proxy` use case with `direction: upload` in the SAME integration.
        - `created_at` string, date-time — Timestamp when the mapping was created
        - `updated_at` string, date-time — Timestamp when the mapping was last updated
      - `ack_tracking` 'on' | 'off' — Whether this use case participates in the acknowledgement protocol. `on` (the default, and the behaviour of every pre-existing use case) records an ACK_PENDING on each event and expects the consumer to confirm receipt via `POST /v1/erp/tracking/acknowledgement`; unconfirmed events raise ACK_TIMEOUT after the timeout window. `off` opts the use case out entirely: no tracking row, no ACK_PENDING, no ACK_TIMEOUT. Set it for consumers that never acknowledge — otherwise every event produces a guaranteed timeout warning — and for deliveries that already keep their own durable per-item record, such as `file_proxy`. The tracking row is per-event, not per-use-case, so it is suppressed only when EVERY enabled use case matching the event has opted out.
  - UpdateFileProxyUseCaseRequest
    - `name` string — Use case name
    - `slug` string — URL-safe identifier for the use case. Recommended for portable cross-environment referencing. Can only be set once on use cases that don't have a slug yet. Immutable after being set.
    - `enabled` boolean — Whether the use case is enabled
    - `change_description` string — Optional description of this change (like a commit message)
    - `type` 'file_proxy' — Use case type
    - `configuration` FileProxyUseCaseConfiguration — Configuration for file_proxy use cases. Defines how to authenticate and move files between epilot and an external document system, in either direction (see `direction`). **Download** (`direction: download`, the default) fetches a file from the external system and serves it to a browser. The download URL always requires `orgId`, `integrationId`, and either `useCaseSlug` (recommended) or `useCaseId` (legacy UUID) as query parameters. The `orgId` is included in the signed URL to establish organization context without requiring authentication. Additional use-case-specific parameters are declared in the `params` array. `response` is REQUIRED for download use cases. **Upload** (`direction: upload`) pushes epilot files to the external system. It is not reachable over the download endpoint; an outbound use case points at it via a `file_proxy` delivery, and this configuration owns everything about what gets sent: `fan_out` decides how many deliveries one event produces, `params_mapping` builds the values, and the `steps` place those values into requests via `{{ params.* }}`. `upload` and `params_mapping` are REQUIRED and `response` MUST be omitted. OpenAPI 3.0 cannot express this conditional requiredness, so it is enforced by the server-side validator, which returns an explicit message naming the offending field.
      - `direction` 'download' | 'upload' — Direction of file travel. `download` (default) pulls a file from the external system into epilot; `upload` pushes an epilot file out to the external system. Omitted means `download`, so every pre-existing configuration keeps its exact meaning. Note this is the direction of the FILE, not the epilot use-case type — an `upload` file_proxy use case is still a `file_proxy` use case, never an `outbound` one.
      - `upload` FileProxyUploadConfig — Upload-side settings for a file_proxy use case with `direction: upload`. Everything about WHAT is sent lives on the outbound mapping (see `FileProxyDeliveryConfig`); this object only governs HOW the transfer is bounded and judged.
        - `max_file_bytes` integer — Per-file ceiling for this use case, in bytes. Files above it fail terminally with `FILE_TOO_LARGE` before any bytes are fetched. Defaults to — and is clamped by — the platform ceiling of 100 MiB (104857600).
        - `max_delivery_attempts` integer — Maximum delivery attempts per item before the delivery is marked failed and the message is dead-lettered. Attempts are spaced by an exponential, jittered backoff spanning roughly 16 hours over the default 8 attempts, so an ERP maintenance window does not exhaust them. Lives here rather than on the outbound delivery because how hard to retry is a property of the transport — the same judgement as `max_file_bytes` — and the outbound delivery is a pure pointer.
        - `success_when` string — Optional JSONata predicate evaluated against the final step result (`{ statusCode, headers, body }`) to decide whether the external system really accepted the file. When omitted, any 2xx counts as delivered. Use this for systems that return 200 with an error envelope.
        - `external_id` string — Optional JSONata expression over the step results yielding the external system's identifier for the stored document. Recorded on the delivery record and on the `FILE_PROXY_UPLOADED` monitoring event so an operator can find the document in the target system.
      - `fan_out` FileProxyFanOutConfig — Splits one event into several independent deliveries. Mirrors the inbound mapping idiom, where an entity's JSONata expression returning an array produces one entity update per element. Made explicit with a toggle here because an upload is also legitimately used without splitting, and because auto-detecting "array means split" would make a single-element result ambiguous. Each resulting delivery is fully independent: its own idempotency record, its own retry schedule, its own monitoring events. A four-item event can therefore end up three-of-four delivered, which is the honest state to report. The split is evaluated ONCE, when the event is enqueued, so item indices — and therefore idempotency keys — stay stable across retries.
        - `enabled` boolean, required — When false (or absent), the event produces exactly one delivery and `$item` is not bound in `params_mapping`.
        - `split_expression` string — JSONata over the event that MUST return an array; one delivery is created per element, bound as `$item` in `params_mapping`. Required when `enabled` is true. Keep this a plain projection — scoping which events are handled at all belongs in the outbound use case's `event_filter`, not here. A non-array result fails the event with `FAN_OUT_INVALID_RESULT`; an empty array produces no deliveries and one info-level `FAN_OUT_EMPTY`, which is the normal outcome for a catch-all subscription seeing an event with nothing to send.
      - `params_mapping` string — Upload-only, REQUIRED when `direction` is `upload`. JSONata expression evaluated once per fan-out item, producing the `params` object that step templates read as `{{ params.* }}`. The evaluation root is the hydrated event, so `contact.customer_pin` and `ticket._purpose` are reachable directly, unprefixed. **Everything per-item is a `$`-prefixed JSONata binding**: `$item` (the fan-out element, absent when `fan_out` is disabled), `$file_base64` and `$file` (`{filename, mime_type, size_bytes}`) for the resolved file, plus `$constants`, `$lookups`, `$ack_id`, `$germanDate(iso)` and `$now()`. Writing `item.filename` instead of `$item.filename` yields nothing — it reads a field named `item` on the event, which does not exist. Must evaluate to an object. `constants` are shallow-merged underneath the result, so the expression wins on any key collision.
      - `lookups` object — Upload-only. Named translation tables resolved BEFORE `params_mapping` runs and bound as `$lookups`, so an expression reads `$lookups.documentType` rather than carrying a conditional chain. Deliberately generic: the next ERP calls the same concept `Belegart`.
      - `constants` object — Upload-only. Fixed values shallow-merged UNDERNEATH the `params_mapping` result — the expression wins on key collisions, constants only add. Use for the unchanging strings (tenant, sender, channel) that would otherwise be repeated in every expression.
      - `file_source` string — Upload-only. JSONata returning the attachment-shaped object (`entity_id`, optionally `s3ref`) whose bytes should be fetched for this delivery. The evaluation root is the event; the fan-out element is the `$item` binding, same as in `params_mapping`. Usually unnecessary: when the fan-out item is itself attachment-shaped it is used directly. Supply this only when splitting over something that is not the attachment — for example one delivery per meter reading, each carrying a file referenced from elsewhere in the event. When nothing resolves, no file is fetched and `file_base64` is undefined, which is valid for a fan-out that sends metadata only.
      - `required_params` string[] — Upload-only. Params that MUST be present after `params_mapping` runs. Any listed name resolving to null or undefined fails the delivery terminally with `REQUIRED_PARAM_MISSING` before a single step executes. This is the generic net behind a lookup's `on_miss: fail`: it catches a required field going missing for any reason, so the external system never receives a body that is silently short a field its API requires.
      - `secure_proxy` FileProxySecureProxyAttachment
        - `use_case_slug` string, required — Slug of the secure_proxy use case in the same integration.
      - `auth` FileProxyAuth
        - `type` 'oauth2_client_credentials' | 'oauth2_password', required — Authentication type
        - `token_url` string, required — Handlebars template for the OAuth2 token endpoint URL
        - `client_id` string, required — Handlebars template for the OAuth2 client ID
        - `client_secret` string, required — Handlebars template for the OAuth2 client secret
        - `scope` string — Optional OAuth2 scope
        - `audience` string — Handlebars template for the OAuth2 audience parameter
        - `resource` string — Handlebars template for the OAuth2 resource parameter
        - `username` string — Handlebars template for the OAuth2 resource owner username. Required when type is oauth2_password.
        - `password` string — Handlebars template for the OAuth2 resource owner password. Required when type is oauth2_password.
        - `body_params` object — Additional key-value pairs to include in the token request body. Values support Handlebars templates.
        - `headers` object — Additional headers to include in the token request. Values support Handlebars templates.
        - `query_params` object — Additional query parameters to append to the token URL. Values support Handlebars templates.
      - `params` FileProxyParam[] — Download-only. Additional use-case-specific parameters expected in the download URL query string (beyond the required orgId, integrationId, and useCaseSlug or useCaseId). Rejected when `direction` is `upload`.
        - `name` string, required — Parameter name as it appears in the query string
        - `required` boolean, required — Whether this parameter is required
        - `description` string — Human-readable description of the parameter
      - `allowed_origins` string[] — Download-only. Additional origins permitted to call /download for this use case (CORS, exact match). Portal origins are always allowed. Rejected when `direction` is `upload`.
      - `steps` FileProxyStep[], required — Ordered list of HTTP steps to execute. For `download` these retrieve the file; for `upload` they deliver it, each assembling its own request body from `{{ params.* }}` built by `params_mapping`.
        - `url` string, required — Handlebars template for the request URL
        - `method` 'GET' | 'POST' | 'PUT' | 'PATCH', required — HTTP method
        - `headers` object — Handlebars templates for request headers
        - `body` string — Handlebars template for the request body (write methods only). On an `upload` use case this is where the payload is assembled, reading `{{ params.* }}` built by `params_mapping`, plus `{{ env.* }}` and `{{ steps.N.body }}`. **Route every user-controlled value through the `json` helper**: the template engine does not escape, so `"name":"{{ params.fileName }}"` produces invalid JSON the moment a filename contains a quote. Write `"name": {{json params.fileName}}` instead — the helper emits the surrounding quotes itself and renders absent values as `null`. Use `{{jsonEscape v}}` if you prefer to keep your own quotes. Upload configurations whose body would break on such input are rejected at save time. Handlebars block helpers work, so optional fields can be omitted rather than sent empty: `{{#if params.pin}},"pin": {{json params.pin}}{{/if}}`. Note that `{{/if}}}` fails to parse — leave a space before a closing brace: `{{/if}} }`.
        - `response_type` 'json' | 'binary', required — Expected response type
      - `response` FileProxyResponseConfig — How to extract the file from the step results. REQUIRED when `direction` is `download`; rejected when `direction` is `upload` (an upload has no file to extract).
        - `body` string, required — JSONata expression to extract file content from step results
        - `encoding` 'base64' | 'binary', required — Encoding of the extracted body
        - `filename` string — JSONata expression to extract the filename
        - `content_type` string — JSONata expression to extract the content type
      - `prevent_indirect_serving` boolean — Download-only; rejected when `direction` is `upload`. When `true`, this use case is served via the streaming endpoint: mapped file URLs are built as `/stream/download`, files of any size are streamed inline over HTTP response streaming, and buffered `/download` requests for oversize files are 307-redirected to `/stream`. Files never transit epilot's temporary S3 storage on the streaming path. Defaults to `false` (small files are served directly and large files are transparently served via a temporary S3 redirect).
  - UpdateManagedCallUseCaseRequest
    - `name` string — Use case name
    - `slug` string — URL-safe identifier for the use case. Recommended for portable cross-environment referencing. Can only be set once on use cases that don't have a slug yet. Immutable after being set.
    - `enabled` boolean — Whether the use case is enabled
    - `change_description` string — Optional description of this change (like a commit message)
    - `type` 'managed_call' — Use case type for managed API calls
    - `configuration` ManagedCallOperationConfig — Configuration for managed_call use cases. Defines a single API operation with JSONata mapping.
      - `operation` ManagedCallOperation, required — HTTP operation configuration for managed calls
        - `method` 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE', required
        - `path` string, required — URL path template with {{variable}} interpolation
        - `headers` object — Custom HTTP headers for the request. Values support {{variable}} interpolation from the request payload and {{env.VAR}} references for environment variables.
        - `query_params` object — Query parameters for the request. Values support {{variable}} interpolation from the request payload.
      - `request_mapping` string — JSONata expression for outbound body transformation
      - `response_mapping` string — JSONata expression for inbound response transformation
      - `inbound_use_case_slug` string — Slug of the inbound use case to route responses to for async entity processing. When set, the managed call response is queued to the inbound pipeline and processed using the referenced inbound use case's mapping configuration.
    - `type_annotations` TypeAnnotations — Developer-provided type annotations for a use case's request and response fields
      - `request` object — Type annotations for request fields, keyed by dot-path (e.g., "vendors[].id" -> "string")
      - `response` object — Type annotations for response fields
  - UpdateSecureProxyUseCaseRequest
    - `name` string — Use case name
    - `slug` string — URL-safe identifier for the use case. Recommended for portable cross-environment referencing. Can only be set once on use cases that don't have a slug yet. Immutable after being set.
    - `enabled` boolean — Whether the use case is enabled
    - `change_description` string — Optional description of this change (like a commit message)
    - `type` 'secure_proxy' — Use case type
    - `configuration` SecureProxyUseCaseConfiguration — Configuration for secure_proxy use cases. Defines how to route requests through a secure VPC.
      - `vpc_mode` 'static_ip' | 'secure_link', required — VPC routing mode. Read-only after creation. - static_ip: Routes through a VPC with static outbound IP (NAT Gateway) for IP-allowlisted external APIs. - secure_link: Routes through a VPN VPC for accessing private customer networks.
      - `allowed_domains` string[] — Domain whitelist for secure_link mode. Admin-only — can only be modified directly in DynamoDB via admin script. Supports exact match (e.g., "api.wemag.com") and wildcard prefix (e.g., "*.wemag.com").
      - `allowed_ips` string[] — IP allowlist (CIDR notation) for secure_link mode. Admin-only — can only be modified directly in DynamoDB via admin script. Required for secure_link mode. All DNS-resolved IPs must match at least one range. Example: ["10.0.1.0/24", "192.168.1.0/24"]

## Response `200`

Use case updated successfully

- union
  - InboundUseCase
    - `id` string, uuid, required — Unique identifier for the use case
    - `integrationId` string, uuid, required — Parent integration ID
    - `name` string, required — Use case name
    - `slug` string — URL-safe identifier for the use case. Recommended for portable cross-environment referencing. Unique per integration. Immutable after creation. Lowercase alphanumeric, hyphens, and underscores only.
    - `type` 'inbound', required — Use case type
    - `enabled` boolean, required
    - `change_description` string — Reason given for the update that produced the current version. Absent when the update supplied none — it is not inherited from the previous version.
    - `changed_by` string — User ID recorded for the update that produced the current version. Absent when the update had no user context, e.g. an automated sync.
    - `created_at` string, date-time, required — ISO-8601 timestamp when the use case was created
    - `updated_at` string, date-time, required — ISO-8601 timestamp when the use case was last updated
    - `configuration` InboundIntegrationEventConfiguration — Configuration for inbound use cases (ERP to epilot)
      - `entities` IntegrationEntity[] — Array of entity configurations for this event
        - `entity_schema` string, required — Target entity schema (e.g., 'contact', 'contract')
        - `unique_ids` string[], required — Array of attribute names that uniquely identify this entity. The _type hint for repeatable fields (e.g., email, phone) should be specified on the corresponding field definition in the fields array.
        - `jsonataExpression` string — Optional JSONata expression to pre-process the event data before field mapping
        - `enabled` union — Controls whether this entity mapping should be processed. Can be a boolean or a JSONata expression (string) that evaluates to a boolean.
          - boolean
          - string
        - `mode` 'upsert' | 'delete' | 'purge' | 'upsert-prune-scope-purge' | 'upsert-prune-scope-delete' — Operation mode for entity mapping: - 'upsert': Create or update the entity (default) - 'delete': Soft delete the entity (marks as deleted) - 'purge': Hard delete the entity (permanent removal) - 'upsert-prune-scope-purge': Upsert entities from array, then purge entities in scope that weren't upserted - 'upsert-prune-scope-delete': Upsert entities from array, then soft delete entities in scope that weren't upserted
        - `scope` PruneScopeConfig — Scope configuration for upsert-prune-scope modes. Defines how to find entities that should be pruned if not in the upsert payload. The scope is resolved against the original event payload (not individual array items).
          - `scope_mode` 'relations' | 'query', required — Scope mode for finding entities to prune: - 'relations': Find scope by looking at all entities related to a specific entity (both direct and reverse relations) - 'query': Find scope entities directly via query parameters
          - `schema` string — For 'relations' mode: The schema of the entity to find (e.g., 'billing_account'). Not used for 'query' mode.
          - `unique_ids` RelationUniqueIdField[] — For 'relations' mode: How to identify the scope entity from the payload. Not used for 'query' mode.
            - `attribute` string, required — Target attribute name in the related entity
            - `_type` 'email' | 'phone' — Type hint for repeatable fields that require special search handling. These fields are stored as arrays of objects (e.g., email: [{ email: "value" }]).
            - `field` string — Source field name from the event data
            - `jsonataExpression` string — JSONata expression to compute the value
            - `constant` unknown
          - `query` RelationUniqueIdField[] — For 'query' mode: Direct query parameters to find scope entities. Not used for 'relations' or 'reverse-relations' modes.
            - `attribute` string, required — Target attribute name in the related entity
            - `_type` 'email' | 'phone' — Type hint for repeatable fields that require special search handling. These fields are stored as arrays of objects (e.g., email: [{ email: "value" }]).
            - `field` string — Source field name from the event data
            - `jsonataExpression` string — JSONata expression to compute the value
            - `constant` unknown
        - `fields` IntegrationEntityField[], required — Field mapping definitions
          - `attribute` string, required — Target attribute name
          - `field` string — Source field name or JSONPath expression (if starts with $)
          - `jsonataExpression` string — JSONata expression for transformation
          - `constant` unknown
          - `_type` 'email' | 'phone' — Type hint for repeatable fields that require special search handling. These fields are stored as arrays of objects (e.g., email: [{ email: "value" }]).
          - `enabled` union — Controls whether this field mapping should be processed. Can be a boolean or a JSONata expression (string) that evaluates to a boolean. Defaults to true if omitted.
            - boolean
            - string
          - `relations` RelationConfig
            - `operation` '_set' | '_append' | '_append_all', required — Relation operation: - '_set': Replace all existing relations with the specified items - '_append': Add new unique items to existing relations (deduplicates by entity_id) - '_append_all': Add all items to existing relations (no deduplication, allows duplicates)
            - `items` RelationItemConfig[] — Array of relation item configurations
              - …
            - `jsonataExpression` string — JSONata expression that returns relation items array (alternative to 'items')
          - `relation_refs` RelationRefsConfig — Configuration for relation references ($relation_ref). Relation references link to a specific item within a repeatable attribute on a related entity. Common use case: referencing a specific address within a contact's address list.
            - `operation` '_set' | '_append' | '_append_all', required — Relation reference operation: - '_set': Replace all existing relation_refs with the specified items - '_append': Add new unique items to existing relation_refs (deduplicates by entity_id + _id) - '_append_all': Add all items to existing relation_refs (no deduplication, allows duplicates)
            - `items` RelationRefItemConfig[] — Array of relation reference item configurations
              - …
            - `jsonataExpression` string — JSONata expression that returns relation_ref items array (alternative to 'items')
          - `file_proxy_url` union — Auto-constructs a file proxy download URL. orgId and integrationId are injected from context. Exactly one of use_case_id or use_case_slug must be provided. Using use_case_slug is recommended as it is portable across environments.
            - object
              - …
            - object
              - …
          - `portal_ref` union — Resolves to a property of one of the calling organization's epilot portal configurations at runtime, replacing hard-coded environment-specific portal UUIDs in inbound mappings. Matched portals are sorted ascending by `(_created_at, portal_id)`; portals without `_created_at` sort first (treated as oldest). When `select: "single"` matches more than one portal, the resolver still returns the oldest match and emits a `PORTAL_REF_AMBIGUOUS` warning.
            - object
              - …
            - object
              - …
          - `env_var_ref` union — Resolves to an org-scoped environment variable from the epilot environments-api service at runtime, replacing hard-coded environment-specific values (URLs, prefixes, identifiers) in inbound mappings. Secrets (`SecretString` values) are never exposed; the runtime treats both "missing key" and "secret-typed key" as identical `undefined` outcomes (no info disclosure via error code). For secret-resolving contexts (e.g. authorization headers in managed-call or file-proxy step configurations), use the templated `{{ env.<key> }}` syntax instead — that mechanism does decrypt secrets.
            - object
              - …
            - object
              - …
      - `meter_readings` IntegrationMeterReading[] — Array of meter reading configurations for this event
        - `jsonataExpression` string — Optional JSONata expression to extract meter reading items from the event data. If not provided, the entire payload is used as the reading data. Useful when you need to extract an array of readings from a nested structure (e.g., "$.readings").
        - `reading_matching` 'external_id' | 'strict-date' — Strategy for matching incoming readings against existing readings. - 'external_id': Match readings by external_id attribute (default behavior) - 'strict-date': Match by meter_id + counter_id + direction + date (German timezone). Useful when readings originate from ECP and are echoed back by the ERP with truncated timestamps.
        - `mode` 'upsert' | 'delete' | 'upsert-prune-scope' — Operation mode for meter reading mapping: - 'upsert': Create or update meter readings (default) - 'delete': Delete the meter reading - 'upsert-prune-scope': Upsert readings from array, then delete all other readings for the same meter+counter that weren't upserted
        - `scope` MeterReadingPruneScopeConfig — Scope configuration for meter reading upsert-prune-scope mode. The scope is all readings for the same meter + counter.
          - `source` string — Optional source filter. When set, only readings with this source are eligible for pruning (e.g., 'ERP' to only prune ERP-synced readings).
        - `meter` MeterUniqueIdsConfig, required
          - `unique_ids` RelationUniqueIdField[], required — Array of unique identifier field mappings
            - `attribute` string, required — Target attribute name in the related entity
            - `_type` 'email' | 'phone' — Type hint for repeatable fields that require special search handling. These fields are stored as arrays of objects (e.g., email: [{ email: "value" }]).
            - `field` string — Source field name from the event data
            - `jsonataExpression` string — JSONata expression to compute the value
            - `constant` unknown
        - `meter_counter` MeterUniqueIdsConfig
          - `unique_ids` RelationUniqueIdField[], required — Array of unique identifier field mappings
            - `attribute` string, required — Target attribute name in the related entity
            - `_type` 'email' | 'phone' — Type hint for repeatable fields that require special search handling. These fields are stored as arrays of objects (e.g., email: [{ email: "value" }]).
            - `field` string — Source field name from the event data
            - `jsonataExpression` string — JSONata expression to compute the value
            - `constant` unknown
        - `fields` IntegrationEntityField[], required — Field mapping definitions for meter reading attributes
          - `attribute` string, required — Target attribute name
          - `field` string — Source field name or JSONPath expression (if starts with $)
          - `jsonataExpression` string — JSONata expression for transformation
          - `constant` unknown
          - `_type` 'email' | 'phone' — Type hint for repeatable fields that require special search handling. These fields are stored as arrays of objects (e.g., email: [{ email: "value" }]).
          - `enabled` union — Controls whether this field mapping should be processed. Can be a boolean or a JSONata expression (string) that evaluates to a boolean. Defaults to true if omitted.
            - boolean
            - string
          - `relations` RelationConfig
            - `operation` '_set' | '_append' | '_append_all', required — Relation operation: - '_set': Replace all existing relations with the specified items - '_append': Add new unique items to existing relations (deduplicates by entity_id) - '_append_all': Add all items to existing relations (no deduplication, allows duplicates)
            - `items` RelationItemConfig[] — Array of relation item configurations
              - …
            - `jsonataExpression` string — JSONata expression that returns relation items array (alternative to 'items')
          - `relation_refs` RelationRefsConfig — Configuration for relation references ($relation_ref). Relation references link to a specific item within a repeatable attribute on a related entity. Common use case: referencing a specific address within a contact's address list.
            - `operation` '_set' | '_append' | '_append_all', required — Relation reference operation: - '_set': Replace all existing relation_refs with the specified items - '_append': Add new unique items to existing relation_refs (deduplicates by entity_id + _id) - '_append_all': Add all items to existing relation_refs (no deduplication, allows duplicates)
            - `items` RelationRefItemConfig[] — Array of relation reference item configurations
              - …
            - `jsonataExpression` string — JSONata expression that returns relation_ref items array (alternative to 'items')
          - `file_proxy_url` union — Auto-constructs a file proxy download URL. orgId and integrationId are injected from context. Exactly one of use_case_id or use_case_slug must be provided. Using use_case_slug is recommended as it is portable across environments.
            - object
              - …
            - object
              - …
          - `portal_ref` union — Resolves to a property of one of the calling organization's epilot portal configurations at runtime, replacing hard-coded environment-specific portal UUIDs in inbound mappings. Matched portals are sorted ascending by `(_created_at, portal_id)`; portals without `_created_at` sort first (treated as oldest). When `select: "single"` matches more than one portal, the resolver still returns the oldest match and emits a `PORTAL_REF_AMBIGUOUS` warning.
            - object
              - …
            - object
              - …
          - `env_var_ref` union — Resolves to an org-scoped environment variable from the epilot environments-api service at runtime, replacing hard-coded environment-specific values (URLs, prefixes, identifiers) in inbound mappings. Secrets (`SecretString` values) are never exposed; the runtime treats both "missing key" and "secret-typed key" as identical `undefined` outcomes (no info disclosure via error code). For secret-resolving contexts (e.g. authorization headers in managed-call or file-proxy step configurations), use the templated `{{ env.<key> }}` syntax instead — that mechanism does decrypt secrets.
            - object
              - …
            - object
              - …
  - OutboundUseCase
    - `id` string, uuid, required — Unique identifier for the use case
    - `integrationId` string, uuid, required — Parent integration ID
    - `name` string, required — Use case name
    - `slug` string — URL-safe identifier for the use case. Recommended for portable cross-environment referencing. Unique per integration. Immutable after creation. Lowercase alphanumeric, hyphens, and underscores only.
    - `type` 'outbound', required — Use case type
    - `enabled` boolean, required
    - `change_description` string — Reason given for the update that produced the current version. Absent when the update supplied none — it is not inherited from the previous version.
    - `changed_by` string — User ID recorded for the update that produced the current version. Absent when the update had no user context, e.g. an automated sync.
    - `created_at` string, date-time, required — ISO-8601 timestamp when the use case was created
    - `updated_at` string, date-time, required — ISO-8601 timestamp when the use case was last updated
    - `configuration` OutboundIntegrationEventConfiguration — Configuration for outbound use cases. Defines the event that triggers the flow and the webhook mappings.
      - `event_catalog_event` string, required — The Event Catalog event name that triggers this outbound flow
      - `event_filter` string — JSONata boolean predicate over the hydrated event payload. The use case handles the event only when this evaluates truthy; when absent it handles every event of its name, which is the behaviour of every pre-existing configuration. This is where event scoping belongs — narrowing to certain ticket purposes, contract types or channels. Evaluation input is the full hydrated event, so relation nodes such as `ticket` and `contact` are populated. An expression that throws is treated as no match and logged, so one malformed filter cannot block the other use cases subscribed to the same event.
      - `mappings` OutboundMapping[], required — List of mappings that transform and deliver the event
        - `id` string, uuid — Unique identifier for this mapping
        - `name` string, required — Human-readable name for this mapping
        - `jsonata_expression` string — JSONata expression to transform the event payload. Required for webhook delivery, ignored for poll delivery, and rejected for file_proxy delivery — a file_proxy payload is built by the referenced use case's `params_mapping`, so accepting an expression here would silently do nothing.
        - `enabled` boolean, required — Whether this mapping is active
        - `delivery` union, required — Configuration for how the event should be delivered. webhook = push delivery via svc-webhooks (JSONata-transformed payload); poll = pull-based queue delivery where the consumer fetches items via the poll API (raw event payload); file_proxy = one push per event attachment to an external document system, through a file_proxy use case (JSONata-transformed payload carrying the file bytes)
          - WebhookDeliveryConfig — Push delivery of the transformed event to a webhook via svc-webhooks
            - `type` 'webhook', required — Delivery mechanism type
            - `webhook_id` string, required — Reference to the webhook configuration in svc-webhooks
            - `webhook_name` string — Cached webhook name for display purposes
            - `webhook_url` string — Cached webhook URL for display purposes
          - PollDeliveryConfig — Pull-based queue delivery. Items carry the raw standardized event-catalog payload; no JSONata mapping is applied in poll mode. Consumers fetch and acknowledge items via the poll API.
            - `type` 'poll', required — Delivery mechanism type
            - `retention_days` integer — How long undelivered queue items are retained before expiry
            - `poison_policy` 'dead_letter' | 'block' — What happens when an item exhausts max_delivery_attempts: dead_letter routes the exhausted item to the dead-letter queue and advances past it so the stream keeps flowing; block halts the queue at that item until operator/consumer action removes it.
            - `max_delivery_attempts` integer — Maximum delivery attempts before the poison_policy is applied
          - FileProxyDeliveryConfig — Push delivery to an external document system through a `file_proxy` use case. A pure pointer, deliberately. The outbound use case decides WHEN to deliver — its event name plus `event_filter` — and the referenced `file_proxy` use case decides WHAT and HOW: which items to fan out over (`fan_out`), what values to build (`params_mapping`, `lookups`, `constants`), and the steps that send them. Keeping every file concern on the file_proxy side is what lets one upload recipe be shared by several event subscriptions without duplicating any of it. `jsonata_expression` on the mapping is NOT used for this delivery type and is rejected. The slug is resolved at delivery time, not at save time, so use cases can be saved in any order; a dangling reference surfaces as a `USE_CASE_NOT_FOUND` monitoring event naming the referencing mapping.
            - `type` 'file_proxy', required — Delivery mechanism type
            - `use_case_slug` string, required — Slug of a `file_proxy` use case with `direction: upload` in the SAME integration.
        - `created_at` string, date-time — Timestamp when the mapping was created
        - `updated_at` string, date-time — Timestamp when the mapping was last updated
      - `ack_tracking` 'on' | 'off' — Whether this use case participates in the acknowledgement protocol. `on` (the default, and the behaviour of every pre-existing use case) records an ACK_PENDING on each event and expects the consumer to confirm receipt via `POST /v1/erp/tracking/acknowledgement`; unconfirmed events raise ACK_TIMEOUT after the timeout window. `off` opts the use case out entirely: no tracking row, no ACK_PENDING, no ACK_TIMEOUT. Set it for consumers that never acknowledge — otherwise every event produces a guaranteed timeout warning — and for deliveries that already keep their own durable per-item record, such as `file_proxy`. The tracking row is per-event, not per-use-case, so it is suppressed only when EVERY enabled use case matching the event has opted out.
  - FileProxyUseCase
    - `id` string, uuid, required — Unique identifier for the use case
    - `integrationId` string, uuid, required — Parent integration ID
    - `name` string, required — Use case name
    - `slug` string — URL-safe identifier for the use case. Recommended for portable cross-environment referencing. Unique per integration. Immutable after creation. Lowercase alphanumeric, hyphens, and underscores only.
    - `type` 'file_proxy', required — Use case type
    - `enabled` boolean, required
    - `change_description` string — Reason given for the update that produced the current version. Absent when the update supplied none — it is not inherited from the previous version.
    - `changed_by` string — User ID recorded for the update that produced the current version. Absent when the update had no user context, e.g. an automated sync.
    - `created_at` string, date-time, required — ISO-8601 timestamp when the use case was created
    - `updated_at` string, date-time, required — ISO-8601 timestamp when the use case was last updated
    - `configuration` FileProxyUseCaseConfiguration — Configuration for file_proxy use cases. Defines how to authenticate and move files between epilot and an external document system, in either direction (see `direction`). **Download** (`direction: download`, the default) fetches a file from the external system and serves it to a browser. The download URL always requires `orgId`, `integrationId`, and either `useCaseSlug` (recommended) or `useCaseId` (legacy UUID) as query parameters. The `orgId` is included in the signed URL to establish organization context without requiring authentication. Additional use-case-specific parameters are declared in the `params` array. `response` is REQUIRED for download use cases. **Upload** (`direction: upload`) pushes epilot files to the external system. It is not reachable over the download endpoint; an outbound use case points at it via a `file_proxy` delivery, and this configuration owns everything about what gets sent: `fan_out` decides how many deliveries one event produces, `params_mapping` builds the values, and the `steps` place those values into requests via `{{ params.* }}`. `upload` and `params_mapping` are REQUIRED and `response` MUST be omitted. OpenAPI 3.0 cannot express this conditional requiredness, so it is enforced by the server-side validator, which returns an explicit message naming the offending field.
      - `direction` 'download' | 'upload' — Direction of file travel. `download` (default) pulls a file from the external system into epilot; `upload` pushes an epilot file out to the external system. Omitted means `download`, so every pre-existing configuration keeps its exact meaning. Note this is the direction of the FILE, not the epilot use-case type — an `upload` file_proxy use case is still a `file_proxy` use case, never an `outbound` one.
      - `upload` FileProxyUploadConfig — Upload-side settings for a file_proxy use case with `direction: upload`. Everything about WHAT is sent lives on the outbound mapping (see `FileProxyDeliveryConfig`); this object only governs HOW the transfer is bounded and judged.
        - `max_file_bytes` integer — Per-file ceiling for this use case, in bytes. Files above it fail terminally with `FILE_TOO_LARGE` before any bytes are fetched. Defaults to — and is clamped by — the platform ceiling of 100 MiB (104857600).
        - `max_delivery_attempts` integer — Maximum delivery attempts per item before the delivery is marked failed and the message is dead-lettered. Attempts are spaced by an exponential, jittered backoff spanning roughly 16 hours over the default 8 attempts, so an ERP maintenance window does not exhaust them. Lives here rather than on the outbound delivery because how hard to retry is a property of the transport — the same judgement as `max_file_bytes` — and the outbound delivery is a pure pointer.
        - `success_when` string — Optional JSONata predicate evaluated against the final step result (`{ statusCode, headers, body }`) to decide whether the external system really accepted the file. When omitted, any 2xx counts as delivered. Use this for systems that return 200 with an error envelope.
        - `external_id` string — Optional JSONata expression over the step results yielding the external system's identifier for the stored document. Recorded on the delivery record and on the `FILE_PROXY_UPLOADED` monitoring event so an operator can find the document in the target system.
      - `fan_out` FileProxyFanOutConfig — Splits one event into several independent deliveries. Mirrors the inbound mapping idiom, where an entity's JSONata expression returning an array produces one entity update per element. Made explicit with a toggle here because an upload is also legitimately used without splitting, and because auto-detecting "array means split" would make a single-element result ambiguous. Each resulting delivery is fully independent: its own idempotency record, its own retry schedule, its own monitoring events. A four-item event can therefore end up three-of-four delivered, which is the honest state to report. The split is evaluated ONCE, when the event is enqueued, so item indices — and therefore idempotency keys — stay stable across retries.
        - `enabled` boolean, required — When false (or absent), the event produces exactly one delivery and `$item` is not bound in `params_mapping`.
        - `split_expression` string — JSONata over the event that MUST return an array; one delivery is created per element, bound as `$item` in `params_mapping`. Required when `enabled` is true. Keep this a plain projection — scoping which events are handled at all belongs in the outbound use case's `event_filter`, not here. A non-array result fails the event with `FAN_OUT_INVALID_RESULT`; an empty array produces no deliveries and one info-level `FAN_OUT_EMPTY`, which is the normal outcome for a catch-all subscription seeing an event with nothing to send.
      - `params_mapping` string — Upload-only, REQUIRED when `direction` is `upload`. JSONata expression evaluated once per fan-out item, producing the `params` object that step templates read as `{{ params.* }}`. The evaluation root is the hydrated event, so `contact.customer_pin` and `ticket._purpose` are reachable directly, unprefixed. **Everything per-item is a `$`-prefixed JSONata binding**: `$item` (the fan-out element, absent when `fan_out` is disabled), `$file_base64` and `$file` (`{filename, mime_type, size_bytes}`) for the resolved file, plus `$constants`, `$lookups`, `$ack_id`, `$germanDate(iso)` and `$now()`. Writing `item.filename` instead of `$item.filename` yields nothing — it reads a field named `item` on the event, which does not exist. Must evaluate to an object. `constants` are shallow-merged underneath the result, so the expression wins on any key collision.
      - `lookups` object — Upload-only. Named translation tables resolved BEFORE `params_mapping` runs and bound as `$lookups`, so an expression reads `$lookups.documentType` rather than carrying a conditional chain. Deliberately generic: the next ERP calls the same concept `Belegart`.
      - `constants` object — Upload-only. Fixed values shallow-merged UNDERNEATH the `params_mapping` result — the expression wins on key collisions, constants only add. Use for the unchanging strings (tenant, sender, channel) that would otherwise be repeated in every expression.
      - `file_source` string — Upload-only. JSONata returning the attachment-shaped object (`entity_id`, optionally `s3ref`) whose bytes should be fetched for this delivery. The evaluation root is the event; the fan-out element is the `$item` binding, same as in `params_mapping`. Usually unnecessary: when the fan-out item is itself attachment-shaped it is used directly. Supply this only when splitting over something that is not the attachment — for example one delivery per meter reading, each carrying a file referenced from elsewhere in the event. When nothing resolves, no file is fetched and `file_base64` is undefined, which is valid for a fan-out that sends metadata only.
      - `required_params` string[] — Upload-only. Params that MUST be present after `params_mapping` runs. Any listed name resolving to null or undefined fails the delivery terminally with `REQUIRED_PARAM_MISSING` before a single step executes. This is the generic net behind a lookup's `on_miss: fail`: it catches a required field going missing for any reason, so the external system never receives a body that is silently short a field its API requires.
      - `secure_proxy` FileProxySecureProxyAttachment
        - `use_case_slug` string, required — Slug of the secure_proxy use case in the same integration.
      - `auth` FileProxyAuth
        - `type` 'oauth2_client_credentials' | 'oauth2_password', required — Authentication type
        - `token_url` string, required — Handlebars template for the OAuth2 token endpoint URL
        - `client_id` string, required — Handlebars template for the OAuth2 client ID
        - `client_secret` string, required — Handlebars template for the OAuth2 client secret
        - `scope` string — Optional OAuth2 scope
        - `audience` string — Handlebars template for the OAuth2 audience parameter
        - `resource` string — Handlebars template for the OAuth2 resource parameter
        - `username` string — Handlebars template for the OAuth2 resource owner username. Required when type is oauth2_password.
        - `password` string — Handlebars template for the OAuth2 resource owner password. Required when type is oauth2_password.
        - `body_params` object — Additional key-value pairs to include in the token request body. Values support Handlebars templates.
        - `headers` object — Additional headers to include in the token request. Values support Handlebars templates.
        - `query_params` object — Additional query parameters to append to the token URL. Values support Handlebars templates.
      - `params` FileProxyParam[] — Download-only. Additional use-case-specific parameters expected in the download URL query string (beyond the required orgId, integrationId, and useCaseSlug or useCaseId). Rejected when `direction` is `upload`.
        - `name` string, required — Parameter name as it appears in the query string
        - `required` boolean, required — Whether this parameter is required
        - `description` string — Human-readable description of the parameter
      - `allowed_origins` string[] — Download-only. Additional origins permitted to call /download for this use case (CORS, exact match). Portal origins are always allowed. Rejected when `direction` is `upload`.
      - `steps` FileProxyStep[], required — Ordered list of HTTP steps to execute. For `download` these retrieve the file; for `upload` they deliver it, each assembling its own request body from `{{ params.* }}` built by `params_mapping`.
        - `url` string, required — Handlebars template for the request URL
        - `method` 'GET' | 'POST' | 'PUT' | 'PATCH', required — HTTP method
        - `headers` object — Handlebars templates for request headers
        - `body` string — Handlebars template for the request body (write methods only). On an `upload` use case this is where the payload is assembled, reading `{{ params.* }}` built by `params_mapping`, plus `{{ env.* }}` and `{{ steps.N.body }}`. **Route every user-controlled value through the `json` helper**: the template engine does not escape, so `"name":"{{ params.fileName }}"` produces invalid JSON the moment a filename contains a quote. Write `"name": {{json params.fileName}}` instead — the helper emits the surrounding quotes itself and renders absent values as `null`. Use `{{jsonEscape v}}` if you prefer to keep your own quotes. Upload configurations whose body would break on such input are rejected at save time. Handlebars block helpers work, so optional fields can be omitted rather than sent empty: `{{#if params.pin}},"pin": {{json params.pin}}{{/if}}`. Note that `{{/if}}}` fails to parse — leave a space before a closing brace: `{{/if}} }`.
        - `response_type` 'json' | 'binary', required — Expected response type
      - `response` FileProxyResponseConfig — How to extract the file from the step results. REQUIRED when `direction` is `download`; rejected when `direction` is `upload` (an upload has no file to extract).
        - `body` string, required — JSONata expression to extract file content from step results
        - `encoding` 'base64' | 'binary', required — Encoding of the extracted body
        - `filename` string — JSONata expression to extract the filename
        - `content_type` string — JSONata expression to extract the content type
      - `prevent_indirect_serving` boolean — Download-only; rejected when `direction` is `upload`. When `true`, this use case is served via the streaming endpoint: mapped file URLs are built as `/stream/download`, files of any size are streamed inline over HTTP response streaming, and buffered `/download` requests for oversize files are 307-redirected to `/stream`. Files never transit epilot's temporary S3 storage on the streaming path. Defaults to `false` (small files are served directly and large files are transparently served via a temporary S3 redirect).
  - ManagedCallUseCase
    - `id` string, uuid, required — Unique identifier for the use case
    - `integrationId` string, uuid, required — Parent integration ID
    - `name` string, required — Use case name
    - `slug` string — URL-safe identifier for the use case. Recommended for portable cross-environment referencing. Unique per integration. Immutable after creation. Lowercase alphanumeric, hyphens, and underscores only.
    - `type` 'managed_call', required — Use case type for managed API calls
    - `enabled` boolean, required
    - `change_description` string — Reason given for the update that produced the current version. Absent when the update supplied none — it is not inherited from the previous version.
    - `changed_by` string — User ID recorded for the update that produced the current version. Absent when the update had no user context, e.g. an automated sync.
    - `created_at` string, date-time, required — ISO-8601 timestamp when the use case was created
    - `updated_at` string, date-time, required — ISO-8601 timestamp when the use case was last updated
    - `configuration` ManagedCallOperationConfig — Configuration for managed_call use cases. Defines a single API operation with JSONata mapping.
      - `operation` ManagedCallOperation, required — HTTP operation configuration for managed calls
        - `method` 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE', required
        - `path` string, required — URL path template with {{variable}} interpolation
        - `headers` object — Custom HTTP headers for the request. Values support {{variable}} interpolation from the request payload and {{env.VAR}} references for environment variables.
        - `query_params` object — Query parameters for the request. Values support {{variable}} interpolation from the request payload.
      - `request_mapping` string — JSONata expression for outbound body transformation
      - `response_mapping` string — JSONata expression for inbound response transformation
      - `inbound_use_case_slug` string — Slug of the inbound use case to route responses to for async entity processing. When set, the managed call response is queued to the inbound pipeline and processed using the referenced inbound use case's mapping configuration.
    - `type_annotations` TypeAnnotations — Developer-provided type annotations for a use case's request and response fields
      - `request` object — Type annotations for request fields, keyed by dot-path (e.g., "vendors[].id" -> "string")
      - `response` object — Type annotations for response fields
    - `types_locked` boolean — Whether types have been generated for this use case
  - SecureProxyUseCase
    - `id` string, uuid, required — Unique identifier for the use case
    - `integrationId` string, uuid, required — Parent integration ID
    - `name` string, required — Use case name
    - `slug` string — URL-safe identifier for the use case. Recommended for portable cross-environment referencing. Unique per integration. Immutable after creation. Lowercase alphanumeric, hyphens, and underscores only.
    - `type` 'secure_proxy', required — Use case type
    - `enabled` boolean, required
    - `change_description` string — Reason given for the update that produced the current version. Absent when the update supplied none — it is not inherited from the previous version.
    - `changed_by` string — User ID recorded for the update that produced the current version. Absent when the update had no user context, e.g. an automated sync.
    - `created_at` string, date-time, required — ISO-8601 timestamp when the use case was created
    - `updated_at` string, date-time, required — ISO-8601 timestamp when the use case was last updated
    - `configuration` SecureProxyUseCaseConfiguration — Configuration for secure_proxy use cases. Defines how to route requests through a secure VPC.
      - `vpc_mode` 'static_ip' | 'secure_link', required — VPC routing mode. Read-only after creation. - static_ip: Routes through a VPC with static outbound IP (NAT Gateway) for IP-allowlisted external APIs. - secure_link: Routes through a VPN VPC for accessing private customer networks.
      - `allowed_domains` string[] — Domain whitelist for secure_link mode. Admin-only — can only be modified directly in DynamoDB via admin script. Supports exact match (e.g., "api.wemag.com") and wildcard prefix (e.g., "*.wemag.com").
      - `allowed_ips` string[] — IP allowlist (CIDR notation) for secure_link mode. Admin-only — can only be modified directly in DynamoDB via admin script. Required for secure_link mode. All DNS-resolved IPs must match at least one range. Example: ["10.0.1.0/24", "192.168.1.0/24"]

## Other responses

- `400` — Bad request
- `401` — Unauthorized request
- `404` — Use case not found
- `500` — Internal Server Error

---

[API](https://skmtc.net/epilot/apis/integration-toolkit-api.md) · [All operations](https://skmtc.net/epilot/apis/integration-toolkit-api/llms.txt) · [OpenAPI document](https://skmtc-service-staging.skmtc.workers.dev/v1/apis/epilot/integration-toolkit-api/revisions/68961a011db0/schema)
