Skip to content

Module veryfi.cli.documents

CLI commands for receipts/invoice documents.

Wraps :class:veryfi.documents.Documents, plus nested line-items, tags, and PDF-split sub-apps.

View Source
"""CLI commands for receipts/invoice documents.

Wraps :class:`veryfi.documents.Documents`, plus nested ``line-items``,

``tags``, and PDF-split sub-apps.

"""

from __future__ import annotations

from typing import List, Optional

import typer

from veryfi.cli._common import (

    emit,

    get_client,

    handle_errors,

    merge_body,

    optional_list,

    parse_kv,

    read_file_arg,

)

app = typer.Typer(

    help="Receipt and invoice documents.",

    no_args_is_help=True,

    rich_markup_mode=None,

)

line_items_app = typer.Typer(

    help="Line items belonging to a document.",

    no_args_is_help=True,

    rich_markup_mode=None,

)

tags_app = typer.Typer(

    help="Tags attached to a document.",

    no_args_is_help=True,

    rich_markup_mode=None,

)

set_app = typer.Typer(

    help="Multi-page PDF document sets (split-and-process).",

    no_args_is_help=True,

    rich_markup_mode=None,

)

app.add_typer(line_items_app, name="line-items")

app.add_typer(tags_app, name="tags")

app.add_typer(set_app, name="set")

@app.command("process")

@handle_errors

def process_document(

    ctx: typer.Context,

    file: str = typer.Option(

        ...,

        "--file",

        "-f",

        help="Path to a local file to upload, or '-' to read raw bytes from stdin.",

    ),

    categories: Optional[List[str]] = typer.Option(

        None,

        "--category",

        help="Optional category. Repeat to pass multiple (e.g. --category Travel --category Meals).",

    ),

    delete_after_processing: bool = typer.Option(

        False,

        "--delete-after-processing/--keep",

        help="Delete the document from Veryfi after extraction.",

    ),

    fields: Optional[List[str]] = typer.Option(

        None,

        "--field",

        help="Extra body parameter KEY=VALUE. Repeat for multiple.",

    ),

    json_body: Optional[str] = typer.Option(

        None,

        "--json-body",

        help="Extra body parameters as a JSON object. Merged before --field overrides.",

    ),

) -> None:

    """Upload a receipt or invoice and extract structured data."""

    client = get_client(ctx)

    result = client.process_document(

        file_path=read_file_arg(file),

        categories=optional_list(categories),

        delete_after_processing=delete_after_processing,

        **merge_body(fields, json_body),

    )

    emit(ctx, result)

@app.command("process-url")

@handle_errors

def process_document_url(

    ctx: typer.Context,

    file_url: Optional[str] = typer.Option(

        None, "--file-url", help="Publicly accessible URL of the file."

    ),

    file_urls: Optional[List[str]] = typer.Option(

        None,

        "--file-urls",

        help="Publicly accessible URLs (repeat the flag). Alternative to --file-url.",

    ),

    categories: Optional[List[str]] = typer.Option(None, "--category"),

    delete_after_processing: bool = typer.Option(False, "--delete-after-processing/--keep"),

    boost_mode: bool = typer.Option(False, "--boost-mode/--no-boost-mode"),

    external_id: Optional[str] = typer.Option(

        None, "--external-id", help="Optional custom ID to attach to the document."

    ),

    max_pages_to_process: Optional[int] = typer.Option(

        None, "--max-pages", help="Cap processing at this many pages."

    ),

    fields: Optional[List[str]] = typer.Option(None, "--field"),

    json_body: Optional[str] = typer.Option(None, "--json-body"),

) -> None:

    """Process a remote receipt or invoice by URL."""

    if not file_url and not file_urls:

        raise typer.BadParameter("either --file-url or --file-urls must be provided")

    client = get_client(ctx)

    result = client.process_document_url(

        file_url=file_url,

        file_urls=optional_list(file_urls),

        categories=optional_list(categories),

        delete_after_processing=delete_after_processing,

        boost_mode=boost_mode,

        external_id=external_id,

        max_pages_to_process=max_pages_to_process,

        **merge_body(fields, json_body),

    )

    emit(ctx, result)

@app.command("process-bulk")

@handle_errors

def process_documents_bulk(

    ctx: typer.Context,

    file_urls: List[str] = typer.Option(

        ...,

        "--file-url",

        help="Publicly accessible URL. Repeat the flag for each file.",

    ),

) -> None:

    """Submit several remote URLs in a single bulk request."""

    client = get_client(ctx)

    emit(ctx, client.process_documents_bulk(file_urls=list(file_urls)))

@app.command("list")

@handle_errors

def list_documents(

    ctx: typer.Context,

    q: Optional[str] = typer.Option(None, "--q", help="Free-text search query."),

    external_id: Optional[str] = typer.Option(None, "--external-id"),

    tag: Optional[str] = typer.Option(None, "--tag"),

    created_gt: Optional[str] = typer.Option(None, "--created-gt"),

    created_gte: Optional[str] = typer.Option(None, "--created-gte"),

    created_lt: Optional[str] = typer.Option(None, "--created-lt"),

    created_lte: Optional[str] = typer.Option(None, "--created-lte"),

    extra: Optional[List[str]] = typer.Option(

        None, "--param", help="Extra query parameter KEY=VALUE. Repeat for multiple."

    ),

) -> None:

    """Search/list previously processed documents."""

    client = get_client(ctx)

    emit(

        ctx,

        client.get_documents(

            q=q,

            external_id=external_id,

            tag=tag,

            created_gt=created_gt,

            created_gte=created_gte,

            created_lt=created_lt,

            created_lte=created_lte,

            **parse_kv(extra),

        ),

    )

@app.command("get")

@handle_errors

def get_document(

    ctx: typer.Context,

    document_id: int = typer.Argument(..., help="Document ID."),

    extra: Optional[List[str]] = typer.Option(

        None, "--param", help="Extra query parameter KEY=VALUE. Repeat for multiple."

    ),

) -> None:

    """Fetch a single document by ID."""

    client = get_client(ctx)

    emit(ctx, client.get_document(document_id=document_id, **parse_kv(extra)))

@app.command("update")

@handle_errors

def update_document(

    ctx: typer.Context,

    document_id: int = typer.Argument(..., help="Document ID."),

    fields: Optional[List[str]] = typer.Option(

        None,

        "--field",

        help="Field to update: KEY=VALUE. Repeat for multiple.",

    ),

    json_body: Optional[str] = typer.Option(None, "--json-body"),

) -> None:

    """Update mutable fields on a document."""

    body = merge_body(fields, json_body)

    if not body:

        raise typer.BadParameter("at least one --field or --json-body is required")

    client = get_client(ctx)

    emit(ctx, client.update_document(document_id=document_id, **body))

@app.command("delete")

@handle_errors

def delete_document(

    ctx: typer.Context,

    document_id: int = typer.Argument(..., help="Document ID."),

) -> None:

    """Delete a document."""

    client = get_client(ctx)

    client.delete_document(document_id=document_id)

    emit(ctx, {"deleted": document_id})

# --- line-items sub-app ------------------------------------------------------

@line_items_app.command("list")

@handle_errors

def line_items_list(

    ctx: typer.Context,

    document_id: int = typer.Argument(..., help="Parent document ID."),

) -> None:

    """List all line items on a document."""

    client = get_client(ctx)

    emit(ctx, client.get_line_items(document_id=document_id))

@line_items_app.command("get")

@handle_errors

def line_item_get(

    ctx: typer.Context,

    document_id: int = typer.Argument(...),

    line_item_id: int = typer.Argument(...),

) -> None:

    """Fetch a single line item."""

    client = get_client(ctx)

    emit(ctx, client.get_line_item(document_id=document_id, line_item_id=line_item_id))

@line_items_app.command("add")

@handle_errors

def line_item_add(

    ctx: typer.Context,

    document_id: int = typer.Argument(...),

    fields: Optional[List[str]] = typer.Option(None, "--field"),

    json_body: Optional[str] = typer.Option(None, "--json-body"),

) -> None:

    """Append a new line item."""

    payload = merge_body(fields, json_body)

    if not payload:

        raise typer.BadParameter("at least one --field or --json-body is required")

    client = get_client(ctx)

    emit(ctx, client.add_line_item(document_id=document_id, payload=payload))

@line_items_app.command("update")

@handle_errors

def line_item_update(

    ctx: typer.Context,

    document_id: int = typer.Argument(...),

    line_item_id: int = typer.Argument(...),

    fields: Optional[List[str]] = typer.Option(None, "--field"),

    json_body: Optional[str] = typer.Option(None, "--json-body"),

) -> None:

    """Update an existing line item."""

    payload = merge_body(fields, json_body)

    if not payload:

        raise typer.BadParameter("at least one --field or --json-body is required")

    client = get_client(ctx)

    emit(

        ctx,

        client.update_line_item(

            document_id=document_id, line_item_id=line_item_id, payload=payload

        ),

    )

@line_items_app.command("delete")

@handle_errors

def line_item_delete(

    ctx: typer.Context,

    document_id: int = typer.Argument(...),

    line_item_id: int = typer.Argument(...),

) -> None:

    """Delete a single line item."""

    client = get_client(ctx)

    client.delete_line_item(document_id=document_id, line_item_id=line_item_id)

    emit(ctx, {"deleted": {"document_id": document_id, "line_item_id": line_item_id}})

@line_items_app.command("delete-all")

@handle_errors

def line_items_delete_all(

    ctx: typer.Context,

    document_id: int = typer.Argument(...),

) -> None:

    """Delete every line item on a document."""

    client = get_client(ctx)

    client.delete_line_items(document_id=document_id)

    emit(ctx, {"deleted_all_on_document": document_id})

# --- tags sub-app ------------------------------------------------------------

@tags_app.command("list")

@handle_errors

def tags_list(

    ctx: typer.Context,

    document_id: int = typer.Argument(...),

) -> None:

    """List tags on a document."""

    client = get_client(ctx)

    emit(ctx, client.get_tags(document_id=document_id))

@tags_app.command("add")

@handle_errors

def tags_add(

    ctx: typer.Context,

    document_id: int = typer.Argument(...),

    name: str = typer.Option(..., "--name", help="Tag name to attach."),

) -> None:

    """Attach a single tag."""

    client = get_client(ctx)

    emit(ctx, client.add_tag(document_id=document_id, tag_name=name))

@tags_app.command("add-many")

@handle_errors

def tags_add_many(

    ctx: typer.Context,

    document_id: int = typer.Argument(...),

    tags: List[str] = typer.Option(..., "--tag", help="Tag name. Repeat for multiple."),

) -> None:

    """Attach several tags at once."""

    client = get_client(ctx)

    emit(ctx, client.add_tags(document_id=document_id, tags=list(tags)))

@tags_app.command("replace")

@handle_errors

def tags_replace(

    ctx: typer.Context,

    document_id: int = typer.Argument(...),

    tags: List[str] = typer.Option(..., "--tag", help="Replacement tag name. Repeat for multiple."),

) -> None:

    """Replace all tags with the supplied set."""

    client = get_client(ctx)

    emit(ctx, client.replace_tags(document_id=document_id, tags=list(tags)))

@tags_app.command("delete")

@handle_errors

def tags_delete(

    ctx: typer.Context,

    document_id: int = typer.Argument(...),

    tag_id: int = typer.Argument(...),

) -> None:

    """Detach a single tag."""

    client = get_client(ctx)

    client.delete_tag(document_id=document_id, tag_id=tag_id)

    emit(ctx, {"deleted": {"document_id": document_id, "tag_id": tag_id}})

@tags_app.command("delete-all")

@handle_errors

def tags_delete_all(

    ctx: typer.Context,

    document_id: int = typer.Argument(...),

) -> None:

    """Detach every tag from a document."""

    client = get_client(ctx)

    client.delete_tags(document_id=document_id)

    emit(ctx, {"deleted_all_on_document": document_id})

# --- document-set / PDF split sub-app ---------------------------------------

@set_app.command("split")

@handle_errors

def split_pdf(

    ctx: typer.Context,

    file: str = typer.Option(..., "--file", "-f", help="Local PDF path, or '-' for stdin."),

    categories: Optional[List[str]] = typer.Option(None, "--category"),

    fields: Optional[List[str]] = typer.Option(None, "--field"),

    json_body: Optional[str] = typer.Option(None, "--json-body"),

) -> None:

    """Split a multi-page PDF and process each page."""

    client = get_client(ctx)

    emit(

        ctx,

        client.split_and_process_pdf(

            file_path=read_file_arg(file),

            categories=optional_list(categories),

            **merge_body(fields, json_body),

        ),

    )

@set_app.command("split-url")

@handle_errors

def split_pdf_url(

    ctx: typer.Context,

    file_url: Optional[str] = typer.Option(None, "--file-url"),

    file_urls: Optional[List[str]] = typer.Option(None, "--file-urls"),

    categories: Optional[List[str]] = typer.Option(None, "--category"),

    max_pages_to_process: Optional[int] = typer.Option(None, "--max-pages"),

    fields: Optional[List[str]] = typer.Option(None, "--field"),

    json_body: Optional[str] = typer.Option(None, "--json-body"),

) -> None:

    """Split a remote PDF and process each page."""

    if not file_url and not file_urls:

        raise typer.BadParameter("either --file-url or --file-urls must be provided")

    client = get_client(ctx)

    emit(

        ctx,

        client.split_and_process_pdf_url(

            file_url=file_url,

            file_urls=optional_list(file_urls),

            categories=optional_list(categories),

            max_pages_to_process=max_pages_to_process,

            **merge_body(fields, json_body),

        ),

    )

@set_app.command("list")

@handle_errors

def set_list(

    ctx: typer.Context,

    extra: Optional[List[str]] = typer.Option(None, "--param"),

) -> None:

    """List previously processed PDF document sets."""

    client = get_client(ctx)

    emit(ctx, client.get_pdf(**parse_kv(extra)))

@set_app.command("get")

@handle_errors

def set_get(

    ctx: typer.Context,

    document_id: int = typer.Argument(..., help="Document-set ID."),

) -> None:

    """Fetch all documents inside a PDF set."""

    client = get_client(ctx)

    emit(ctx, client.get_documents_from_pdf(document_id=document_id))

Variables

app
line_items_app
set_app
tags_app

Functions

delete_document

def delete_document(
    ctx: 'typer.Context',
    document_id: 'int' = <typer.models.ArgumentInfo object at 0x7f078bb71eb0>
) -> 'None'

Delete a document.

View Source
@app.command("delete")

@handle_errors

def delete_document(

    ctx: typer.Context,

    document_id: int = typer.Argument(..., help="Document ID."),

) -> None:

    """Delete a document."""

    client = get_client(ctx)

    client.delete_document(document_id=document_id)

    emit(ctx, {"deleted": document_id})

get_document

def get_document(
    ctx: 'typer.Context',
    document_id: 'int' = <typer.models.ArgumentInfo object at 0x7f078bb71dc0>,
    extra: 'Optional[List[str]]' = <typer.models.OptionInfo object at 0x7f078bb71f40>
) -> 'None'

Fetch a single document by ID.

View Source
@app.command("get")

@handle_errors

def get_document(

    ctx: typer.Context,

    document_id: int = typer.Argument(..., help="Document ID."),

    extra: Optional[List[str]] = typer.Option(

        None, "--param", help="Extra query parameter KEY=VALUE. Repeat for multiple."

    ),

) -> None:

    """Fetch a single document by ID."""

    client = get_client(ctx)

    emit(ctx, client.get_document(document_id=document_id, **parse_kv(extra)))

line_item_add

def line_item_add(
    ctx: 'typer.Context',
    document_id: 'int' = <typer.models.ArgumentInfo object at 0x7f078bbd9160>,
    fields: 'Optional[List[str]]' = <typer.models.OptionInfo object at 0x7f078bbd91c0>,
    json_body: 'Optional[str]' = <typer.models.OptionInfo object at 0x7f078bbd91f0>
) -> 'None'

Append a new line item.

View Source
@line_items_app.command("add")

@handle_errors

def line_item_add(

    ctx: typer.Context,

    document_id: int = typer.Argument(...),

    fields: Optional[List[str]] = typer.Option(None, "--field"),

    json_body: Optional[str] = typer.Option(None, "--json-body"),

) -> None:

    """Append a new line item."""

    payload = merge_body(fields, json_body)

    if not payload:

        raise typer.BadParameter("at least one --field or --json-body is required")

    client = get_client(ctx)

    emit(ctx, client.add_line_item(document_id=document_id, payload=payload))

line_item_delete

def line_item_delete(
    ctx: 'typer.Context',
    document_id: 'int' = <typer.models.ArgumentInfo object at 0x7f078bbd9ac0>,
    line_item_id: 'int' = <typer.models.ArgumentInfo object at 0x7f078bbd9b20>
) -> 'None'

Delete a single line item.

View Source
@line_items_app.command("delete")

@handle_errors

def line_item_delete(

    ctx: typer.Context,

    document_id: int = typer.Argument(...),

    line_item_id: int = typer.Argument(...),

) -> None:

    """Delete a single line item."""

    client = get_client(ctx)

    client.delete_line_item(document_id=document_id, line_item_id=line_item_id)

    emit(ctx, {"deleted": {"document_id": document_id, "line_item_id": line_item_id}})

line_item_get

def line_item_get(
    ctx: 'typer.Context',
    document_id: 'int' = <typer.models.ArgumentInfo object at 0x7f078bbd9fa0>,
    line_item_id: 'int' = <typer.models.ArgumentInfo object at 0x7f078bbd9bb0>
) -> 'None'

Fetch a single line item.

View Source
@line_items_app.command("get")

@handle_errors

def line_item_get(

    ctx: typer.Context,

    document_id: int = typer.Argument(...),

    line_item_id: int = typer.Argument(...),

) -> None:

    """Fetch a single line item."""

    client = get_client(ctx)

    emit(ctx, client.get_line_item(document_id=document_id, line_item_id=line_item_id))

line_item_update

def line_item_update(
    ctx: 'typer.Context',
    document_id: 'int' = <typer.models.ArgumentInfo object at 0x7f078bbd92e0>,
    line_item_id: 'int' = <typer.models.ArgumentInfo object at 0x7f078bbd9340>,
    fields: 'Optional[List[str]]' = <typer.models.OptionInfo object at 0x7f078bbd9370>,
    json_body: 'Optional[str]' = <typer.models.OptionInfo object at 0x7f078bbd93a0>
) -> 'None'

Update an existing line item.

View Source
@line_items_app.command("update")

@handle_errors

def line_item_update(

    ctx: typer.Context,

    document_id: int = typer.Argument(...),

    line_item_id: int = typer.Argument(...),

    fields: Optional[List[str]] = typer.Option(None, "--field"),

    json_body: Optional[str] = typer.Option(None, "--json-body"),

) -> None:

    """Update an existing line item."""

    payload = merge_body(fields, json_body)

    if not payload:

        raise typer.BadParameter("at least one --field or --json-body is required")

    client = get_client(ctx)

    emit(

        ctx,

        client.update_line_item(

            document_id=document_id, line_item_id=line_item_id, payload=payload

        ),

    )

line_items_delete_all

def line_items_delete_all(
    ctx: 'typer.Context',
    document_id: 'int' = <typer.models.ArgumentInfo object at 0x7f078bbd9c40>
) -> 'None'

Delete every line item on a document.

View Source
@line_items_app.command("delete-all")

@handle_errors

def line_items_delete_all(

    ctx: typer.Context,

    document_id: int = typer.Argument(...),

) -> None:

    """Delete every line item on a document."""

    client = get_client(ctx)

    client.delete_line_items(document_id=document_id)

    emit(ctx, {"deleted_all_on_document": document_id})

line_items_list

def line_items_list(
    ctx: 'typer.Context',
    document_id: 'int' = <typer.models.ArgumentInfo object at 0x7f078bb71ca0>
) -> 'None'

List all line items on a document.

View Source
@line_items_app.command("list")

@handle_errors

def line_items_list(

    ctx: typer.Context,

    document_id: int = typer.Argument(..., help="Parent document ID."),

) -> None:

    """List all line items on a document."""

    client = get_client(ctx)

    emit(ctx, client.get_line_items(document_id=document_id))

list_documents

def list_documents(
    ctx: 'typer.Context',
    q: 'Optional[str]' = <typer.models.OptionInfo object at 0x7f078bb2e070>,
    external_id: 'Optional[str]' = <typer.models.OptionInfo object at 0x7f078bb69220>,
    tag: 'Optional[str]' = <typer.models.OptionInfo object at 0x7f078bb69280>,
    created_gt: 'Optional[str]' = <typer.models.OptionInfo object at 0x7f078bb690a0>,
    created_gte: 'Optional[str]' = <typer.models.OptionInfo object at 0x7f078bb690d0>,
    created_lt: 'Optional[str]' = <typer.models.OptionInfo object at 0x7f078bb69160>,
    created_lte: 'Optional[str]' = <typer.models.OptionInfo object at 0x7f078bb71310>,
    extra: 'Optional[List[str]]' = <typer.models.OptionInfo object at 0x7f078bb71cd0>
) -> 'None'

Search/list previously processed documents.

View Source
@app.command("list")

@handle_errors

def list_documents(

    ctx: typer.Context,

    q: Optional[str] = typer.Option(None, "--q", help="Free-text search query."),

    external_id: Optional[str] = typer.Option(None, "--external-id"),

    tag: Optional[str] = typer.Option(None, "--tag"),

    created_gt: Optional[str] = typer.Option(None, "--created-gt"),

    created_gte: Optional[str] = typer.Option(None, "--created-gte"),

    created_lt: Optional[str] = typer.Option(None, "--created-lt"),

    created_lte: Optional[str] = typer.Option(None, "--created-lte"),

    extra: Optional[List[str]] = typer.Option(

        None, "--param", help="Extra query parameter KEY=VALUE. Repeat for multiple."

    ),

) -> None:

    """Search/list previously processed documents."""

    client = get_client(ctx)

    emit(

        ctx,

        client.get_documents(

            q=q,

            external_id=external_id,

            tag=tag,

            created_gt=created_gt,

            created_gte=created_gte,

            created_lt=created_lt,

            created_lte=created_lte,

            **parse_kv(extra),

        ),

    )

process_document

def process_document(
    ctx: 'typer.Context',
    file: 'str' = <typer.models.OptionInfo object at 0x7f078bb2e520>,
    categories: 'Optional[List[str]]' = <typer.models.OptionInfo object at 0x7f078bb2e4f0>,
    delete_after_processing: 'bool' = <typer.models.OptionInfo object at 0x7f078bb2e4c0>,
    fields: 'Optional[List[str]]' = <typer.models.OptionInfo object at 0x7f078bb2e490>,
    json_body: 'Optional[str]' = <typer.models.OptionInfo object at 0x7f078bb2e460>
) -> 'None'

Upload a receipt or invoice and extract structured data.

View Source
@app.command("process")

@handle_errors

def process_document(

    ctx: typer.Context,

    file: str = typer.Option(

        ...,

        "--file",

        "-f",

        help="Path to a local file to upload, or '-' to read raw bytes from stdin.",

    ),

    categories: Optional[List[str]] = typer.Option(

        None,

        "--category",

        help="Optional category. Repeat to pass multiple (e.g. --category Travel --category Meals).",

    ),

    delete_after_processing: bool = typer.Option(

        False,

        "--delete-after-processing/--keep",

        help="Delete the document from Veryfi after extraction.",

    ),

    fields: Optional[List[str]] = typer.Option(

        None,

        "--field",

        help="Extra body parameter KEY=VALUE. Repeat for multiple.",

    ),

    json_body: Optional[str] = typer.Option(

        None,

        "--json-body",

        help="Extra body parameters as a JSON object. Merged before --field overrides.",

    ),

) -> None:

    """Upload a receipt or invoice and extract structured data."""

    client = get_client(ctx)

    result = client.process_document(

        file_path=read_file_arg(file),

        categories=optional_list(categories),

        delete_after_processing=delete_after_processing,

        **merge_body(fields, json_body),

    )

    emit(ctx, result)

process_document_url

def process_document_url(
    ctx: 'typer.Context',
    file_url: 'Optional[str]' = <typer.models.OptionInfo object at 0x7f078bb2e3d0>,
    file_urls: 'Optional[List[str]]' = <typer.models.OptionInfo object at 0x7f078bb2e3a0>,
    categories: 'Optional[List[str]]' = <typer.models.OptionInfo object at 0x7f078bb2e370>,
    delete_after_processing: 'bool' = <typer.models.OptionInfo object at 0x7f078bb2e340>,
    boost_mode: 'bool' = <typer.models.OptionInfo object at 0x7f078bb2e310>,
    external_id: 'Optional[str]' = <typer.models.OptionInfo object at 0x7f078bb2e2e0>,
    max_pages_to_process: 'Optional[int]' = <typer.models.OptionInfo object at 0x7f078bb2e2b0>,
    fields: 'Optional[List[str]]' = <typer.models.OptionInfo object at 0x7f078bb2e280>,
    json_body: 'Optional[str]' = <typer.models.OptionInfo object at 0x7f078bb2e250>
) -> 'None'

Process a remote receipt or invoice by URL.

View Source
@app.command("process-url")

@handle_errors

def process_document_url(

    ctx: typer.Context,

    file_url: Optional[str] = typer.Option(

        None, "--file-url", help="Publicly accessible URL of the file."

    ),

    file_urls: Optional[List[str]] = typer.Option(

        None,

        "--file-urls",

        help="Publicly accessible URLs (repeat the flag). Alternative to --file-url.",

    ),

    categories: Optional[List[str]] = typer.Option(None, "--category"),

    delete_after_processing: bool = typer.Option(False, "--delete-after-processing/--keep"),

    boost_mode: bool = typer.Option(False, "--boost-mode/--no-boost-mode"),

    external_id: Optional[str] = typer.Option(

        None, "--external-id", help="Optional custom ID to attach to the document."

    ),

    max_pages_to_process: Optional[int] = typer.Option(

        None, "--max-pages", help="Cap processing at this many pages."

    ),

    fields: Optional[List[str]] = typer.Option(None, "--field"),

    json_body: Optional[str] = typer.Option(None, "--json-body"),

) -> None:

    """Process a remote receipt or invoice by URL."""

    if not file_url and not file_urls:

        raise typer.BadParameter("either --file-url or --file-urls must be provided")

    client = get_client(ctx)

    result = client.process_document_url(

        file_url=file_url,

        file_urls=optional_list(file_urls),

        categories=optional_list(categories),

        delete_after_processing=delete_after_processing,

        boost_mode=boost_mode,

        external_id=external_id,

        max_pages_to_process=max_pages_to_process,

        **merge_body(fields, json_body),

    )

    emit(ctx, result)

process_documents_bulk

def process_documents_bulk(
    ctx: 'typer.Context',
    file_urls: 'List[str]' = <typer.models.OptionInfo object at 0x7f078bb2e190>
) -> 'None'

Submit several remote URLs in a single bulk request.

View Source
@app.command("process-bulk")

@handle_errors

def process_documents_bulk(

    ctx: typer.Context,

    file_urls: List[str] = typer.Option(

        ...,

        "--file-url",

        help="Publicly accessible URL. Repeat the flag for each file.",

    ),

) -> None:

    """Submit several remote URLs in a single bulk request."""

    client = get_client(ctx)

    emit(ctx, client.process_documents_bulk(file_urls=list(file_urls)))

set_get

def set_get(
    ctx: 'typer.Context',
    document_id: 'int' = <typer.models.ArgumentInfo object at 0x7f078bbcfca0>
) -> 'None'

Fetch all documents inside a PDF set.

View Source
@set_app.command("get")

@handle_errors

def set_get(

    ctx: typer.Context,

    document_id: int = typer.Argument(..., help="Document-set ID."),

) -> None:

    """Fetch all documents inside a PDF set."""

    client = get_client(ctx)

    emit(ctx, client.get_documents_from_pdf(document_id=document_id))

set_list

def set_list(
    ctx: 'typer.Context',
    extra: 'Optional[List[str]]' = <typer.models.OptionInfo object at 0x7f078bbcfee0>
) -> 'None'

List previously processed PDF document sets.

View Source
@set_app.command("list")

@handle_errors

def set_list(

    ctx: typer.Context,

    extra: Optional[List[str]] = typer.Option(None, "--param"),

) -> None:

    """List previously processed PDF document sets."""

    client = get_client(ctx)

    emit(ctx, client.get_pdf(**parse_kv(extra)))

split_pdf

def split_pdf(
    ctx: 'typer.Context',
    file: 'str' = <typer.models.OptionInfo object at 0x7f078bbd9670>,
    categories: 'Optional[List[str]]' = <typer.models.OptionInfo object at 0x7f078bbd96d0>,
    fields: 'Optional[List[str]]' = <typer.models.OptionInfo object at 0x7f078bbd9100>,
    json_body: 'Optional[str]' = <typer.models.OptionInfo object at 0x7f078bbd90a0>
) -> 'None'

Split a multi-page PDF and process each page.

View Source
@set_app.command("split")

@handle_errors

def split_pdf(

    ctx: typer.Context,

    file: str = typer.Option(..., "--file", "-f", help="Local PDF path, or '-' for stdin."),

    categories: Optional[List[str]] = typer.Option(None, "--category"),

    fields: Optional[List[str]] = typer.Option(None, "--field"),

    json_body: Optional[str] = typer.Option(None, "--json-body"),

) -> None:

    """Split a multi-page PDF and process each page."""

    client = get_client(ctx)

    emit(

        ctx,

        client.split_and_process_pdf(

            file_path=read_file_arg(file),

            categories=optional_list(categories),

            **merge_body(fields, json_body),

        ),

    )

split_pdf_url

def split_pdf_url(
    ctx: 'typer.Context',
    file_url: 'Optional[str]' = <typer.models.OptionInfo object at 0x7f078bbd99a0>,
    file_urls: 'Optional[List[str]]' = <typer.models.OptionInfo object at 0x7f078bbcf6d0>,
    categories: 'Optional[List[str]]' = <typer.models.OptionInfo object at 0x7f078bbcf7f0>,
    max_pages_to_process: 'Optional[int]' = <typer.models.OptionInfo object at 0x7f078bbcf850>,
    fields: 'Optional[List[str]]' = <typer.models.OptionInfo object at 0x7f078bbcfa30>,
    json_body: 'Optional[str]' = <typer.models.OptionInfo object at 0x7f078bbcfa90>
) -> 'None'

Split a remote PDF and process each page.

View Source
@set_app.command("split-url")

@handle_errors

def split_pdf_url(

    ctx: typer.Context,

    file_url: Optional[str] = typer.Option(None, "--file-url"),

    file_urls: Optional[List[str]] = typer.Option(None, "--file-urls"),

    categories: Optional[List[str]] = typer.Option(None, "--category"),

    max_pages_to_process: Optional[int] = typer.Option(None, "--max-pages"),

    fields: Optional[List[str]] = typer.Option(None, "--field"),

    json_body: Optional[str] = typer.Option(None, "--json-body"),

) -> None:

    """Split a remote PDF and process each page."""

    if not file_url and not file_urls:

        raise typer.BadParameter("either --file-url or --file-urls must be provided")

    client = get_client(ctx)

    emit(

        ctx,

        client.split_and_process_pdf_url(

            file_url=file_url,

            file_urls=optional_list(file_urls),

            categories=optional_list(categories),

            max_pages_to_process=max_pages_to_process,

            **merge_body(fields, json_body),

        ),

    )

tags_add

def tags_add(
    ctx: 'typer.Context',
    document_id: 'int' = <typer.models.ArgumentInfo object at 0x7f078bbd95e0>,
    name: 'str' = <typer.models.OptionInfo object at 0x7f078bbd9580>
) -> 'None'

Attach a single tag.

View Source
@tags_app.command("add")

@handle_errors

def tags_add(

    ctx: typer.Context,

    document_id: int = typer.Argument(...),

    name: str = typer.Option(..., "--name", help="Tag name to attach."),

) -> None:

    """Attach a single tag."""

    client = get_client(ctx)

    emit(ctx, client.add_tag(document_id=document_id, tag_name=name))

tags_add_many

def tags_add_many(
    ctx: 'typer.Context',
    document_id: 'int' = <typer.models.ArgumentInfo object at 0x7f078bbd94c0>,
    tags: 'List[str]' = <typer.models.OptionInfo object at 0x7f078bbd9460>
) -> 'None'

Attach several tags at once.

View Source
@tags_app.command("add-many")

@handle_errors

def tags_add_many(

    ctx: typer.Context,

    document_id: int = typer.Argument(...),

    tags: List[str] = typer.Option(..., "--tag", help="Tag name. Repeat for multiple."),

) -> None:

    """Attach several tags at once."""

    client = get_client(ctx)

    emit(ctx, client.add_tags(document_id=document_id, tags=list(tags)))

tags_delete

def tags_delete(
    ctx: 'typer.Context',
    document_id: 'int' = <typer.models.ArgumentInfo object at 0x7f078bbd9ee0>,
    tag_id: 'int' = <typer.models.ArgumentInfo object at 0x7f078bbd9d30>
) -> 'None'

Detach a single tag.

View Source
@tags_app.command("delete")

@handle_errors

def tags_delete(

    ctx: typer.Context,

    document_id: int = typer.Argument(...),

    tag_id: int = typer.Argument(...),

) -> None:

    """Detach a single tag."""

    client = get_client(ctx)

    client.delete_tag(document_id=document_id, tag_id=tag_id)

    emit(ctx, {"deleted": {"document_id": document_id, "tag_id": tag_id}})

tags_delete_all

def tags_delete_all(
    ctx: 'typer.Context',
    document_id: 'int' = <typer.models.ArgumentInfo object at 0x7f078bbd9d90>
) -> 'None'

Detach every tag from a document.

View Source
@tags_app.command("delete-all")

@handle_errors

def tags_delete_all(

    ctx: typer.Context,

    document_id: int = typer.Argument(...),

) -> None:

    """Detach every tag from a document."""

    client = get_client(ctx)

    client.delete_tags(document_id=document_id)

    emit(ctx, {"deleted_all_on_document": document_id})

tags_list

def tags_list(
    ctx: 'typer.Context',
    document_id: 'int' = <typer.models.ArgumentInfo object at 0x7f078bbd9790>
) -> 'None'

List tags on a document.

View Source
@tags_app.command("list")

@handle_errors

def tags_list(

    ctx: typer.Context,

    document_id: int = typer.Argument(...),

) -> None:

    """List tags on a document."""

    client = get_client(ctx)

    emit(ctx, client.get_tags(document_id=document_id))

tags_replace

def tags_replace(
    ctx: 'typer.Context',
    document_id: 'int' = <typer.models.ArgumentInfo object at 0x7f078bbd9880>,
    tags: 'List[str]' = <typer.models.OptionInfo object at 0x7f078bbd98e0>
) -> 'None'

Replace all tags with the supplied set.

View Source
@tags_app.command("replace")

@handle_errors

def tags_replace(

    ctx: typer.Context,

    document_id: int = typer.Argument(...),

    tags: List[str] = typer.Option(..., "--tag", help="Replacement tag name. Repeat for multiple."),

) -> None:

    """Replace all tags with the supplied set."""

    client = get_client(ctx)

    emit(ctx, client.replace_tags(document_id=document_id, tags=list(tags)))

update_document

def update_document(
    ctx: 'typer.Context',
    document_id: 'int' = <typer.models.ArgumentInfo object at 0x7f078bb71c40>,
    fields: 'Optional[List[str]]' = <typer.models.OptionInfo object at 0x7f078bb71be0>,
    json_body: 'Optional[str]' = <typer.models.OptionInfo object at 0x7f078bb71340>
) -> 'None'

Update mutable fields on a document.

View Source
@app.command("update")

@handle_errors

def update_document(

    ctx: typer.Context,

    document_id: int = typer.Argument(..., help="Document ID."),

    fields: Optional[List[str]] = typer.Option(

        None,

        "--field",

        help="Field to update: KEY=VALUE. Repeat for multiple.",

    ),

    json_body: Optional[str] = typer.Option(None, "--json-body"),

) -> None:

    """Update mutable fields on a document."""

    body = merge_body(fields, json_body)

    if not body:

        raise typer.BadParameter("at least one --field or --json-body is required")

    client = get_client(ctx)

    emit(ctx, client.update_document(document_id=document_id, **body))