Document Storage

Hyphen provides built-in document storage so workflows can operate on uploaded files rather than requiring data to be embedded in API payloads. Upload a CSV, PDF, or JSON file once, get back a document_id, and reference it anywhere in your workflow using the doc: prefix.

This keeps large file payloads out of workflow definitions. URL ingestion checks the destination before fetching the file, and workflow execution reads the stored copy.


Uploading Documents

Upload via multipart form data:

bash
curl -X POST https://your-hyphen.example.com/documents \
  -H "X-Org-Id: acme-corp" \
  -F "file=@transactions_q4.csv" \
  -F 'name=Q4 Transactions' \
  -F 'tags=["finance", "quarterly"]' \
  -F 'metadata={"department": "accounting"}' \
  -F 'ttl_days=90'

Response:

json
{
  "document": {
    "id": "doc_a1b2c3d4e5f6",
    "name": "Q4 Transactions",
    "content_type": "text/csv",
    "size_bytes": 245890,
    "checksum_sha256": "e3b0c44298fc...",
    "current_version": 1,
    "status": "ready",
    "tags": ["finance", "quarterly"],
    "created_at": "2026-02-01T00:00:00Z"
  }
}

Supported Content Types

Content Type Extensions Max Size
CSV .csv 50 MB
JSON .json 50 MB
Plain text .txt 50 MB
PDF .pdf 50 MB
Excel .xlsx 50 MB
Images .png, .jpg, .jpeg 50 MB

Deduplication

If you upload a file with the same SHA-256 checksum as an existing document in your organization, Hyphen returns 200 with the existing document and deduplicated: true. A newly stored document returns 201.

Upload from URL

For files already hosted elsewhere:

bash
curl -X POST https://your-hyphen.example.com/documents/from-url \
  -H "X-Org-Id: acme-corp" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://storage.example.com/reports/q4.csv",
    "name": "Q4 Report",
    "tags": ["finance"]
  }'

Hyphen fetches the file, validates it, and stores it internally. The external URL is not accessed at workflow execution time.


Referencing Documents in Workflows

Use the doc: prefix anywhere you would normally pass inline data:

json
{
  "type": "matcher",
  "properties": {
    "left": "doc:doc_a1b2c3d4e5f6",
    "right": "doc:doc_x7y8z9w0v1u2",
    "matchOn": ["invoice_id"],
    "tolerance": 50,
    "outputMatched": "matched",
    "outputUnmatchedLeft": "unmatched_invoices"
  }
}

At execution time, the engine retrieves the stored file and resolves it according to content type.

Resolution by Content Type

Content Type Resolves To Example Use
text/csv Array<Object> with header values as keys Matcher left/right, Loop items_path
application/json Object or Array as-is Any property expecting structured data
text/plain String LLM template input, text analysis
application/pdf Text extraction result with document metadata; binary metadata if extraction fails Agent objective or tool input
image/* Binary metadata with the document ID, type, size, and version Tool input for image processing
Excel (.xlsx) Binary metadata with the document ID, type, size, and version Tool input for spreadsheet processing

CSV and JSON arrays resolve up to the configured extraction row limit, which defaults to 10,000 rows per document.

CSV parsing is strict. Use UTF-8, comma-delimited files with a header row and consistent columns. Validation errors identify the first inconsistent row found during upload.

### Using `doc:` with Loop
json
{
  "type": "loop",
  "properties": {
    "mode": "foreach",
    "items_path": "doc:doc_customer_list",
    "item_variable_name": "customer",
    "actions_to_execute": [
      {
        "type": "custom-table",
        "properties": {
          "table": "welcome_email_queue",
          "operation": "upsert",
          "key_fields": ["email"],
          "keys": ["email", "name", "status"],
          "values": ["@customer.email", "@customer.name", "ready"]
        }
      }
    ]
  }
}

The CSV resolves to an array, each row becomes @customer inside the loop, and each row is written to the delivery queue. A separate workflow can dispatch the queued emails through Gmail or Outlook with its own retry and evidence boundary.


Versioning

Upload a new version of an existing document without changing the document_id:

bash
curl -X POST https://your-hyphen.example.com/documents/doc_a1b2c3d4e5f6/versions \
  -H "X-Org-Id: acme-corp" \
  -F "file=@transactions_q4_updated.csv" \
  -F 'change_note=Updated with December corrections'

The current_version increments automatically. Workflows referencing doc:doc_a1b2c3d4e5f6 always get the latest version.

Pinning to a Version

For audit reproducibility, pin a specific version:

json
{
  "left": "doc:doc_a1b2c3d4e5f6@2"
}

The @N suffix locks to version N. A pinned reference lets a run use the same dataset version during review or replay.

Reading Version History

Use the document audit endpoint for version history:

bash
curl https://your-hyphen.example.com/documents/doc_a1b2c3d4e5f6/audit \
  -H "X-Org-Id: acme-corp"

The audit stream includes version_created entries with version metadata.


Webhook Triggers

Register webhooks to trigger your own automation service when documents are uploaded:

json
{
  "event": "document.uploaded",
  "url": "https://automation.example.com/hyphen/hooks/document-uploaded",
  "filter": {
    "tags": ["invoices"],
    "content_type": "text/csv"
  },
  "auto_payload": {
    "workflow_id": "wfl-123e4567-e89b-12d3-a456-426614174000"
  }
}

Your automation service can then call POST /workflows/:id/execute with:

json
{
  "input": {
    "invoices": "doc:<document_id>"
  }
}

This enables the upload-to-execution pattern: data providers drop files, workflows run automatically. See Webhooks for full configuration.


Audit Trail

Every document action is tracked:

Event When
uploaded New document created
version_created New version uploaded
downloaded File content retrieved
metadata_updated Metadata changed
deleted Document soft-deleted
bash
curl https://your-hyphen.example.com/documents/doc_a1b2c3d4e5f6/audit?limit=50 \
  -H "X-Org-Id: acme-corp"

Storage Limits

Limit Default
Storage per organization 10 GB
Maximum file size 50 MB

Check current usage:

bash
curl https://your-hyphen.example.com/documents/storage-usage \
  -H "X-Org-Id: acme-corp"

Common Patterns

Reconciliation with uploaded datasets. Upload invoices and payments as CSVs, reference both in a matcher step, investigate the exceptions, and write the result to a custom table.

Agent document processing. Upload a PDF and reference it in an agent objective or input. Hyphen extracts the text before the agent reasons over the content.

Scheduled reconciliation with versioned data. Upload a new version each month. A scheduled workflow can use the latest version, while a pinned reference supports historical comparison.

Webhook-driven ingestion. An external system uploads a CSV, the document.uploaded webhook fires, and your automation starts the right workflow. This is the Agent as Trigger pattern with document storage.

Document storage works with existing primitives. Matcher can consume doc: references, loop can iterate over uploaded CSV or JSON arrays, and agents can receive extracted PDF text. Documents use the same context resolution model as other workflow inputs.

→ Next: [Conditional Logic](/platform/conditional-logic)