All templates

Enterprise S3 File Intake Workflow

Govern files stored in AWS S3 without Xano ever holding the bytes. Register uploaded objects, validate file type and size, route through an approval state machine, and maintain a full audit trail - all joined to S3 by object key.

Prompt Start building in Xano: Access the prompt at https://go.xano.co/start-xano-with-template-skill and run it here with file-intake-s3 as your template.

Copy and paste the prompt in your local coding agent of choice.

View on GitHub
The line, before it boards

Template preview: canvas and code.

Client app Client app Web · mobile · agent s3_file_intake API s3_file_intake API files/{file_id}/approve files/{file_id}/mark-processed files/register files/{file_id}/reject files/{file_id}/send-to-review files/{file_id}/status files/{file_id}/validate Business logic Business logic S3 file intake check auth S3 file intake check transition S3 file intake log request S3 file intake record event S3 file intake s3 head S3 file intake s3 object url S3 file intake validate meta... Data Data api_request_logs file_events file_reviews files External services External services AWS S3
Boarding

Start in your local coding agent.

Copy the prompt above and paste it in your local coding agent of choice. It builds this template into your workspace, ready to customize and test.

Coding agents on Xano
Prompt Start building in Xano Claude Code Codex Cursor VS Code Your Xano workspace ready to customize copy, paste in your local coding agent of choice

Overview

Govern files stored in AWS S3 without Xano ever holding the bytes - register uploaded objects, validate type and size, route them through an approval state machine, and keep a full audit trail, all joined to S3 by object key.

S3 is the system of record for the bytes; Xano is the system of record for the metadata, status, and decisions. Drop this into a Xano workspace, point it at your bucket, and you get a complete intake pipeline - register → validate → review → approve → process - with every step guarded and logged.

What it provides

Files arrive in a bucket and then what? Someone eyeballs them, moves the good ones somewhere, and the record of "who approved this, when, and why" lives in memory or a thread. There's no gate that stops an unvalidated or unapproved file from moving downstream, and no reconstructable history when finance or audit asks.

This template makes the intake a governed workflow instead of a convention. Every file moves through an explicit state machine - registered → validated → needs_review → approved → processed (with failed and rejected branches) - and illegal transitions are refused server-side, not left to the UI. Validation runs at the gate (allowed type, size ceiling, and an optional real S3 object-existence check). Every transition appends an immutable event, every review is recorded, and every API call is logged on success and failure. Xano owns the workflow and the audit; S3 keeps holding the bytes.

Repo layout

backend/
  api/
    s3_file_intake/
      api_group.xs
      files_approve_post.xs
      files_mark_processed_post.xs
      files_register_post.xs
      files_reject_post.xs
      files_send_to_review_post.xs
      files_status_get.xs
      files_validate_post.xs
  function/
    s3_file_intake_check_auth.xs
    s3_file_intake_check_transition.xs
    s3_file_intake_log_request.xs
    s3_file_intake_record_event.xs
    s3_file_intake_s3_head.xs
    s3_file_intake_s3_object_url.xs
    s3_file_intake_validate_metadata.xs
  table/
    api_request_logs.xs
    file_events.xs
    file_reviews.xs
    files.xs
  workflow_test/
    s3_file_intake_happy_path.xs
    s3_file_intake_reject_path.xs
  workspace/
    templates.xs

How it works

A file is a row in files carrying a status and joined to its S3 object by a unique s3_key. The lifecycle is a guarded state machine - the transition guard (s3_file_intake_check_transition) is a pure function, so the rules hold no matter which endpoint is called:

                ┌──────────────► failed        (bad type/size, or S3 object missing)
                │
 registered ──► validated ──► needs_review ──► approved ──► processed
                                    │
                                    └────────► rejected
  • register records the metadata and creates the files row (registered).
  • validate (only from registered) checks the type is one of pdf | csv | xlsx | json and size ≤ 25 MB; if AWS credentials are set it additionally confirms the object exists in S3 via Xano's native cloud.aws.s3.get_file_info. Passes → validated, fails → failed.
  • send-to-review (only from validated) opens a pending review → needs_review.
  • approve / reject (only from needs_review) record a reviewer decision.
  • mark-processed (only from approved) closes the loop → processed.

Every transition appends a file_events row, so a file's history is always reconstructable from the log; every request writes an api_request_logs row. There is deliberately no AI extraction and no Azure/GCS support - this is intake, validation, approval, and auditability.

Quick start

Start with your coding agent. Paste the prompt below into a local MCP-capable coding agent - Claude Code, Cursor, Windsurf, and others. It installs the Xano CLI, connects Xano, and imports this template.

Start building in Xano: Access the prompt at https://go.xano.co/start-xano-with-template-skill and run it here with file-intake-s3 as your template.

The agent will walk you through the setup below.

  1. Push the backend to a Xano workspace (the CLI/agent flow below does this).
  2. Call the flow - POST /files/register → POST /files/{file_id}/validate → send-to-review → approve → mark-processed, then GET /files/{file_id}/status for the metadata + full event history.
  3. It runs before you wire S3. With API_AUTH_SECRET unset the auth check is a deliberate no-op, and with the AWS keys unset the S3 object-existence check is skipped (validation still runs on the declared metadata) - so the whole state machine works out of the box. Set the environment variables to enforce auth and confirm objects really exist in your bucket.

API surface

All endpoints live in the S3FileIntake API group (canonical s3-file-intake) and require the API secret when API_AUTH_SECRET is set (header X-API-Secret: <secret> or an api_secret field). Every call writes an api_request_logs row.

Method Path Purpose
POST /files/register Record an uploaded object's metadata → a registered files row + event.
POST /files/{file_id}/validate Validate type + size (and S3 existence when creds are set) → validated or failed.
POST /files/{file_id}/send-to-review From validated → needs_review + a pending review row.
POST /files/{file_id}/approve From needs_review → approved (records reviewer + note).
POST /files/{file_id}/reject From needs_review → rejected (requires a note).
POST /files/{file_id}/mark-processed From approved → processed.
GET /files/{file_id}/status Metadata, current status, latest review, canonical S3 URL, and full event history.

Database Tables

  • files - one row per file: s3_key (unique), file_name, file_type, file_size_bytes, uploaded_by, status, timestamps.
  • file_events - append-only audit trail: one row per lifecycle action (file_id, event_type, event_payload, created_by).
  • file_reviews - one row per review: review_status (pending → approved / rejected), reviewer_id, review_note.
  • api_request_logs - one row per API call (endpoint, requester, status, error), written on success and failure.

Testing

Run from a deployed workspace with xano workflow_test run_all:

  • s3_file_intake_happy_path - register → validate → send-to-review → approve → mark-processed, asserting each status transition and the resulting event trail.
  • s3_file_intake_reject_path - the review-rejection branch and that illegal transitions are refused.

Both run credential-free (the S3 existence check is skipped and the auth check is a no-op when their env vars are unset), so the full state machine is exercised without an AWS account.

Environment variables

Set these in your Xano workspace (Settings → Environment Variables) to enforce auth and enable the real S3 checks. The state machine runs without them (auth no-op, S3 existence check skipped).

  • S3_BUCKET_NAME - your bucket; used to build the canonical S3 object URL and for the existence check.
  • AWS_REGION - the bucket's region; used for the URL and the existence check.
  • AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY - used by validate to confirm the object exists in S3 (cloud.aws.s3.get_file_info). When unset, the existence check is skipped and validation runs on the declared metadata only.
  • API_AUTH_SECRET - the shared secret every endpoint checks (via X-API-Secret header or api_secret field). When unset the check is a deliberate no-op so the module runs out of the box; set it in production.
Questions

Frequently asked questions.

What file types and sizes does it accept?
It accepts pdf, csv, xlsx, and json up to 25 MB; anything else is marked `failed` at validation. The allowed types and size ceiling live in one validation function (`s3_file_intake_validate_metadata`), so you widen or narrow them in one place.
Does Xano store the files?
No - S3 holds the bytes; Xano only stores each file's metadata, status, and decision history, joined to the object by its `s3_key`. When AWS credentials are set, the validate step confirms the object actually exists in S3 via Xano's native `cloud.aws.s3.get_file_info`.
Does this work with S3-compatible storage like MinIO or Cloudflare R2?
The workflow keys off object metadata (bucket and key), so the register → validate → approve flow isn't tied to AWS. The optional object-existence check goes through Xano's native AWS S3 connector, so for a fully S3-compatible store you'd point that at your endpoint and confirm compatibility.
Is this template free to use?
Every template runs on any Xano plan, including the free tier. All you need is a Xano account and a coding agent connected to the CLI and Developer MCP.
How do I add this template to my Xano account?
You can import and run it with your local coding agent - it works against the seed data it ships with. Paste this prompt into your agent: Start building in Xano: Access the prompt at https://go.xano.co/start-xano-with-template-skill and run it here with file-intake-s3 as your template. You can also do a manual clone from Github, still using the Xano CLI and Developer MCP to connect to Xano to build and deploy.
Can I customize and extend this template?
Yes - that's the intent. The backend is plain XanoScript that you can read and AI can build on: add or edit tables, endpoints, and logic, or swap integrations. It ships with tests you can extend as you go.
Why use Xano to build my backend?
Xano gives you an enterprise-grade backend - database, APIs, and logic - that AI can build and you can visually inspect, edit, and trust. Hosted, scalable, and production-ready from day one. Speed of generation, without the black box.

AI builds software. Xano makes it trustworthy.

Get started for free. Paste the template prompt into your coding agent, then customize and test it like any other Xano backend.