Skip to content

Submitting documents

Driving orders via the API

Most tenants receive orders through the mailbox AIOTIC watches. The API adds two more intake paths — for portals, scanners, EDI gateways, or your own mail handling.

Files → one order

POST/order/uploadintegration key

multipart/form-data. All files together form one purchase order (a PO with a separate price list, or a scan of two pages). Optional request_id (UUID v4) and any extra form fields, which are stored as metadata.

bash
curl -X POST https://acme.aiotic.ai/order/upload -H "X-API-Key: $KEY" \
  -F "files=@PO-4711.pdf;type=application/pdf" \
  -F "request_id=7f0c2b6e-1c2a-4f0e-9d4c-2c1a0b7e5a11" \
  -F "source=portal" -F "my_reference=TICKET-8812"
# {"request_id":"7f0c2b6e-1c2a-4f0e-9d4c-2c1a0b7e5a11","split":false}
python
import uuid
up = client.orders.upload(["PO-4711.pdf"], request_id=uuid.uuid4(), metadata={"source": "portal", "my_reference": "TICKET-8812"})
status = client.orders.wait(up.request_id)          # polls with backoff until it lands

Accepted types: .pdf .jpg .jpeg .png .txt .md. Keep files under ~10 MB.

Always pass your own request_id

It makes the upload idempotent (a retry after a network error does not create a second order) and gives you the id before the call returns — handy for logging and for correlating in your ERP.

Raw e-mail → one or more orders

POST/order/raw/uploadintegration key

Upload a complete .eml (headers, body, attachments). AIOTIC classifies it first:

  • Purchase order → processed like a mailbox mail. The e-mail body counts as input ("please change line 3 to 200 pcs" overrides the attachment).
  • Not a purchase order400 with a structured detail (error: not_a_purchase_order, the detected category) and the mail is recorded under rejected e-mails so an operator can override.
  • Several orders in one mail (tenant feature order splitting) → split: true plus one child order per detected order. Poll the children, not the source id.
python
from aiotic import AioticValidationError

try:
    up = client.orders.upload_raw_email("mail.eml")
except AioticValidationError as e:
    if isinstance(e.detail, dict) and e.detail.get("error") == "not_a_purchase_order":
        log.info("skipped %s: %s", e.detail["category"], e.detail["subject"])
        raise SystemExit(0)
    raise

for rid in up.request_ids:                    # 1 id, or the children when split
    status = client.orders.wait(rid)
if up.split:
    group = client.orders.group(up.email_group_id)   # aggregate view

Classify without processing (dry run): POST /order/raw/classify{"category": "purchase_order"}.

After the upload

You get a request_id immediately; processing runs in the background. Continue with Polling & notifications.

What you cannot do through upload

  • Split multipart uploads into several orders — use the raw e-mail path.
  • Attach a customer id up front. AIOTIC identifies the customer itself. (Your extra form fields land in metadata but are not used for identification.)
  • Provide corrections — see Headless limits today.

Documentation revision 3 · Published 8 September 2026 · commit 6862d5e. Verified against AIOTIC API v1.0.0. AIOTIC is a product of DevOps Company.