---
title: "Dry-run an export against real rows"
method: POST
path: "/table-writeback/preview/"
tags: ["table-writeback"]
---

# Dry-run an export against real rows

`POST /table-writeback/preview/`

## Request

- Base URL: `https://api.getoneprofile.ai` (the document declares no server; this is the origin it was published from)
- URL: `https://api.getoneprofile.ai/table-writeback/preview/`
- Auth: one of:
  - API key in header `X-API-Key`
  - HTTP bearer

Render each row's outgoing payload exactly as a real run would, ask the destination (READ-ONLY) whether the row matches an existing record, and report what a real run would do: create, update or skip.

NOTHING is written. The write half of the executor is unreachable from this path.

## Request body

- ExportPreviewRequestSchema: A dry run: render an export against real rows, write nothing.
  - `definition` ExportColumnDefinitionInput, required: Definition of an export column: one row pushed to a connected destination. The cell holds the RECEIPT of that push (status, the destination record id, when it happened), never the row's own data (see ``TableColumnKind.EXPORT``). The destination integration is named by id plus a denormalized ``integration_type``, for the same reason ``table_import_integration`` denormalizes it: rendering a column must not need a join into the integration stratum.
    - `action` 'create' | 'update' | 'upsert': What one export cell does at the destination. Its own vocabulary, not a copy of the integration stratum's ``WriteBehavior``: the members and their values differ, and ``table_export_integration`` translates between the two at the seam (``_WRITE_BEHAVIOR_TO_EXPORT_ACTION``). Mirroring has no member here because it deletes and recreates the whole destination object, which is a whole-object concern rather than a per-row one.
    - `batched` boolean: Write rows in destination-sized batches instead of one at a time. Faster, at a cost: the destination reports batch-level results, so receipts say 'pushed' rather than created-versus-updated, and a burst of writes can overwhelm automations attached to the destination. Off by default for that reason.
    - `fields` ExportFieldMappingInput[], required: The destination properties written, in mapping order.
      - `destination_field` string, required
      - `property_type` 'string' | 'integer' | 'float' | 'boolean' | 'datetime' | 'date' | 'list' | 'dict': Type of a property. Used while creating/updating/deleting properties.
      - `skip_if_blank` boolean, nullable: Per-field override of the definition's ``remove_blanks``; None inherits it. Set False on one field to send an explicit blank that clears a destination value.
      - `source` ExportValueSourceInput, required: The per-row value written into ONE destination field. ``template`` reuses the enrichment prompt format verbatim: ordered text and column-reference segments, references stored by column identity so a rename can never silently repoint a mapping and a delete is detectable. PASSTHROUGH RULE: when the template is exactly one whole-cell column reference (one segment, no path), the RAW stored cell value is sent, not its rendered text, so a LIST cell reaches a multi-select property as a list and a NUMBER cell as a number. Any other shape (text around a reference, two references, a path) renders to a string, because concatenation only has a string answer.
        - `template` EnrichmentPromptTemplate: The persisted prompt: an ordered mix of text and column references. Resolution to a per-row string happens at run time; the segment list is the single source of truth the editor renders pills from and the executor substitutes values into.
          - `format` 'plain' | 'markdown': How the editor reads text segments: 'markdown' renders them as markdown source, 'plain' verbatim. Templates saved before the field existed carry no value and default to 'plain', the safe reading. Purely editor metadata: run-time resolution concatenates text segments identically either way.
          - `segments` union[]
            - union
              - …
    - `identity` ExportIdentityMappingInput[]: The destination fields the row may be identified by: the match key that decides create-versus-update, required for every action. More than one is an ordered FALLBACK, not a composite key: the first entry that renders a non-empty value for a row is the one that row is matched on, and the rest are not consulted. Order them most to least trustworthy ('match on work email, failing that personal email'). Matching on two fields at once is not offered because the destination contract takes a single identity field.
      - `destination_field` string, required
      - `source` ExportValueSourceInput, required: The per-row value written into ONE destination field. ``template`` reuses the enrichment prompt format verbatim: ordered text and column-reference segments, references stored by column identity so a rename can never silently repoint a mapping and a delete is detectable. PASSTHROUGH RULE: when the template is exactly one whole-cell column reference (one segment, no path), the RAW stored cell value is sent, not its rendered text, so a LIST cell reaches a multi-select property as a list and a NUMBER cell as a number. Any other shape (text around a reference, two references, a path) renders to a string, because concatenation only has a string answer.
        - `template` EnrichmentPromptTemplate: The persisted prompt: an ordered mix of text and column references. Resolution to a per-row string happens at run time; the segment list is the single source of truth the editor renders pills from and the executor substitutes values into.
          - `format` 'plain' | 'markdown': How the editor reads text segments: 'markdown' renders them as markdown source, 'plain' verbatim. Templates saved before the field existed carry no value and default to 'plain', the safe reading. Purely editor metadata: run-time resolution concatenates text segments identically either way.
          - `segments` union[]
            - union
              - …
    - `integration_id` string, uuid, required: The connected DESTINATION integration this column writes to.
    - `integration_type` string, required: Provider slug, denormalized so display needs no cross-stratum join.
    - `kind` 'export'
    - `only_on_change` boolean: Skip a row whose outgoing payload is identical to the one last successfully written. The row-level idempotency knob; off, every run re-writes every row.
    - `record_type` string, required: Which destination object to write: 'contacts', 'deals', a custom type.
    - `remove_blanks` boolean: Drop fields whose rendered value is empty from the outgoing payload rather than sending a blank. Off, a blank clears the destination value: a deliberate act, not a default.
    - `retry` ExportRetryPolicy: One export column's reliability contract with its destination. Defaults are chosen so the common case needs no configuration: retry the errors worth retrying (rate limits, server errors, network failures) a few times, honour the destination's own Retry-After when it names one, and otherwise write as fast as the destination accepts.
      - `max_retries` integer: Retry attempts per row beyond the first, when retrying at all.
      - `requests_per_minute` integer, nullable: Custom ceiling on write calls per minute. None means the destination's own pace. Its live rate-limit answers are honoured either way.
      - `retry_on_failure` boolean: Retry a row whose failure is retryable: rate limits, server errors, network failures. Bad requests never retry: the same payload would fail the same way.
      - `timeout_seconds` integer, nullable: Response timeout per destination request. None uses the platform default.
    - `typecast` boolean: Coerce each rendered value into the destination property's declared type before writing. Off, values are sent verbatim and the destination's own coercion applies.
    - `update_mode` 'replace' | 'patch' | 'set_once': replace overwrites the whole record, patch touches only the mapped fields, set_once writes only where the destination is empty. Degrades to patch at run time when the destination does not declare replace, the same fallback the sync export makes.
  - `record_ids` string[], required: The rows to preview; the server caps how many render.
  - `table_id` string, uuid, required

## Response `200`

Successful Response

- ExportPreviewResponseSchema: The dry run's rows. Counters are absent on purpose: nothing ran.
  - `rows` ExportPreviewRowSchema[], required
    - `error` string, nullable, required: A per-row resolution failure, if any.
    - `identity` object, required: The rendered match values.
    - `matched_record_id` string, nullable, required: The destination record the row matched, when one exists.
    - `properties` object, required: The rendered outgoing payload.
    - `record_id` string, uuid, required
    - `skipped_reason` string, nullable, required: Why a row would be skipped.
    - `would` string, required: 'create', 'update' or 'skip', resolved by read-only matching.

## Other responses

- `401`: Unauthorized
- `403`: Forbidden
- `404`: Table or integration not found
- `422`: Invalid request made to the API.
- `500`: Internal server error.
- `502`: Bad gateway error.
- `503`: Service unavailable.

---

[API](https://skmtc.dev/getoneprofile/apis/api-reference-oneprofile.md) · [All operations](https://skmtc.dev/getoneprofile/apis/api-reference-oneprofile/llms.txt) · [OpenAPI document](https://skmtc.dev/getoneprofile/apis/api-reference-oneprofile/revisions/4a6bd71e3f02?raw)
