---
title: "Preview what a profile would extract from a text (nothing is stored)"
method: POST
path: "/pots/{pot_id}/extract/preview"
tags: ["extraction-preview"]
---

# Preview what a profile would extract from a text (nothing is stored)

`POST /pots/{pot_id}/extract/preview`

## Request

- Base URL: `https://api.scipot.ai` (the document declares no server; this is the origin it was published from)
- URL: `https://api.scipot.ai/pots/{pot_id}/extract/preview`
- Auth: none declared
- TypeScript: <https://skmtc.dev/scipot/apis/scipot-core-api/docs/pots/:pot_id/extract/preview/post.ts> (a function with typed inputs; its response is checked against the schema)

Run the extraction pipeline on `content` **synchronously** and return the facts a real ingestion would create — with their POT score, provenance, classification and (when scope-aware) scope fields — **without persisting anything**: no facts, no Document, no job, no vectors, no edges. The only durable trace is the usage event (`operation=extraction_preview`), which counts against the workspace's monthly token quota like an ingestion.

**Profile:** pass `profile_id` (a stored profile of this workspace, snapshotted exactly as `/ingest` does) **or** `profile` (an inline lever body, applied once and never stored — iterate on levers before saving them). The response's `profile_applied` says which levers shaped the extraction and where they came from.

**What it does not do:** constitution edge detection and score propagation (`score_stage` is `initial`, `edges` is `null`), and deduplication against the facts already in the POT (`dedup_scope` is `batch+constitution`), and the near-duplicate judge (a similar fact is discarded only when its text is identical; the others are listed in `extraction_audit.near_duplicates_kept` as `undecided`, `no_judge`), so a real ingestion may keep fewer facts than shown.

**Limits:** `content` at most 12288 characters (413), 45 s server-side ceiling (504), 4 concurrent previews per API process (429) and a per-integrator route limit on top of the global one (429). A chunk whose extraction fails is reported in `errors[]` with `partial: true`; when nothing at all could be extracted the call fails with 502 and the same error code a job would carry.

**Error shapes:** the gates this route shares with `/ingest` (404 POT, 422 `PROFILE_NOT_FOUND`, 429 `TOKEN_QUOTA_EXCEEDED`, the rate limiter's 429) answer `{"detail": ...}` like that route; the preview's own gates (413, 429 `PREVIEW_CONCURRENCY_LIMIT`, 500, 502, 503 `LLM_NOT_CONFIGURED`, 504) answer the `{"error": {code, message, fix, retryable, ...}}` envelope.

## Path parameters

- `pot_id` string, uuid, required

## Headers

- `X-Contributor-ID` string, nullable: Optional contributor (UUID) the usage events are attributed to, as on `/ingest`. A value that is not an active contributor of the workspace is ignored; billing never depends on it.

## Request body

- ExtractionPreviewRequest: JSON body of ``POST /pots/{pot_id}/extract/preview``. ``extra='forbid'`` like the ingestion body: an unknown field is a 422, not a silently ignored knob. The profile comes either by id (a profile of this workspace, snapshotted exactly like ``/ingest`` does) or inline (the same lever body ``POST /profiles`` accepts, applied once and never stored) — never both.
  - `content` string, required: Text to preview (plain text or Markdown). At most 12288 characters (also reported as `limits.max_content_chars`); longer content is refused with 413 `PREVIEW_CONTENT_TOO_LARGE`. The cap is enforced by the route, not by schema validation, so the schema deliberately declares no `maxLength`: a generated client must not pre-empt the 413 with its own 422.
  - `source` SourceMetadata: Metadata about a document source.
    - `path` string, required: Path or URL of the source document
    - `type` 'markdown' | 'text' | 'pdf' | 'html' | 'docx' | 'pptx' | 'xlsx' | 'image': Document type
    - `author` string, nullable: Document author or creator
    - `created_at` string, date-time, nullable: When the document was created
    - `effective_from` string, date, nullable: First day the document takes effect, when that differs from created_at («con efectos desde el 1 de enero de 2027»). The facts extracted from it hold from this day (valid_from); without it, from the day of created_at
    - `description` string, nullable: What the document is, in the uploader's words (e.g. 'Message in the #it-help Slack channel'). At most 1000 characters at admission.
    - `tags` string[]: Tags associated with this source (max 20)
    - `source_authority` string, nullable: What kind of source this is: a key of the POT's source types (`GET /pots/{pot}/constitution/source-types`; by default `law`, `convenio_colectivo`, `contract`, `politica_interna`, `actas_y_mails`, `regulation`, `official_guidance`, `expert`, `press_note`, `secondary`, `web`, each 0.60). Its weight is the starting POT Score of every fact extracted from it, and the document keeps it (`PATCH /documents/{id}` changes it and its facts). Unknown or switched off -> 422 `SOURCE_TYPE_UNKNOWN` / `SOURCE_TYPE_INACTIVE`. Omitted = 0.50, as before.
    - `authoritative` boolean: Whether this is an authoritative source (affects POT Index boost)
  - `language` string, nullable: ISO 639-1 override for the extraction language. Same ladder as `/ingest`: request > profile > POT default > auto-detect.
  - `profile_id` string, uuid, nullable: A stored extraction profile of this workspace to apply. Unknown here -> 422 `PROFILE_NOT_FOUND`. Mutually exclusive with `profile`.
  - `profile` ExtractionProfileLevers: The integrator-settable part of a profile: its name and its levers. Shared by the create and update bodies so the two cannot drift. ``extra='forbid'`` rejects unknown fields with a 422 — including levers that later releases add — so an integrator never ships a profile whose lever this deployment silently ignores (decision D2).
    - `name` string, required: Human-readable profile name, unique only by convention (ids are the key).
    - `description` string, nullable: What this profile is for (free text, integrator-facing).
    - `language` string, nullable: ISO 639-1 extraction language applied when the ingestion request carries none. Precedence: request > profile > POT default > auto-detect.
    - `scope_aware` boolean, nullable: Scope-aware extraction (own / third_party / mixed_context per fact). true or false overrides the deployment flag SCOPE_AWARE_EXTRACTION; null follows it.
    - `granularity` 'fine' | 'standard' | 'coarse', nullable: How finely the LLM splits the text into facts: 'fine' extracts every data point and minor attribute, 'coarse' keeps only the substantive claims, 'standard' (or null) is the base prompt's own behaviour. Applies to the LLM extraction only: facts the deterministic table extractor produces before the LLM call (one per table cell) are not affected.
    - `additional_instructions` string, nullable: Free-text guidance quoted into the extraction prompt's INTEGRATOR GUIDANCE section. It refines what to extract and how to phrase facts; every rule of the base prompt (output format, language, scope, atomicity) takes precedence over it. Line endings normalised to LF and stripped; at most 1000 characters after that; must not be blank, contain control characters other than line breaks and tabs, or contain the guidance delimiters (any letter case).
    - `business_relevance` string, nullable: What the destination knowledge base is for (one focus statement). The prompt prioritizes facts that bear on it and may omit claims that do not; facts the deterministic table extractor produces are not affected. Same text rules as additional_instructions, at most 500 characters.
    - `initial_pot_score` number, nullable: POT Score every fact extracted under this profile starts with, instead of the extraction's default (0.50, level Inferred). Between 0.10 and 0.79: a profile can vouch for a source up to Supported, never into Verified (0.80+), which stays for several sources or human validation. Edge propagation and curation move the score from there as usual; null keeps the default.
    - `pii` PiiLevers: The ``pii`` lever of an extraction profile. ``extra='forbid'`` like the profile body: a key this release does not apply is a 422, never silently ignored (decision D2).
      - `mode` 'off' | 'anonymize' | 'block': Default 'anonymize' when the object is sent without it (a null lever follows the deployment default instead). 'anonymize' keeps every fact and masks the PII in it (structured entities flat, contextual ones with a stable per-workspace pseudonym); 'block' drops any fact that carries PII; 'off' applies only the deployment floor.
      - `contextual_entities` string[], nullable: Which context-detected entity types to act on: NAME, ADDRESS, AGE, USERNAME. null = all of them; [] = none (structured entities only). Structured entities follow `structured_entities` (all by default); only the floor types (secrets and payment data) are in scope unconditionally when mode is not 'off'.
      - `structured_entities` string[], nullable: Which format-detected entity types to act on (emails, phones, cards, IBANs, national ids, credentials...). null = all of them. Lets an integrator whose facts carry 9-digit revenue figures or version numbers switch PHONE or IP_ADDRESS off. The floor types (secrets and payment data) are masked regardless of this list.
      - `reversible` boolean: Reserved: keep the pseudonym-to-value mapping in a restricted vault so an authorised role can reverse it. Not available yet; only false is accepted.
  - `context_attachments` ContextAttachmentCreateRequest[]: Context for interpreting `content`, never extracted itself: the same body `POST /documents/{document_id}/context-attachments` takes (`type` `author_note` or `json_schema`, `name`, `content_text` at most 16 KB), at most 5. Rendered into the extraction prompt exactly as an extraction of a stored document with those attachments, under the same caps (80 KB in total, 25,000 tokens; over either, 413 `PREVIEW_CONTEXT_TOO_LARGE`). Nothing is stored.
    - `type` 'author_note' | 'json_schema', required: Vocabulary label for the attachment. SciPot does NOT interpret the payload by type — both kinds are opaque typed strings to the extraction pipeline. The type and the name are shown to the extraction model alongside the payload.
    - `name` string, required: Human-readable label shown to curators ('HR platform schema v3').
    - `content_text` string, required: Inline UTF-8 text payload. Max 16 KB by encoded byte size. Payloads larger than 16 KB are rejected by this schema's validator and surface as a 422 with the Pydantic error detail — use the multipart `/upload` variant of the endpoint for larger payloads (it returns 413 ATTACHMENT_FILE_TOO_LARGE for files > 80 KB).
    - `mime_type` 'text/plain' | 'application/json' | 'application/schema+json', nullable: Optional MIME type. Defaults to 'text/plain' for author_note and 'application/schema+json' for json_schema when omitted.
  - `include_unmasked` boolean: T9 — when the PII layer masked a fact, also return the fact as it was before masking (`facts[].pii.unmasked_content`), for a side-by-side view at the cost of one extraction. The text came from this same request and nothing is persisted, so nothing new is disclosed.

## Response `200`

The facts the pipeline would create; nothing was stored

- ExtractionPreviewResponse: What ``POST /pots/{pot_id}/extract/preview`` returns (HTTP 200). Nothing in it was persisted: not the facts, not a Document, not a job, not a vector, not an edge. The only durable trace is the usage event.
  - `pot_id` string, uuid, required
  - `persisted` false: Always false: a preview writes nothing but its usage event
  - `score_stage` 'initial' | 'propagated': `initial` — the POT score each fact would be created with. Constitution edge detection and score propagation do not run in a preview in this release; `propagated` is reserved for the release that adds them.
  - `edges` unknown[], nullable: Null in this release: no edge detection runs in a preview. The release that adds it returns the edges here (`score_stage: propagated`).
    - unknown
  - `dedup_scope` 'batch+constitution': Deduplication crossed the previewed facts with each other and with the POT's constitution only — NOT with the facts already in the POT (the preview uses an empty disposable store). A real ingestion also dedups against the POT's existing facts and asks the near-duplicate judge (the preview discards only identical text), so it may keep fewer facts than shown.
  - `profile_applied` AppliedProfile: The profile the preview actually applied, whatever its origin. For a stored profile ``profile_id``/``version`` are the same pair the facts' provenance would carry after a real ingestion. For an inline profile both are ``null``: nothing was stored, so there is nothing a ``provenance.profile_id`` could ever resolve to — and the facts in the response carry ``null`` there too.
    - `source` 'stored' | 'inline', required
    - `profile_id` string, uuid, nullable
    - `version` integer, nullable
    - `name` string, required
    - `language` string, nullable
    - `scope_aware` boolean, nullable
    - `granularity` 'fine' | 'standard' | 'coarse', nullable
    - `additional_instructions` string, nullable
    - `business_relevance` string, nullable
    - `initial_pot_score` number, nullable
    - `pii` PiiLevers: The ``pii`` lever of an extraction profile. ``extra='forbid'`` like the profile body: a key this release does not apply is a 422, never silently ignored (decision D2).
      - `mode` 'off' | 'anonymize' | 'block': Default 'anonymize' when the object is sent without it (a null lever follows the deployment default instead). 'anonymize' keeps every fact and masks the PII in it (structured entities flat, contextual ones with a stable per-workspace pseudonym); 'block' drops any fact that carries PII; 'off' applies only the deployment floor.
      - `contextual_entities` string[], nullable: Which context-detected entity types to act on: NAME, ADDRESS, AGE, USERNAME. null = all of them; [] = none (structured entities only). Structured entities follow `structured_entities` (all by default); only the floor types (secrets and payment data) are in scope unconditionally when mode is not 'off'.
      - `structured_entities` string[], nullable: Which format-detected entity types to act on (emails, phones, cards, IBANs, national ids, credentials...). null = all of them. Lets an integrator whose facts carry 9-digit revenue figures or version numbers switch PHONE or IP_ADDRESS off. The floor types (secrets and payment data) are masked regardless of this list.
      - `reversible` boolean: Reserved: keep the pseudonym-to-value mapping in a restricted vault so an authorised role can reverse it. Not available yet; only false is accepted.
  - `language_applied` string, nullable: Extraction language after the ladder (request > profile > POT default > auto-detect) — the `language` on every fact.
  - `content_chars` integer, required
  - `document_context` DocumentContext, required: Global context extracted from a document.
    - `title` string, required: Main topic or title of the document
    - `key_entities` string[], required: 5-10 key entities mentioned in document
    - `main_themes` string[], required: 3-5 main themes or topics discussed
    - `document_type` 'technical' | 'conceptual' | 'reference' | 'guide' | 'other', required: Document type classification
    - `language` string, nullable: ISO 639-1 language code of the document (e.g. 'es', 'en'). None if mixed-language or undetermined.
    - `scope_hint` 'own' | 'third_party' | 'mixed_context' | 'unknown', nullable: Document-level scope hint inferred during context generation. 'own' = describes the operating organization's own data/operations; 'third_party' = describes external entities (case studies, customer data); 'mixed_context' = contains both first-party and third-party content; 'unknown' = cannot determine. None = scope inference not performed.
  - `chunking` ChunkingSummary, required: What the semantic chunker did with the non-table text. Sizes are in TOKENS (the chunker's unit), not characters.
    - `chunks_total` integer, required: LLM chunks the text was split into
    - `chunks_failed` integer, required: Chunks whose extraction failed (see `errors[]`)
    - `target_size_tokens` integer, required: Chunker target size, in tokens
    - `overlap_tokens` integer, required: Overlap between consecutive chunks, in tokens
  - `facts` FactExtended[], required: The facts a real ingestion would create (after deduplication), in creation order, with the same fields (`pot_score`, `level`, `provenance`, `classification`, scope fields when scope-aware). Ids are throwaway.
    - `id` string, uuid, required: Unique identifier (UUID)
    - `pot_id` string, uuid, required: POT this fact belongs to
    - `workspace_id` string, uuid, nullable: Multi-tenant isolation. Defaults to DEV_WORKSPACE_ID if None.
    - `content` string, required: The actual claim or statement
    - `title` string, nullable: Short title (max 50 chars, hard limit 80) for graph node labels. Generated by LLM during extraction. None for legacy facts.
    - `content_hash` string, nullable: SHA-256 hash of content for deduplication
    - `pot_score` number, required: Certainty score (0.0-1.0, where 1.0 = CONSTITUTION)
    - `original_pot_score` number, nullable: Pre-propagation POT Score. None if propagation hasn't run.
    - `level` 'CONSTITUTION' | 'VERIFIED' | 'SUPPORTED' | 'INFERRED' | 'HYPOTHESIS' | 'SPECULATION', required: POT Index levels matching score ranges.
    - `embedding_model` string, nullable: Embedding model used, e.g. text-embedding-3-small
    - `provenance` Provenance, required: Provenance information for a fact.
      - `type` string, required: Type of provenance (extracted, manual, inferred, imported)
      - `sources` object[]: Where the fact came from. One entry today. Its typed keys: `contributor_id` (the registered contributor who brought the source in: the uploader of the document, the author of a contribution) and `episode_id` (the session it was written in); `authority` is the POT source type, `source_type` the file format. A contribution may carry `basis_quote`: the contributor's own words it is based on, after the PII layer; a citation that no score reads.
      - `derivation` object, nullable: Derivation info for inferred facts
      - `profile_id` string, uuid, nullable: Extraction profile applied (T4); null when none.
      - `profile_version` integer, nullable: Version of that profile at enqueue time; set iff profile_id is set.
    - `classification` Classification: Classification metadata for a fact.
      - `domain` string: Domain category
      - `subdomain` string, nullable: Subdomain category
      - `tags` string[]: Tags for search (max 20). Inherited from SourceMetadata.tags during ingestion.
      - `entity_mentions` string[]: Entities mentioned
    - `status` string: Processing status (processing, ready, error)
    - `validation_status` string: Validation status (pending, validated, disputed, rejected)
    - `created_at` string, date-time: When fact was created
    - `updated_at` string, date-time: When fact was last updated
    - `valid_from` string, date, nullable: First day the fact holds in the world (valid time, not when it was stored). Taken from the document's effective date, else its date; null = not known.
    - `valid_from_source` 'document_effective' | 'document_date' | 'contributor' | 'imported' | 'curator', nullable: Where valid_from came from: document_effective, document_date, contributor, imported or curator. Null when valid_from is null.
    - `document_id` string, uuid, nullable: Document UUID this fact was extracted from. None for constitution facts and inline ingestion.
    - `language` string, nullable: ISO 639-1 language code of the fact content (e.g. 'es', 'en'). Inherited from document language detection during extraction.
    - `location` DocumentLocation: Location metadata tracing a fact back to its source in the document. Populated during ingestion when document_index_enabled=True. Available fields depend on document format: - PDF: section_path, source_text - DOCX: section_path, source_text - PPTX: section_path, source_text - XLSX: sheet_name, row_number, cell_ref, source_text - MD/TXT: section_path, source_text Ingestion does not set page_number from PDF pages or PPTX slides: page tracing is off in every ingestion path since v0.5.7.1.
      - `page_number` integer, nullable: Page number (1-indexed): PDF page or PPTX slide. Ingestion does not set it from PDF or PPTX since v0.5.7.1 (page tracing is off).
      - `section_path` string, nullable: Heading breadcrumb, e.g. '## Resultados > ### Q3 2024'.
      - `source_text` string, nullable: Verbatim text excerpt from the document this fact was derived from.
      - `sheet_name` string, nullable: XLSX sheet (tab) name where the fact was found.
      - `row_number` integer, nullable: XLSX row number (1-indexed).
      - `cell_ref` string, nullable: XLSX cell reference, e.g. 'B7' or 'B7:D7'.
    - `scope_inferred` 'own' | 'third_party' | 'mixed_context' | 'unknown', nullable: Per-fact scope classification from scope-aware extraction. 'own' = describes the operating organization's data; 'third_party' = describes external entities (case studies, customers); 'mixed_context' = blends first- and third-party content; 'unknown' = LLM could not determine. None = legacy fact, extraction predates scope-aware prompt.
    - `scope_confidence` number, nullable: LLM-reported confidence (0.0-1.0) on the scope_inferred classification. ≥0.7 = trust; 0.4-0.7 = hint; <0.4 = manually verify. None when scope_inferred is None.
    - `scope_reason` string, nullable: Short LLM-generated explanation of the scope_inferred classification (why this scope, what signal the LLM used). Used by KB2B tooltip to explain confidence to admins. None when scope_inferred is None.
    - `context_attachment_ids_used` string[], nullable: Snapshot of context_attachment_ids in scope at extraction time. Empty list = extraction ran under M21+ with zero attachments. None = legacy fact (pre-M21) or non-extraction-pipeline fact.
    - `pii` FactPii: What the PII layer did to one fact. Counts and states only — no values.
      - `applied` boolean, required: True when at least one entity was masked
      - `mode` 'off' | 'anonymize' | 'block', required
      - `entities` object: Masked entities by type, e.g. {'NAME': 2, 'EMAIL': 1}
      - `coverage` 'full' | 'partial' | 'none'
      - `language` string, nullable
      - `unmasked_content` string, nullable: The fact before masking. Only on the extraction preview with include_unmasked=true; never persisted.
    - `version` integer: Version number (increments on update)
    - `flags` string[]: Special flags (needs_review, deprecated, etc.)
    - `notes` string, nullable: Internal notes
    - `warnings` FactWarning[]: Patterns detected on the fact for curators (e.g. a stated repeal).
      - `pattern` string, required: Pattern name, e.g. `repeal`
      - `message` string, required: Human-readable warning
      - `detail` string, nullable: What matched, e.g. the cited reference
      - `probability` number, nullable: Decisions API confidence; null when the filter alone decided
    - `edge_summary` EdgeSummary: Denormalized edge count summary stored on each fact. Enables KB2B to display edge counts per fact without extra API calls. Updated atomically when edges are created or deleted.
      - `total_in` integer: Total incoming edges
      - `total_out` integer: Total outgoing edges
      - `by_type` object: Edge counts broken down by type (e.g. supports, contradicts)
  - `extraction_audit` ExtractionAudit, required: Metadata about extraction and deduplication process.
    - `total_extracted` integer, required: Facts extracted from the document that reached deduplication (after the quality filter and the PII layer; filtered counts what the filter dropped)
    - `filtered` integer: Statements the model returned that the quality filter dropped before deduplication: too short, too long or opening with an ambiguous pronoun. Not part of total_extracted. 0 on jobs before v0.7.103.0.
    - `filtered_by_reason` object: filtered by reason: too_short, too_long, ambiguous_pronoun
    - `kept` integer, required: Facts kept post-deduplication
    - `discarded` integer, required: Facts discarded
    - `discarded_facts` DiscardedFact[]: List of discarded facts with metadata
      - `id` string, required: ID of the discarded fact
      - `content` string, required: Full content of the fact
      - `reason` 'duplicate' | 'duplicate_of_constitution' | 'too_short' | 'too_long', required: Reasons why a fact was discarded during extraction. MVP: Only DUPLICATE implemented. Future: TOO_SHORT, TOO_LONG for validation of length.
      - `duplicate_of_id` string, nullable: ID of the kept fact
      - `similarity_score` number, nullable: Cosine similarity with kept fact
      - `decision_confidence` number, nullable: Confidence of the near-duplicate judge that this fact copies the kept one; null when the texts were identical (no call)
      - `timestamp` string, date-time: Moment of discard
    - `near_duplicates_kept` KeptNearDuplicate[]: Facts kept although similar to an axiom or a fact, with the judge's verdict (first 100 at most, contradictions and undecided pairs first; the counts below cover them all)
      - `id` string, required: ID of the kept fact
      - `other_id` string, nullable: ID of the similar fact; null for an axiom
      - `other_kind` 'fact' | 'constitution', required: What it resembles
      - `relation` 'contradiction' | 'restates' | 'complementary' | 'undecided', required: The judge's verdict; undecided = no confident verdict. restates (v0.7.100.0): same values from a document that reports or relies on the other (also a low-confidence duplicate across documents, and a duplicate from a backend that may not discard); kept, linked by a restates edge, never support
      - `reason` string, required: The backend that gave the verdict (decisions, bedrock), or why there was none: low_confidence, refused, error, no_judge, budget, deadline, breaker, not_judged (ranked past the candidates judged for this fact), route_only (a confident duplicate from a backend that may not discard, kept as restates)
      - `confidence` number, nullable: The judge's confidence
      - `similarity_score` number, required: Wording similarity
      - `cosine` number, nullable: Similarity of meaning (embeddings); null when not computed
      - `found_by` 'wording' | 'meaning' | 'both': Which filter made it a candidate: wording (fuzz ratio), meaning (embeddings cosine, top-k), or both
    - `near_duplicates_total` integer: Pairs kept although similar, including those not listed
    - `near_duplicates_by_relation` object: near_duplicates_total by relation
    - `near_duplicates_by_reason` object: near_duplicates_total by reason
    - `near_duplicates_unchecked` integer: Similar candidates ranked past those a fact is judged or compared with: neither judged nor sent to the contradiction detector
    - `near_duplicates_restates` integer: Kept facts that restate another (relation restates): linked by a restates edge, never counted as support
    - `semantic_candidates` SemanticCandidateAudit: What the near-duplicate candidates by meaning did on one run (v0.7.100.0). ``status``: ``on`` (ran), ``failed`` (the new facts could not be embedded: the run fell back to the wording filter alone). ``errors`` counts the failed calls (the embedding batch, each failed vector-store query).
      - `status` 'on' | 'failed', required: on, or failed (wording only)
      - `threshold` number, required: Cosine floor of a candidate
      - `top_k` integer, required: Candidates by meaning per fact and comparison
      - `pairs_added` integer: Candidate pairs found by meaning that wording did not find
      - `errors` integer: Failed embedding or vector-store calls
  - `errors` ChunkError[]: One entry per failed chunk (first 25 at most)
    - `chunk_index` integer, required: 0-based chunk position
    - `code` string, required: Error code (LLM_TIMEOUT, LLM_RATE_LIMIT, ...)
    - `message` string, required: Human-readable error message
    - `retryable` boolean: Whether re-extracting the document is likely to succeed
  - `processing_time_ms` integer, required
  - `usage` PreviewUsage, required: What this preview cost, as it was metered (``operation=extraction_preview``). The same event shape a real ingestion bills; it counts against the workspace's monthly token quota.
    - `llm_calls` integer, required
    - `prompt_tokens` integer, required
    - `completion_tokens` integer, required
    - `cached_prompt_tokens` integer
    - `cache_write_tokens` integer
  - `context_attachments_applied` AppliedContextAttachment[]: The `context_attachments` rendered into every chunk's prompt, in order.
    - `type` 'author_note' | 'json_schema', required
    - `name` string, required
    - `byte_size` integer, required
  - `pii_applied` PiiRunSummary: What the PII layer did across one extraction run (job or preview).
    - `mode` 'off' | 'anonymize' | 'block', required
    - `provider` string, required
    - `provider_version` string, nullable: Detector configuration that ran (guardrail id:version, pattern-set tag).
    - `entitlement` 'premium' | 'floor', required
    - `facts_scanned` integer
    - `facts_anonymized` integer
    - `facts_blocked` integer
    - `by_type` object
    - `coverage` 'full' | 'partial' | 'none'
    - `language` string, nullable
    - `units` integer: Billable text units (1 unit = 1000 chars)
    - `free_units` integer
    - `chars` integer: Characters sent to the detector
    - `processing_ms` integer
  - `detail` string, nullable: Why nothing would be stored, when no fact survived and no chunk failed (same as the job's result.detail). Since v0.7.103.0.
  - `detail_code` 'all_filtered' | 'no_facts_found' | 'all_withheld' | 'all_duplicates', nullable: Machine-readable form of detail. Since v0.7.103.0.
  - `limits` PreviewLimits: The ceilings this call ran under, so a Workbench can size its inputs.
    - `max_content_chars` integer
    - `timeout_seconds` integer
    - `max_concurrent_per_process` integer
    - `requests_per_minute` integer
  - `partial` boolean, required: True when chunking.chunks_failed > 0: the text was only partially extracted.

## Other responses

- `403`: `PII_NOT_ENTITLED` (`error`): the profile (inline or stored) switches the PII layer on in a workspace without `features.pii`
- `404`: POT not found in this workspace (`detail`)
- `413`: `content` longer than 12288 characters (`error.code` = `PREVIEW_CONTENT_TOO_LARGE`), or `context_attachments` over 80 KB in total or 25,000 tokens (`PREVIEW_CONTEXT_TOO_LARGE`)
- `422`: Validation error — including `PROFILE_NOT_FOUND`, both `profile_id` and `profile` given, an unsupported `language`, or an inline profile that fails the profile rules (`detail`).
- `429`: Route rate limit or `TOKEN_QUOTA_EXCEEDED` (`detail`), or `PREVIEW_CONCURRENCY_LIMIT` (`error`, with `Retry-After`).
- `500`: A failure of ours inside the pipeline (`error.code` = `INTERNAL_ERROR`)
- `502`: The LLM provider failed on every chunk (`error.code` as in a job error: `LLM_TIMEOUT`, `LLM_RATE_LIMIT`, `LLM_INVALID_RESPONSE`, ...)
- `503`: LLM provider not configured or `PII_DETECTOR_UNAVAILABLE` (`error`), or storage unavailable (`detail`)
- `504`: The pipeline exceeded 45 s (`error`)

## Changes

- **2026-10-24** `87bd973b9c0f`: 4 info
  - added the optional property `detail` to the response with the `200` status
  - added the optional property `detail_code` to the response with the `200` status
  - added the optional property `extraction_audit/filtered` to the response with the `200` status
  - added the optional property `extraction_audit/filtered_by_reason` to the response with the `200` status
- **2026-10-24** `0fb6ed61ba72`: 1 warning, 4 info
  - added the new `restates` enum value to the `extraction_audit/near_duplicates_kept/items/relation` response property for the response status `200`
  - added the optional property `extraction_audit/near_duplicates_kept/items/cosine` to the response with the `200` status
  - added the optional property `extraction_audit/near_duplicates_kept/items/found_by` to the response with the `200` status
  - added the optional property `extraction_audit/near_duplicates_restates` to the response with the `200` status
  - …1 more
- …earlier changes not shown

[Operation history](https://skmtc.dev/scipot/apis/scipot-core-api/changes/pots/:pot_id/extract/preview/post.md)

---

[API](https://skmtc.dev/scipot/apis/scipot-core-api.md) - [All operations](https://skmtc.dev/scipot/apis/scipot-core-api/llms.txt) - [OpenAPI document](https://skmtc.dev/scipot/apis/scipot-core-api/revisions/7620af82156a?raw)
