> ## Documentation Index
> Fetch the complete documentation index at: https://developer.kodexa.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Bridge API & External Services

> Use the Bridge API in Kodexa data forms for platform data access, HTTP requests, and service bridge integration directly from sandboxed scripts.

The Bridge API is the interface between scripts running in the QuickJS sandbox and the Kodexa platform. Scripts access it through the `kodexa.*` namespace, where each sub-namespace corresponds to a capability gated by the form's bridge permissions.

## Bridge Permissions

The `bridge` property on a data form configures what scripts are allowed to do. The `permissions` array lists capability gates -- a script that attempts to call a method without the required permission will receive a `Permission denied` error.

| Permission      | Grants access to                                                                 |
| --------------- | -------------------------------------------------------------------------------- |
| `data:read`     | Read data objects, attributes, tag metadata, taxonomies                          |
| `data:write`    | Modify data objects and attributes                                               |
| `document:read` | Access the document proxy (`kodexa.document`)                                    |
| `navigation`    | Focus attributes, switch pages, rotate pages, switch views (`kodexa.navigation`) |
| `viewer`        | Scroll and pan the spatial document viewport (`kodexa.viewer`)                   |
| `formState`     | Get and set form state values, access node refs                                  |
| `http:get`      | HTTP GET requests via `kodexa.http`                                              |
| `http:post`     | HTTP POST requests via `kodexa.http` and service bridge calls                    |

Additional bridge configuration:

* **`apiBaseUrl`** -- Base URL for `kodexa.http` and `kodexa.serviceBridge` calls. Defaults to `"/api"`.
* **`maxExecutionMs`** -- Script execution timeout in milliseconds. Defaults to `1000`.

```json theme={null}
{
  "bridge": {
    "permissions": ["data:read", "data:write", "navigation", "http:post"],
    "apiBaseUrl": "https://api.example.com",
    "maxExecutionMs": 2000
  }
}
```

## kodexa.data

Requires `data:read`. Methods that modify data also require `data:write`.

| Method                                      | Parameters                                         | Returns               | Permission   |
| ------------------------------------------- | -------------------------------------------------- | --------------------- | ------------ |
| `getDataObjects(filter?)`                   | `{ path?: string, parentId?: string }`             | `DataObject[]`        | `data:read`  |
| `getDataObject(uuid)`                       | `uuid: string`                                     | `DataObject \| null`  | `data:read`  |
| `getAttributes(dataObjectUuid)`             | `dataObjectUuid: string`                           | `Attribute[]`         | `data:read`  |
| `getAttribute(dataObjectUuid, path)`        | `dataObjectUuid: string, path: string`             | `any`                 | `data:read`  |
| `setAttribute(dataObjectUuid, path, value)` | `dataObjectUuid: string, path: string, value: any` | `void`                | `data:write` |
| `addDataObject(parentUuid, path)`           | `parentUuid: string, path: string`                 | `DataObject`          | `data:write` |
| `deleteDataObject(uuid)`                    | `uuid: string`                                     | `void`                | `data:write` |
| `getTagMetadata(path)`                      | `path: string`                                     | `TagMetadata \| null` | `data:read`  |
| `getTaxonomies()`                           | --                                                 | `Taxonomy[]`          | `data:read`  |

```javascript theme={null}
// Read all line items under a parent object
const items = kodexa.data.getDataObjects({ parentId: parentUuid });
for (const item of items) {
  const amount = kodexa.data.getAttribute(item.uuid, "LineItem/Amount");
  kodexa.log.debug("Amount: " + amount);
}

// Set a computed total
kodexa.data.setAttribute(summaryUuid, "Invoice/Total", calculatedTotal);
```

## kodexa.navigation

Requires `navigation`. Spatial-viewer methods route to the document viewer for the form's first document family by default; pass an explicit `documentFamilyId` when a form is bound to more than one document.

| Method                                                   | Parameters                                                       | Returns               | Description                                                                                              |
| -------------------------------------------------------- | ---------------------------------------------------------------- | --------------------- | -------------------------------------------------------------------------------------------------------- |
| `focusAttribute(dataObjectUuid, attributePath, viewId?)` | `dataObjectUuid: string, attributePath: string, viewId?: string` | `void`                | Highlight an attribute in the document viewer (and broadcast to sidecars)                                |
| `setPage(page, documentFamilyId?)`                       | `page: number, documentFamilyId?: string`                        | `void`                | Navigate the spatial viewer to a **1-based** page number                                                 |
| `nextPage(documentFamilyId?)`                            | `documentFamilyId?: string`                                      | `void`                | Step the spatial viewer forward one page. Clamped to the last page -- a no-op at the end of the document |
| `previousPage(documentFamilyId?)`                        | `documentFamilyId?: string`                                      | `void`                | Step the spatial viewer back one page. Clamped to the first page -- a no-op at the start of the document |
| `rotatePage(direction, documentFamilyId?)`               | `direction: "left" \| "right", documentFamilyId?: string`        | `void`                | Rotate the spatial viewer's **current** page by 90° in the given direction                               |
| `getCurrentPage(documentFamilyId?)`                      | `documentFamilyId?: string`                                      | `number \| undefined` | Current 1-based page of the spatial viewer                                                               |
| `getPageCount(documentFamilyId?)`                        | `documentFamilyId?: string`                                      | `number \| undefined` | Total page count of the spatial viewer's document                                                        |
| `scrollToNode(ref)`                                      | `ref: string`                                                    | `void`                | Scroll the document viewer to a content node *(planned)*                                                 |
| `switchView(viewName)`                                   | `viewName: string`                                               | `void`                | Switch to a different form view *(planned)*                                                              |

Note that `focusAttribute` takes `dataObjectUuid` as the first parameter and `attributePath` as the second.

`rotatePage` behaves differently from the absolute `setPage`:

* **Rotation is relative.** Each call rotates by ±90° from the page's *current* rotation -- it is not an absolute angle. Calling `rotatePage("right")` twice leaves the page at 180°; a following `rotatePage("left")` returns it to 90°.
* **It affects only the current page.** Rotating every page of the document remains a separate viewer menu action; the bridge method touches just the page the viewer is currently showing.
* **It requires the `navigation` permission** and accepts an optional `documentFamilyId` to target a specific open document when the form is bound to more than one. A per-document rotation does not bleed across documents.

An invalid `direction` (anything other than `"left"` or `"right"`) or a missing viewer is a no-op -- a warning is logged to the browser console and no error is thrown.

```javascript theme={null}
// Jump to page 3 of the form's document
kodexa.navigation.setPage(3);

// Step one page at a time -- these clamp at the document edges,
// so calling nextPage() on the last page (or previousPage() on
// the first) is a silent no-op rather than an error.
kodexa.navigation.nextPage();
kodexa.navigation.previousPage();

// Read the current page after a navigation
const current = kodexa.navigation.getCurrentPage();
kodexa.log.debug("Now on page " + current + " of " + kodexa.navigation.getPageCount());

// Target a specific document when the form is bound to multiple
kodexa.navigation.setPage(1, "doc-family-uuid");

// Rotate the current page of the form's document clockwise
kodexa.navigation.rotatePage("right");

// Rotate a specific open document's current page counter-clockwise
kodexa.navigation.rotatePage("left", "doc-family-uuid");
```

These rotate actions are typically wired to keyboard shortcuts rather than called directly. The `alt+R` / `alt+shift+R` bindings are not hard-coded -- they are authored declaratively as form `shortcuts` entries whose script calls `kodexa.navigation.rotatePage(...)`:

```yaml theme={null}
shortcuts:
  - key: "alt+r"
    description: "Rotate the current page right"
    scriptRef: rotate-page-right
  - key: "alt+shift+r"
    description: "Rotate the current page left"
    scriptRef: rotate-page-left
```

The `rotate-page-right` script calls `kodexa.navigation.rotatePage("right")` and `rotate-page-left` calls `kodexa.navigation.rotatePage("left")`, so a `shortcuts:` block round-trips through the data form schema.

## kodexa.viewer

Requires `viewer`. The `viewer` surface manipulates the spatial document viewport (its scroll/pan position) rather than the page state -- that separation is why it is a distinct permission from `navigation`.

| Method                                 | Parameters                                                                  | Returns | Description                                                             |
| -------------------------------------- | --------------------------------------------------------------------------- | ------- | ----------------------------------------------------------------------- |
| `scroll(direction, documentFamilyId?)` | `direction: "up" \| "down" \| "left" \| "right", documentFamilyId?: string` | `void`  | Nudge the spatial viewer \~¼ of its visible size in the given direction |

* **Direction is one of `"up"`, `"down"`, `"left"`, `"right"`.** Any other value is a no-op -- a warning is logged to the browser console and no error is thrown.
* **Vertical vs. horizontal differ under the hood.** `"up"` / `"down"` scroll the viewer's page wrapper; `"left"` / `"right"` pan the zoom transform (the spatial image is CSS-transformed, so left/right movement pans rather than scrolls). Both feel like a small "arrow key" nudge, not a full-screen jump.
* **Requires the `viewer` permission.** Calling `kodexa.viewer.scroll(...)` without `"viewer"` in `bridge.permissions` throws a `Permission denied: viewer` error. Like the rest of the Bridge API, these actions are typically wired to keyboard shortcuts (see [Keyboard Shortcuts](/guides/data-forms/shortcuts)) rather than called directly.
* **Targets one document.** With no argument it scrolls the form's default (first) document family; pass an optional `documentFamilyId` to target a specific open document.

```javascript theme={null}
// Nudge the viewer down, then pan right
kodexa.viewer.scroll("down");
kodexa.viewer.scroll("right");

// Target a specific open document
kodexa.viewer.scroll("up", "doc-family-uuid");
```

## kodexa.form

Requires `formState`.

| Method            | Parameters                | Returns               | Description                                  |
| ----------------- | ------------------------- | --------------------- | -------------------------------------------- |
| `get(key)`        | `key: string`             | `any`                 | Read a form state value                      |
| `set(key, value)` | `key: string, value: any` | `void`                | Write a form state value                     |
| `getNodeRef(ref)` | `ref: string`             | `{ setProps(props) }` | Get a UINode by ref for dynamic prop updates |

Form state is ephemeral -- it persists for the lifetime of the form session but is not saved to the server. Use it for UI-only concerns like toggling visibility, tracking selection state, or passing values between scripts.

```javascript theme={null}
// Toggle a detail panel
const expanded = kodexa.form.get("detailExpanded") || false;
kodexa.form.set("detailExpanded", !expanded);

// Dynamically update a node's props
const node = kodexa.form.getNodeRef("statusLabel");
node.setProps({ text: "Validated", variant: "success" });
```

## kodexa.document

Requires `document:read`. The writable snapshot also requires `data:write`.

| Method               | Returns               | Description                                                  |
| -------------------- | --------------------- | ------------------------------------------------------------ |
| `snapshot()`         | `ScriptDocumentProxy` | Read-only proxy over the current document data               |
| `writableSnapshot()` | `ScriptDocumentProxy` | Writable proxy -- mutations flow through the workspace store |

The `ScriptDocumentProxy` exposes `getAllDataObjects()`, `getDataObjectByUUID(uuid)`, and `getDataObjectsByPath(path)`. Each returns `ScriptDataObjectProxy` instances with methods like `getAttributes()`, `getAttribute(label)`, `getChildren()`, and `getPath()`. Writable proxies additionally support `addAttribute()`, `addChild()`, and `setValue()` on attributes.

This API is separate from `loadDocument()` available in inline or named scripts. `kodexa.document` provides Bridge API context tied to the current workspace session; `loadDocument()` is for standalone script execution.

## kodexa.http

Requires `http:get` for GET requests, `http:post` for POST requests. Both are async.

| Method              | Parameters                 | Returns        |
| ------------------- | -------------------------- | -------------- |
| `get(path)`         | `path: string`             | `Promise<any>` |
| `post(path, body?)` | `path: string, body?: any` | `Promise<any>` |

Requests are sent to `apiBaseUrl + path`. The base URL defaults to `"/api"` if not configured.

```javascript theme={null}
// Call an external validation endpoint
const result = await kodexa.http.post("/validate", {
  invoiceNumber: invoiceNum,
  vendorId: vendorId
});

if (!result.valid) {
  kodexa.log.warn("Validation failed: " + result.reason);
}
```

## kodexa.serviceBridge

Requires `http:post`.

| Method                       | Parameters                                  | Returns        |
| ---------------------------- | ------------------------------------------- | -------------- |
| `call(ref, endpoint, body?)` | `ref: string, endpoint: string, body?: any` | `Promise<any>` |

Service bridges are named proxy endpoints that connect the platform to external APIs with centralized authentication. The `ref` is the bridge slug (e.g., `"acme-logistics/carrier-lookup"`), and `endpoint` is the endpoint name defined in the bridge YAML.

The bridge manages an `X-Bridge-Context` header for session caching. On the first call, no context header is sent; the server runs any configured `initScript` and returns context in the response header. Subsequent calls attach the cached context, skipping re-initialization. Context expires after a configurable TTL (default 3600 seconds).

```javascript theme={null}
const carriers = await kodexa.serviceBridge.call(
  "acme-logistics/carrier-lookup",
  "lookup-carrier",
  { scac: scacCode }
);
```

## kodexa.log

No permission required.

| Method           | Parameters        |
| ---------------- | ----------------- |
| `debug(message)` | `message: string` |
| `warn(message)`  | `message: string` |
| `error(message)` | `message: string` |

All log output is prefixed with `[DataFormV2]` and routed to the browser console.

## Service Bridges on Panels

In addition to imperative `kodexa.serviceBridge.call()` from scripts, panels support declarative service bridge integration through the `serviceBridge` prop on `v2:panel`. This allows a component to declare an external API dependency without writing script code.

### ServiceBridgeConfig

| Property          | Type                           | Description                                                |
| ----------------- | ------------------------------ | ---------------------------------------------------------- |
| `ref`             | `string`                       | Bridge reference (e.g., `"acme-logistics/carrier-lookup"`) |
| `endpoint`        | `string`                       | Endpoint name from the bridge YAML                         |
| `method`          | `"GET" \| "POST"`              | HTTP method, defaults to `POST`                            |
| `requestMapping`  | `Record<string, string>`       | Maps data attribute paths to request fields                |
| `responseMapping` | `ServiceBridgeResponseMapping` | Controls how the response maps back to UI or data          |
| `triggerOn`       | `string[]`                     | Attribute paths that trigger a re-call when values change  |

### ServiceBridgeResponseMapping

| Property      | Type                  | Description                                                                                          |
| ------------- | --------------------- | ---------------------------------------------------------------------------------------------------- |
| `value`       | `string`              | Path to extract option value from each response item                                                 |
| `label`       | `string`              | Path or expression for the option label (supports concatenation like `"code + ' - ' + description"`) |
| `description` | `string`              | Path or expression for hint text shown below the label in dropdowns                                  |
| `autoSelect`  | `string`              | Auto-select a single best-match field from the response                                              |
| `attributes`  | `Array<{ from, to }>` | Map response fields to data attributes (`from`: response path, `to`: attribute path)                 |

### How It Works

When any attribute listed in `triggerOn` changes, the panel reads the current values from `requestMapping`, calls the service bridge endpoint, and maps the response back using `responseMapping`. This creates a reactive loop: user edits a field, the bridge fetches updated data, and dependent fields populate automatically.

```json theme={null}
{
  "component": "v2:panel",
  "props": {
    "title": "Carrier Details",
    "serviceBridge": {
      "ref": "acme-logistics/carrier-lookup",
      "endpoint": "lookup-carrier",
      "requestMapping": {
        "scac": "Shipment/CarrierSCAC"
      },
      "responseMapping": {
        "value": "carrierId",
        "label": "scac + ' - ' + carrierName",
        "attributes": [
          { "from": "carrierName", "to": "Shipment/CarrierName" },
          { "from": "dotNumber", "to": "Shipment/DOTNumber" }
        ]
      },
      "triggerOn": ["Shipment/CarrierSCAC"]
    }
  }
}
```

In this example, when the user enters a SCAC code, the panel calls the carrier lookup endpoint and auto-populates the carrier name and DOT number fields from the response.

## v2:serviceBridgeView

A container component that calls a service bridge endpoint and makes the response available to its children through the data context. Unlike the panel `serviceBridge` prop (which maps responses back to attributes), `v2:serviceBridgeView` is designed for **read-only display** -- rendering bridge responses as tables, markdown, labels, or any combination of child components.

### How It Works

1. The component resolves the bridge slug to an ID via the platform's `/api/resolve` endpoint.
2. It POSTs the `params` to the bridge proxy endpoint (`/api/service-bridges/{id}/proxy/{endpoint}`).
3. If a `transform` expression is provided, the response is reshaped using [JSONata](https://jsonata.org/).
4. The result is injected into a scoped `DataContextV2` as `ctx.$bridgeResult`, along with `ctx.$bridgeLoading` and `ctx.$bridgeError`.
5. Children render using bindings that reference these context variables.

The component handles loading and error states automatically -- children are only rendered once data is available.

### Props

| Prop      | Type   | Default  | Description                                                                              |
| --------- | ------ | -------- | ---------------------------------------------------------------------------------------- |
| bridgeRef | string | required | Service bridge URI (e.g., `"service-bridge://acme/rate-lookup"` or `"acme/rate-lookup"`) |
| endpoint  | string | required | Endpoint name defined in the bridge configuration                                        |
| params    | Record | --       | Request body sent to the bridge (typically provided via `bindings`)                      |
| transform | string | --       | JSONata expression applied to the response before injecting into context                 |

### Context Variables

Children of `v2:serviceBridgeView` receive these additional context variables:

| Variable             | Type      | Description                                            |
| -------------------- | --------- | ------------------------------------------------------ |
| `ctx.$bridgeResult`  | `any`     | The bridge response (after optional JSONata transform) |
| `ctx.$bridgeLoading` | `boolean` | `true` while the request is in flight                  |
| `ctx.$bridgeError`   | `string`  | Error message if the request failed                    |

All existing context variables (`ctx.dataObjects`, `ctx.tagMetadataMap`, etc.) remain available -- the bridge context is additive.

### Reactive Parameters

When `params` is provided via `bindings`, the component re-calls the bridge whenever the bound values change. This creates a reactive chain: the user edits an attribute, the binding expression re-evaluates, new params are sent to the bridge, and children re-render with fresh data.

<Note>
  When referencing attributes in binding expressions, the `tag` property is the **leaf name** (e.g., `originZip`), not the full taxonomy path (`shipment/originZip`). The full path is available as `path` on the attribute. Use `a.tag === 'originZip'` or `a.path === 'shipment/originZip'` depending on which you need.
</Note>

### Example: Table from Bridge Response

Call a rate lookup endpoint, transform the response with JSONata, and render the results as a filterable AG Grid table:

```yaml theme={null}
component: v2:serviceBridgeView
props:
  bridgeRef: "service-bridge://acme/rate-lookup"
  endpoint: "get-rates"
  transform: |
    results.{
      "Carrier": carrier,
      "Rate": "$" & $string(rate),
      "Transit": transit & " days"
    }
bindings:
  params: |
    {
      originZip: ctx.dataObjects[0]?.attributes?.find(
        a => a.tag === 'originZip'
      )?.stringValue,
      destZip: ctx.dataObjects[0]?.attributes?.find(
        a => a.tag === 'destZip'
      )?.stringValue,
      weight: ctx.dataObjects[0]?.attributes?.find(
        a => a.tag === 'weight'
      )?.numericValue
    }
children:
  - component: v2:dataTable
    bindings:
      rows: "ctx.$bridgeResult"
    props:
      filterable: true
      columns:
        - field: Carrier
          title: Carrier Name
        - field: Rate
          title: Rate
          width: 120
        - field: Transit
          title: Transit Time
          width: 120
```

### Example: Markdown Summary

Fetch a report from a bridge and render it as markdown:

```yaml theme={null}
component: v2:serviceBridgeView
props:
  bridgeRef: "service-bridge://acme/analysis"
  endpoint: "get-report"
bindings:
  params: |
    {
      invoiceId: ctx.dataObjects[0]?.attributes?.find(
        a => a.tag === 'id'
      )?.stringValue
    }
children:
  - component: v2:markdown
    bindings:
      content: "ctx.$bridgeResult?.report"
```

### Example: Mixed Content

Combine multiple child components to render different parts of the bridge response:

```yaml theme={null}
component: v2:serviceBridgeView
props:
  bridgeRef: "service-bridge://acme/vendor-check"
  endpoint: "validate"
bindings:
  params: |
    {
      vendorCode: ctx.dataObjects[0]?.attributes?.find(
        a => a.tag === 'vendorCode'
      )?.stringValue
    }
children:
  - component: v2:label
    bindings:
      text: "'Vendor: ' + (ctx.$bridgeResult?.vendorName ?? 'Unknown')"
  - component: v2:dataTable
    bindings:
      rows: "ctx.$bridgeResult?.recentOrders"
    props:
      filterable: true
      columns:
        - field: orderNumber
          title: Order #
        - field: date
          title: Date
        - field: amount
          title: Amount
  - component: v2:markdown
    bindings:
      content: "ctx.$bridgeResult?.notes"
```

### JSONata Transform Reference

The `transform` prop accepts any valid [JSONata](https://jsonata.org/) expression. Common patterns:

| Pattern                | Expression                                                    | Description                            |
| ---------------------- | ------------------------------------------------------------- | -------------------------------------- |
| Extract a nested array | `response.data.items`                                         | Drill into the response structure      |
| Reshape objects        | `items.{ "Name": name, "Total": "$" & $string(price * qty) }` | Create new fields from existing ones   |
| Filter rows            | `items[status = "active"]`                                    | Only include rows matching a condition |
| Aggregate              | `$sum(items.amount)`                                          | Compute totals or other aggregations   |
| Sort                   | `items^(>amount)`                                             | Sort results by a field                |
| String formatting      | `items.{ "Display": firstName & " " & lastName }`             | Concatenate fields                     |

If the transform is omitted, the raw response is passed through as `ctx.$bridgeResult`.

<Note>
  The JSONata transform runs client-side after the response is received. For large responses, consider using the bridge's `postReplyScript` to filter server-side before the data reaches the browser.
</Note>

### Comparison: Panel serviceBridge vs serviceBridgeView

| Feature           | Panel `serviceBridge` prop              | `v2:serviceBridgeView`                         |
| ----------------- | --------------------------------------- | ---------------------------------------------- |
| Purpose           | Map bridge response to data attributes  | Display bridge response as read-only content   |
| Trigger           | Attribute path changes (`triggerOn`)    | Binding expression changes (reactive params)   |
| Response handling | `responseMapping` writes to attributes  | Children render from `ctx.$bridgeResult`       |
| Rendering         | No built-in display                     | Children render tables, markdown, labels, etc. |
| Script required   | No                                      | No                                             |
| Use case          | Auto-populate fields from external data | Show reference data, reports, lookup tables    |
