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

# Migration Guide: V2 to V3 Config

> Complete guide for migrating from Legacy (V2) to 2025-10-14 (V3) configuration format

export const V2ToV3Converter = () => {
  const isDict = value => value !== null && typeof value === 'object' && !Array.isArray(value);
  const isBool = value => typeof value === 'boolean';
  const pyTruthy = value => {
    if (value === null || value === undefined || value === false || value === 0 || value === '') return false;
    if (Array.isArray(value) || isDict(value)) return Object.keys(value).length > 0;
    return true;
  };
  const pyString = value => typeof value === 'boolean' ? value ? 'True' : 'False' : String(value);
  const pyGet = (object, key, fallback) => isDict(object) && Object.prototype.hasOwnProperty.call(object, key) ? object[key] : fallback;
  const highlight = value => {
    const escaped = value.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
    return escaped.replace(/"([^"\\]*(\\.[^"\\]*)*)"|\b(\d+\.?\d*)\b|\b(true|false|null)\b/g, (match, _stringBody, _escapedCharacter, number, literal) => {
      if (number !== undefined) return `<span class="v2v3-num">${number}</span>`;
      if (literal !== undefined) return `<span class="v2v3-literal">${literal}</span>`;
      return `<span class="v2v3-string">${match}</span>`;
    });
  };
  const deepMerge = (base, override) => {
    const result = {
      ...base
    };
    Object.entries(override || ({})).forEach(([key, value]) => {
      const existing = result[key];
      if (Object.prototype.hasOwnProperty.call(result, key) && isDict(existing) && isDict(value)) {
        result[key] = deepMerge(existing, value);
      } else if (value !== null) {
        result[key] = value;
      }
    });
    return result;
  };
  const detectRequestType = payload => {
    if (Object.prototype.hasOwnProperty.call(payload, 'schema') || Object.prototype.hasOwnProperty.call(payload, 'extract_schema')) return 'extract';
    if (Object.prototype.hasOwnProperty.call(payload, 'split_description')) return 'split';
    return 'parse';
  };
  const getDefaultV2ParsePayload = () => ({
    document_url: '',
    options: {
      ocr_mode: 'standard',
      extraction_mode: 'ocr',
      chunking: {
        chunk_mode: 'variable'
      },
      table_summary: {
        enabled: false
      },
      figure_summary: {
        enabled: false,
        override: false
      },
      filter_blocks: [],
      force_url_result: false
    },
    advanced_options: {
      ocr_system: 'highres',
      table_output_format: 'html',
      merge_tables: false,
      include_formula_information: false,
      include_color_information: false,
      continue_hierarchy: true,
      keep_line_breaks: false,
      page_range: {},
      large_table_chunking: {
        enabled: true,
        size: 50
      },
      spreadsheet_table_clustering: 'default',
      add_page_markers: false,
      remove_text_formatting: false,
      return_ocr_data: false,
      filter_line_numbers: false,
      read_comments: false,
      persist_results: false,
      exclude_hidden_sheets: false,
      exclude_hidden_rows_cols: false,
      enable_change_tracking: false,
      enable_highlight_detection: false
    },
    experimental_options: {
      enrich: {
        enabled: false,
        mode: 'standard'
      },
      layout_enrichment: false,
      native_docx_parsing: false,
      native_office_conversion: false,
      enable_checkboxes: false,
      enable_equations: false,
      rotate_pages: true,
      rotate_figures: false,
      enable_scripts: false,
      return_figure_images: false,
      return_table_images: false,
      return_page_images: false,
      layout_model: 'default',
      embed_text_metadata_pdf: false,
      detect_signatures: false,
      danger_filter_wide_boxes: false
    }
  });
  const getDefaultV2ExtractPayload = () => ({
    ...getDefaultV2ParsePayload(),
    schema: {},
    extract_schema: {},
    system_prompt: 'Be precise and thorough.',
    generate_citations: false,
    array_extract: {
      enabled: false,
      mode: 'legacy',
      pages_per_segment: 10
    },
    use_chunking: false,
    include_images: false,
    spreadsheet_agent: false,
    experimental_table_citations: true,
    agent_extract: {
      enabled: false
    },
    latency_sensitive: false,
    citations_options: {
      numerical_confidence: false
    }
  });
  const getDefaultV2SplitPayload = () => ({
    ...getDefaultV2ParsePayload(),
    split_description: [],
    split_rules: 'Split the document into the applicable sections. Sections may only overlap at their first and last page if at all.',
    split_options: {
      table_cutoff: 'truncate'
    }
  });
  const getDefaultV2Payload = (requestType = 'parse') => requestType === 'extract' ? getDefaultV2ExtractPayload() : requestType === 'split' ? getDefaultV2SplitPayload() : getDefaultV2ParsePayload();
  const findExtraFields = (input, defaults, path = '') => {
    const warnings = [];
    if (!isDict(input) || !isDict(defaults)) return warnings;
    Object.entries(input).forEach(([key, value]) => {
      const currentPath = path ? `${path}.${key}` : key;
      if (!Object.prototype.hasOwnProperty.call(defaults, key)) {
        warnings.push(`\`${currentPath}\` cannot be set in V3`);
      } else if (isDict(value) && isDict(defaults[key])) {
        warnings.push(...findExtraFields(value, defaults[key], currentPath));
      }
    });
    return warnings;
  };
  const convertParseOptions = payload => {
    const options = pyGet(payload, 'options', {});
    const advanced = pyGet(payload, 'advanced_options', {});
    const experimental = pyGet(payload, 'experimental_options', {});
    const result = {
      enhance: {
        agentic: [],
        summarize_figures: true
      },
      retrieval: {
        chunking: {
          chunk_mode: 'disabled'
        },
        filter_blocks: [],
        embedding_optimized: false
      },
      formatting: {
        add_page_markers: false,
        table_output_format: 'dynamic',
        merge_tables: false,
        include: []
      },
      spreadsheet: {
        split_large_tables: {
          enabled: true,
          size: 50
        },
        include: [],
        clustering: 'accurate',
        exclude: []
      },
      settings: {
        ocr_system: 'standard',
        force_url_result: false,
        return_ocr_data: false,
        return_images: [],
        embed_pdf_metadata: false,
        persist_results: false
      }
    };
    result.retrieval.chunking = pyGet(options, 'chunking', {
      chunk_mode: 'disabled'
    });
    if (pyGet(options, 'ocr_mode', undefined) === 'agentic') {
      result.enhance.agentic.push({
        scope: 'text'
      }, {
        scope: 'table'
      });
    }
    result.retrieval.filter_blocks = pyGet(options, 'filter_blocks', []);
    const tableSummary = pyGet(options, 'table_summary', undefined);
    if (isDict(tableSummary)) result.retrieval.embedding_optimized = pyGet(tableSummary, 'enabled', false); else if (isBool(tableSummary)) result.retrieval.embedding_optimized = tableSummary;
    const figureSummary = pyGet(options, 'figure_summary', undefined);
    if (isDict(figureSummary)) {
      result.enhance.summarize_figures = pyGet(figureSummary, 'enabled', true);
      if (pyTruthy(pyGet(figureSummary, 'enhanced', undefined)) || pyTruthy(pyGet(figureSummary, 'override', undefined))) {
        const figureAgentic = {
          scope: 'figure'
        };
        if (pyTruthy(pyGet(figureSummary, 'prompt', undefined))) figureAgentic.prompt = figureSummary.prompt;
        result.enhance.agentic.push(figureAgentic);
      }
    } else if (isBool(figureSummary)) result.enhance.summarize_figures = figureSummary;
    result.settings.force_url_result = pyGet(options, 'force_url_result', false);
    result.settings.extraction_mode = pyGet(options, 'extraction_mode', 'ocr');
    result.settings.ocr_system = ({
      highres: 'legacy',
      multilingual: 'standard',
      legacy: 'legacy',
      reducto: 'legacy',
      combined: 'standard'
    })[pyGet(advanced, 'ocr_system', undefined)] || 'standard';
    const tableFormat = pyGet(advanced, 'table_output_format', undefined);
    result.formatting.table_output_format = tableFormat === 'jsonbbox' ? 'json' : tableFormat;
    result.formatting.merge_tables = pyGet(advanced, 'merge_tables', false);
    if (pyTruthy(pyGet(advanced, 'keep_line_breaks', false))) result.settings.alpha = {
      keep_line_breaks: true
    };
    result.settings.return_ocr_data = pyGet(advanced, 'return_ocr_data', false);
    result.formatting.add_page_markers = pyGet(advanced, 'add_page_markers', false);
    if (pyTruthy(pyGet(advanced, 'page_range', undefined))) result.settings.page_range = pyGet(advanced, 'page_range', undefined);
    if (pyTruthy(pyGet(advanced, 'document_password', undefined))) result.settings.document_password = pyGet(advanced, 'document_password', undefined);
    if (pyTruthy(pyGet(advanced, 'include_color_information', false))) result.spreadsheet.include.push('cell_colors');
    if (pyTruthy(pyGet(advanced, 'include_formula_information', false))) result.spreadsheet.include.push('formula');
    const largeTable = pyGet(advanced, 'large_table_chunking', undefined);
    if (isDict(largeTable)) {
      result.spreadsheet.split_large_tables.enabled = pyGet(largeTable, 'enabled', true);
      result.spreadsheet.split_large_tables.size = pyGet(largeTable, 'size', 50);
    } else if (isBool(largeTable)) result.spreadsheet.split_large_tables.enabled = largeTable;
    result.spreadsheet.clustering = ({
      default: 'fast',
      disabled: 'disabled',
      intelligent: 'accurate'
    })[pyGet(advanced, 'spreadsheet_table_clustering', undefined)] || 'fast';
    result.settings.persist_results = pyGet(advanced, 'persist_results', false);
    if (pyTruthy(pyGet(advanced, 'exclude_hidden_sheets', false))) result.spreadsheet.exclude.push('hidden_sheets');
    if (pyTruthy(pyGet(advanced, 'exclude_hidden_rows_cols', false))) result.spreadsheet.exclude.push('hidden_rows', 'hidden_cols');
    if (pyTruthy(pyGet(advanced, 'read_comments', false))) result.formatting.include.push('comments');
    if (pyTruthy(pyGet(advanced, 'enable_change_tracking', false))) result.formatting.include.push('change_tracking');
    if (pyTruthy(pyGet(advanced, 'enable_highlight_detection', false))) result.formatting.include.push('highlight');
    result.settings.embed_pdf_metadata = pyGet(experimental, 'embed_text_metadata_pdf', false);
    const enrich = pyGet(experimental, 'enrich', undefined);
    if (isDict(enrich) && pyTruthy(pyGet(enrich, 'enabled', false)) && pyGet(enrich, 'mode', undefined) === 'table') {
      const agentic = {
        scope: 'table'
      };
      if (pyTruthy(pyGet(enrich, 'prompt', undefined))) agentic.prompt = enrich.prompt;
      result.enhance.agentic.push(agentic);
    }
    if (pyTruthy(pyGet(experimental, 'return_figure_images', false))) result.settings.return_images.push('figure');
    if (pyTruthy(pyGet(experimental, 'return_table_images', false))) result.settings.return_images.push('table');
    if (pyTruthy(pyGet(experimental, 'return_page_images', false))) result.settings.return_images.push('page');
    if (pyTruthy(pyGet(experimental, 'detect_signatures', false))) result.formatting.include.push('signatures');
    if (pyTruthy(pyGet(experimental, 'user_specified_timeout_seconds', undefined))) result.settings.timeout = pyGet(experimental, 'user_specified_timeout_seconds', undefined);
    if (pyTruthy(pyGet(experimental, 'enable_checkboxes', false))) result.settings.alpha = {
      ...result.settings.alpha || ({}),
      enable_checkboxes: true
    };
    if (pyTruthy(pyGet(experimental, 'native_docx_parsing', false))) result.settings.alpha = {
      ...result.settings.alpha || ({}),
      native_docx_parsing: true
    };
    const dpi = pyGet(experimental, 'dpi', undefined);
    if (dpi !== null && dpi !== undefined) result.settings.alpha = {
      ...result.settings.alpha || ({}),
      dpi
    };
    const customDpiModel = pyGet(experimental, 'custom_dpi_model', undefined);
    if (customDpiModel !== null && customDpiModel !== undefined) result.settings.alpha = {
      ...result.settings.alpha || ({}),
      custom_dpi_model: customDpiModel
    };
    const customArgs = pyGet(experimental, 'custom_dpi_additional_args', undefined);
    if (pyTruthy(customArgs)) result.settings.alpha = {
      ...result.settings.alpha || ({}),
      custom_dpi_additional_args: customArgs
    };
    return result;
  };
  const fillV2Defaults = payload => deepMerge(getDefaultV2Payload(detectRequestType(payload)), payload);
  const convertV2ToV3 = input => {
    const warnings = [];
    const requestType = detectRequestType(input);
    const defaults = getDefaultV2Payload(requestType);
    warnings.push(...findExtraFields(input, defaults));
    const payload = deepMerge(defaults, input);
    const advanced = payload.advanced_options;
    const experimental = payload.experimental_options;
    const continueHierarchy = pyGet(advanced, 'continue_hierarchy', true);
    if (!pyTruthy(continueHierarchy)) warnings.push(`\`advanced_options.continue_hierarchy\`=${pyString(continueHierarchy)} cannot be set in V3. V3 will default to True.`);
    const removeFormatting = pyGet(advanced, 'remove_text_formatting', false);
    if (pyTruthy(removeFormatting)) warnings.push(`\`advanced_options.remove_text_formatting\`=${pyString(removeFormatting)} cannot be set in V3. V3 will default to False.`);
    const layoutModel = pyGet(experimental, 'layout_model', 'default');
    if (layoutModel === 'default') warnings.push(`\`experimental_options.layout_model\`='${layoutModel}' cannot be set in V3. V3 will default to 'beta'.`);
    const chunkTableBlocks = pyGet(experimental, 'chunk_table_blocks', false);
    if (pyTruthy(chunkTableBlocks)) warnings.push(`\`experimental_options.chunk_table_blocks\`=${pyString(chunkTableBlocks)} cannot be set in V3. V3 will default to False.`);
    const parsing = convertParseOptions(payload);
    if (requestType === 'extract') {
      const schema = pyTruthy(payload.schema) ? payload.schema : pyGet(payload, 'extract_schema', {});
      const citationsOptions = pyGet(payload, 'citations_options', {});
      const v3 = {
        input: pyGet(payload, 'document_url', ''),
        parsing,
        instructions: {
          schema,
          system_prompt: pyGet(payload, 'system_prompt', 'Be precise and thorough.')
        },
        settings: {
          include_images: pyGet(payload, 'include_images', false),
          optimize_for_latency: pyGet(payload, 'latency_sensitive', false),
          array_extract: isDict(payload.array_extract) ? pyGet(payload.array_extract, 'enabled', false) : false,
          citations: {
            enabled: pyGet(payload, 'generate_citations', false),
            numerical_confidence: isDict(citationsOptions) ? pyGet(citationsOptions, 'numerical_confidence', true) : true,
            parent_block: isDict(citationsOptions) ? pyGet(citationsOptions, 'parent_block', 'full') : 'full'
          }
        }
      };
      if (pyTruthy(payload.use_chunking)) warnings.push('`use_chunking` cannot be set in V3. Use `parsing.retrieval.chunking.chunk_mode` instead.');
      if (pyTruthy(payload.spreadsheet_agent)) warnings.push('`spreadsheet_agent` cannot be set in V3. Spreadsheet agent is automatically enabled when needed.');
      if (!pyTruthy(payload.experimental_table_citations)) warnings.push('`experimental_table_citations` cannot be disabled in V3. Table citations are always enabled.');
      if (pyTruthy(payload.alpha_deep_extract)) warnings.push('`alpha_deep_extract` cannot be set in V3 without alpha options.');
      if (pyTruthy(payload.alpha_table_citations)) warnings.push('`alpha_table_citations` cannot be set in V3 without alpha options.');
      if (!pyTruthy(pyGet(payload, 'alpha_big_extraction_model', true))) warnings.push('`alpha_big_extraction_model` cannot be disabled in V3 without alpha options.');
      if (isDict(payload.agent_extract) && pyTruthy(payload.agent_extract.enabled)) v3.instructions.agent_in_the_loop = {
        enabled: true,
        fields_to_verify: []
      };
      return [v3, warnings];
    }
    if (requestType === 'split') {
      const options = pyGet(payload, 'split_options', {});
      return [{
        input: pyGet(payload, 'document_url', ''),
        parsing,
        split_description: pyGet(payload, 'split_description', []),
        split_rules: pyGet(payload, 'split_rules', 'Split the document into the applicable sections. Sections may only overlap at their first and last page if at all.'),
        settings: {
          table_cutoff: isDict(options) ? pyGet(options, 'table_cutoff', 'truncate') : 'truncate',
          allow_page_overlap: isDict(options) ? pyGet(options, 'allow_page_overlap', true) : true
        }
      }, warnings];
    }
    return [{
      input: pyGet(payload, 'document_url', ''),
      ...parsing
    }, warnings];
  };
  const [input, setInput] = React.useState(JSON.stringify(getDefaultV2ParsePayload(), null, 2));
  const [state, setState] = React.useState(() => {
    const parsed = getDefaultV2ParsePayload();
    const [output, warnings] = convertV2ToV3(parsed);
    return {
      output,
      warnings,
      filled: fillV2Defaults(parsed),
      error: ''
    };
  });
  const [copyStatus, setCopyStatus] = React.useState('idle');
  const copyResetTimer = React.useRef(null);
  React.useEffect(() => () => {
    if (copyResetTimer.current) clearTimeout(copyResetTimer.current);
  }, []);
  React.useEffect(() => {
    const timer = setTimeout(() => {
      try {
        const parsed = JSON.parse(input);
        if (!isDict(parsed)) throw new Error('Expected a JSON object.');
        const [output, warnings] = convertV2ToV3(parsed);
        setState({
          output,
          warnings,
          filled: fillV2Defaults(parsed),
          error: ''
        });
      } catch (error) {
        setState({
          output: null,
          warnings: [],
          filled: null,
          error: error.message
        });
      }
    }, 250);
    return () => clearTimeout(timer);
  }, [input]);
  const outputText = state.output ? JSON.stringify(state.output, null, 2) : '';
  const showCopyStatus = status => {
    if (copyResetTimer.current) clearTimeout(copyResetTimer.current);
    setCopyStatus(status);
    copyResetTimer.current = setTimeout(() => setCopyStatus('idle'), 1500);
  };
  const copyOutput = () => {
    if (!outputText) return;
    if (typeof navigator === 'undefined' || !navigator.clipboard?.writeText) {
      showCopyStatus('error');
      return;
    }
    navigator.clipboard.writeText(outputText).then(() => showCopyStatus('success'), () => showCopyStatus('error'));
  };
  return <div className="v2v3-converter not-prose my-6 rounded-xl border border-zinc-200 dark:border-zinc-800 overflow-hidden shadow-sm">
      <style>{`
        .v2v3-converter .v2v3-string { color: #0369a1; }
        .v2v3-converter .v2v3-num { color: #1d4ed8; }
        .v2v3-converter .v2v3-literal { color: #dc2626; }
        :is(.dark *) .v2v3-converter .v2v3-string { color: #a5d6ff; }
        :is(.dark *) .v2v3-converter .v2v3-num { color: #79c0ff; }
        :is(.dark *) .v2v3-converter .v2v3-literal { color: #ff7b72; }
      `}</style>
      <div className="grid md:grid-cols-2">
        <div className="bg-zinc-50 dark:bg-[#0d1117] border-b md:border-b-0 md:border-r border-zinc-200 dark:border-zinc-800">
          <div className="px-4 py-2 bg-zinc-100 dark:bg-zinc-900 border-b border-zinc-200 dark:border-zinc-800 flex items-center gap-2">
            <div className="flex gap-1.5">
              <div className="w-3 h-3 rounded-full bg-[#ff5f56]" />
              <div className="w-3 h-3 rounded-full bg-[#ffbd2e]" />
              <div className="w-3 h-3 rounded-full bg-[#27c93f]" />
            </div>
            <span className="text-xs text-zinc-500 ml-2 font-medium">V2 payload</span>
          </div>
          <textarea aria-label="V2 payload input" value={input} onChange={event => setInput(event.target.value)} className="w-full min-h-[460px] resize-y border-0 bg-zinc-50 dark:bg-[#0d1117] p-4 text-[13px] leading-relaxed text-zinc-700 dark:text-zinc-300 outline-none" style={{
    fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace'
  }} spellCheck={false} />
          {state.error && <div role="alert" className="mx-4 mb-4 rounded-md border border-red-200 bg-red-50 px-3 py-2 text-xs text-red-700 dark:border-red-800 dark:bg-red-950/40 dark:text-red-300">
              Invalid JSON: {state.error}
            </div>}
          <details className="border-t border-zinc-200 dark:border-zinc-800">
            <summary className="cursor-pointer px-4 py-3 text-xs font-medium text-zinc-600 dark:text-zinc-400 hover:text-zinc-900 dark:hover:text-zinc-200">
              V2 payload with defaults applied
            </summary>
            <pre className="m-0 max-h-[360px] overflow-auto whitespace-pre-wrap px-4 pb-4 text-[12px] leading-relaxed text-zinc-700 dark:text-zinc-300" style={{
    fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace'
  }}>
              {state.filled ? JSON.stringify(state.filled, null, 2) : ''}
            </pre>
          </details>
        </div>
        <div className="bg-zinc-50 dark:bg-[#0d1117]">
          <div className="px-4 py-2 bg-zinc-100 dark:bg-zinc-900 border-b border-zinc-200 dark:border-zinc-800 flex items-center justify-between">
            <div className="flex items-center gap-2">
              <div className="flex gap-1.5">
                <div className="w-3 h-3 rounded-full bg-[#ff5f56]" />
                <div className="w-3 h-3 rounded-full bg-[#ffbd2e]" />
                <div className="w-3 h-3 rounded-full bg-[#27c93f]" />
              </div>
              <span className="text-xs text-zinc-500 ml-2 font-medium">V3 payload</span>
            </div>
            <button type="button" onClick={copyOutput} disabled={!outputText} className={`rounded-md border px-2.5 py-1 text-xs transition disabled:cursor-not-allowed disabled:opacity-50 ${copyStatus === 'error' ? 'border-red-300 text-red-700 hover:bg-red-50 dark:border-red-700 dark:text-red-300 dark:hover:bg-red-950/40' : 'border-zinc-300 text-zinc-700 hover:bg-zinc-200 dark:border-zinc-700 dark:text-zinc-300 dark:hover:bg-zinc-800 dark:hover:text-white'}`}>
              {copyStatus === 'success' ? 'Copied' : copyStatus === 'error' ? 'Copy failed' : 'Copy'}
            </button>
          </div>
          {state.warnings.length > 0 && <div role="status" className="m-3 rounded-md border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-800 dark:border-amber-800 dark:bg-amber-950/40 dark:text-amber-200">
              <strong>Warnings</strong>
              <ul className="my-1.5 list-disc space-y-1 pl-4">{state.warnings.map(warning => <li key={warning}>{warning}</li>)}</ul>
            </div>}
          <pre className="m-0 min-h-[460px] overflow-auto whitespace-pre-wrap p-4 text-[13px] leading-relaxed text-zinc-700 dark:text-zinc-300" style={{
    fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace'
  }} dangerouslySetInnerHTML={{
    __html: highlight(outputText)
  }} />
        </div>
      </div>
    </div>;
};

<Warning>
  The V2 configuration format will eventually be deprecated. All V2 API calls will still be functional in the meantime. Please migrate to the V3 version for the latest features and improvements.
</Warning>

## Overview

The 2025-10-14 release introduces a restructured configuration format (v3) that provides better organization and clarity. The redesign is mainly a structual change, as underlying API calls should function the same, however new features post V3 will be released on the V3 version. This guide will help you migrate from the Legacy (v2) configuration format to the new format.

### Convert your V2 config to V3

<V2ToV3Converter />

## Key Changes

### 1. Input Parameter

The `document_url` parameter has been renamed to `input` for clarity:

**Legacy (v2)**

```python theme={null}
client.parse.run(document_url="https://example.com/doc.pdf")
```

**2025-10-14 (v3)**

```python theme={null}
client.parse.run(input="https://example.com/doc.pdf")
```

### 2. Configuration Structure Reorganization

The configuration options have been reorganized into more logical groupings:

* `enhance`: AI-powered enhancements (agentic modes, figure summarization)
* `retrieval`: RAG-focused settings (chunking, filtering, embedding optimization)
* `formatting`: Output format controls (tables, page markers, markup)
* `spreadsheet`: Spreadsheet-specific settings
* `settings`: General settings (OCR system, timeouts, passwords)

## Complete Mapping Reference

### Parse Configuration

#### Basic Options → Multiple Categories

| Legacy (v2)                            | 2025-10-14 (v3)                                          | Notes                       |
| -------------------------------------- | -------------------------------------------------------- | --------------------------- |
| `document_url`                         | `input`                                                  | Renamed for clarity         |
| `options.ocr_mode="agentic"`           | `enhance.agentic=[{"scope": "text"}]`                    | Agentic text mode           |
| `options.extraction_mode`              | *Removed*                                                | No longer configurable      |
| `options.chunking`                     | `retrieval.chunking`                                     | Moved to retrieval category |
| `options.table_summary.enabled`        | `retrieval.embedding_optimized`                          | Simplified to boolean       |
| `options.figure_summary.enabled=True`  | `enhance.summarize_figures=True`                         | Moved to enhance            |
| `options.figure_summary.enhanced=True` | `enhance.agentic=[{"scope": "figure"}]`                  | Now uses agentic            |
| `options.figure_summary.prompt`        | `enhance.agentic=[{"scope": "figure", "prompt": "..."}]` | Custom prompting            |
| `options.filter_blocks`                | `retrieval.filter_blocks`                                | Moved to retrieval          |
| `options.force_url_result`             | `settings.force_url_result`                              | Moved to settings           |

#### Advanced Options → Multiple Categories

| Legacy (v2)                                                   | 2025-10-14 (v3)                                      | Notes                |
| ------------------------------------------------------------- | ---------------------------------------------------- | -------------------- |
| `advanced_options.ocr_system="highres"`                       | `settings.ocr_system="standard"`                     | Values changed       |
| `advanced_options.ocr_system="multilingual"`                  | `settings.ocr_system="standard"`                     | Now uses standard    |
| `advanced_options.ocr_system="legacy"`                        | `settings.ocr_system="legacy"`                       | Same                 |
| `advanced_options.table_output_format`                        | `formatting.table_output_format`                     | Moved to formatting  |
| `advanced_options.merge_tables`                               | `formatting.merge_tables`                            | Moved to formatting  |
| `advanced_options.keep_line_breaks`                           | `settings.alpha.keep_line_breaks`                    | Moved to alpha       |
| `advanced_options.add_page_markers`                           | `formatting.add_page_markers`                        | Moved to formatting  |
| `advanced_options.page_range`                                 | `settings.page_range`                                | Moved to settings    |
| `advanced_options.document_password`                          | `settings.document_password`                         | Moved to settings    |
| `advanced_options.read_comments=True`                         | `formatting.include=["comments"]`                    | Now in list          |
| `advanced_options.enable_change_tracking=True`                | `formatting.include=["change_tracking"]`             | Now in list          |
| `advanced_options.enable_highlight_detection=True`            | `formatting.include=["highlight"]`                   | Now in list          |
| `advanced_options.persist_results`                            | `settings.persist_results`                           | Moved to settings    |
| `advanced_options.return_ocr_data`                            | `settings.return_ocr_data`                           | Moved to settings    |
| `advanced_options.large_table_chunking`                       | `spreadsheet.split_large_tables`                     | Moved to spreadsheet |
| `advanced_options.spreadsheet_table_clustering="default"`     | `spreadsheet.clustering="fast"`                      | Values changed       |
| `advanced_options.spreadsheet_table_clustering="intelligent"` | `spreadsheet.clustering="accurate"`                  | Values changed       |
| `advanced_options.spreadsheet_table_clustering="disabled"`    | `spreadsheet.clustering="disabled"`                  | Same                 |
| `advanced_options.include_formula_information=True`           | `spreadsheet.include=["formula"]`                    | Now in list          |
| `advanced_options.include_color_information=True`             | `spreadsheet.include=["cell_colors"]`                | Now in list          |
| `advanced_options.exclude_hidden_sheets=True`                 | `spreadsheet.exclude=["hidden_sheets"]`              | Now in list          |
| `advanced_options.exclude_hidden_rows_cols=True`              | `spreadsheet.exclude=["hidden_rows", "hidden_cols"]` | Now in list          |
| `advanced_options.force_file_extension`                       | `settings.force_file_extension`                      | Moved to settings    |

#### Experimental Options → Multiple Categories

| Legacy (v2)                                              | 2025-10-14 (v3)                                         | Notes             |
| -------------------------------------------------------- | ------------------------------------------------------- | ----------------- |
| `experimental_options.enrich.enabled=True, mode="table"` | `enhance.agentic=[{"scope": "table"}]`                  | Now uses agentic  |
| `experimental_options.enrich.prompt`                     | `enhance.agentic=[{"scope": "table", "prompt": "..."}]` | Custom prompting  |
| `experimental_options.return_figure_images=True`         | `settings.return_images=["figure"]`                     | Now in list       |
| `experimental_options.return_table_images=True`          | `settings.return_images=["table"]`                      | Now in list       |
| `experimental_options.embed_text_metadata_pdf`           | `settings.embed_pdf_metadata`                           | Renamed           |
| `experimental_options.timeout`                           | `settings.timeout`                                      | Moved to settings |

### Extract Configuration

| Legacy (v2)                    | 2025-10-14 (v3)                           | Notes                       |
| ------------------------------ | ----------------------------------------- | --------------------------- |
| `document_url`                 | `input`                                   | Renamed                     |
| `schema`                       | `instructions.schema`                     | Nested in instructions      |
| `system_prompt`                | `instructions.system_prompt`              | Nested in instructions      |
| `parse_config`                 | `parsing`                                 | Renamed (uses ParseOptions) |
| `include_images`               | `settings.include_images`                 | Moved to settings           |
| `generate_citations`           | `settings.citations.enabled`              | Nested in citations         |
| `array_extract`                | `settings.array_extract`                  | Moved to settings           |
| `options.numerical_confidence` | `settings.citations.numerical_confidence` | Nested in citations         |
| `latency_sensitive`            | `settings.optimize_for_latency`           | Renamed                     |

### Extract Response Format

The extract response format has changed significantly:

**Legacy (v2)**

```json theme={null}
{
  "result": [{"field1": "value1", "field2": "value2"}],
  "citations": [{"field1": [...], "field2": [...]}]
}
```

**2025-10-14 (v3)**

```json theme={null}
{
  "result": {
    "field1": {
      "value": "value1",
      "citations": [...]
    },
    "field2": {
      "value": "value2",
      "citations": [...]
    }
  }
}
```

## Migration Examples

### Example 1: Basic Parse with Agentic OCR

**Legacy (v2)**

```python theme={null}
result = client.parse.run(
    document_url=upload,
    options={
        "ocr_mode": "agentic"
    }
)
```

**2025-10-14 (v3)**

```python theme={null}
result = client.parse.run(
    input=upload,
    enhance={
        "agentic": [{"scope": "text"}]
    }
)
```

### Example 2: Parse with Multiple Configurations

**Legacy (v2)**

```python theme={null}
result = client.parse.run(
    document_url=upload,
    options={
        "ocr_mode": "agentic",
        "chunking": {"chunk_mode": "variable"},
        "table_summary": {"enabled": True},
        "figure_summary": {"enabled": True, "enhanced": True}
    },
    advanced_options={
        "ocr_system": "multilingual",
        "table_output_format": "html",
        "page_range": {"start": 1, "end": 10},
        "enable_change_tracking": True
    },
    experimental_options={
        "enrich": {"enabled": True, "mode": "table"}
    }
)
```

**2025-10-14 (v3)**

```python theme={null}
result = client.parse.run(
    input=upload,
    enhance={
        "agentic": [
            {"scope": "text"},
            {"scope": "figure"},
            {"scope": "table"}
        ],
        "summarize_figures": True
    },
    retrieval={
        "chunking": {"chunk_mode": "variable"},
        "embedding_optimized": True
    },
    formatting={
        "table_output_format": "html",
        "include": ["change_tracking"]
    },
    settings={
        "ocr_system": "standard",
        "page_range": {"start": 1, "end": 10}
    }
)
```

### Example 3: Extract Configuration

**Legacy (v2)**

```python theme={null}
result = client.extract.run(
    document_url=upload,
    schema=my_schema,
    system_prompt="Be precise and thorough.",
    generate_citations=True,
    array_extract=True,
    options={"numerical_confidence": True}
)
```

**2025-10-14 (v3)**

```python theme={null}
result = client.extract.run(
    input=upload,
    instructions={
        "schema": my_schema,
        "system_prompt": "Be precise and thorough."
    },
    settings={
        "array_extract": True,
        "citations": {
            "enabled": True,
            "numerical_confidence": True
        }
    }
)
```

### Example 4: Spreadsheet Processing

**Legacy (v2)**

```python theme={null}
result = client.parse.run(
    document_url=upload,
    advanced_options={
        "large_table_chunking": {"enabled": True, "size": 100},
        "spreadsheet_table_clustering": "intelligent",
        "include_formula_information": True,
        "include_color_information": True,
        "exclude_hidden_sheets": True
    }
)
```

**2025-10-14 (v3)**

```python theme={null}
result = client.parse.run(
    input=upload,
    spreadsheet={
        "split_large_tables": {"enabled": True, "size": 100},
        "clustering": "accurate",
        "include": ["formula", "cell_colors"],
        "exclude": ["hidden_sheets"]
    }
)
```

## Async Configuration

The async configuration structure remains similar but uses the `async` parameter:

**Legacy (v2)**

```python theme={null}
from reducto.models import WebhookConfig

result = client.parse.run_async(
    document_url=upload,
    async_config={
        "webhook": WebhookConfig(url="https://example.com/webhook"),
        "priority": True
    }
)
```

**2025-10-14 (v3)**

```python theme={null}
result = client.parse.run_async(
    input=upload,
    async={
        "webhook": {"mode": "direct", "url": "https://example.com/webhook"},
        "priority": True
    }
)
```

## Breaking Changes Checklist

When migrating your code, make sure to:

* [ ] Replace all `document_url` with `input`
* [ ] Move `ocr_mode="agentic"` to `enhance.agentic=[{"scope": "text"}]`
* [ ] Update `ocr_system` values (highres/multilingual → standard)
* [ ] Replace `table_summary.enabled` with `retrieval.embedding_optimized`
* [ ] Move figure/table enhancements to `enhance.agentic`
* [ ] Convert boolean flags to list entries where applicable (e.g., `enable_change_tracking` → `formatting.include=["change_tracking"]`)
* [ ] Update spreadsheet clustering values (default → fast, intelligent → accurate)
* [ ] Restructure extract response handling to use nested value/citations format
* [ ] Move extract `schema` and `system_prompt` into `instructions` object
* [ ] Update citation handling in extract to use the new nested format

## Need Help?

If you encounter issues during migration:

1. Check the [API Reference](https://docs.reducto.ai/api-reference) for the 2025-10-14 version
2. Review the [configuration examples](https://docs.reducto.ai/parsing/default-configurations) in the new version
3. Contact support at [support@reducto.ai](mailto:support@reducto.ai)
