Learning the OAA Runner Framework: Part 1 — Your First CSV Upload

Calvin stands on a step stool dramatically pressing a giant "UPLOAD" button on an old computer. Hobbes watches from behind, tail raised in anticipation. A large scroll of paper labeled "data.csv" unrolls across the floor.

This is Part 1 of the Learning the OAA Runner Framework series.

What you’ll learn: Create a working config.yaml that reads a CSV file and maps its columns to OAA user fields. By the end you’ll run the integration and see oaa-runner load users from a CSV without errors.

Prerequisites

Setup

Create your working directory:

mkdir -p 01-csv-upload/data
cd 01-csv-upload

Copy the following content into a new file at data/users.csv:

user_id,email,first_name,last_name
1001,alice@example.com,Alice,Anderson
1002,bob@example.com,Bob,Baker
1003,carol@example.com,Carol,Chen

Create an empty config.yaml:

touch config.yaml

Your goal: load these three users into Veza as a Custom App called “Example App - CSV Upload”.


Step 1: Define the application

Open config.yaml and add the following at the top:

version: v2
log_level: INFO

app:
  name: Example App - CSV Upload
  application_type: Custom App
  • version: v2 — required header for all oaa-runner configs.

  • app.name — the name that appears in Veza.

  • application_type: Custom App — tells oaa-runner to create a generic custom application.


Step 2: Add a source

A source is a named data stream that feeds one type of OAA entity. Add a sources block below the app block:

sources:
  users:
    destination: LocalUser
    source_type: csv_push
    csv_filepath: data/users.csv
    csv_provider_id: "!env"
  • destination: LocalUser — the OAA entity type for users who belong to an application.

  • source_type: csv_push — reads a local CSV and pushes it to Veza’s CSV ingest endpoint. This is the simpler of the two CSV source types: it uploads the file as-is without running it through oaa-runner’s transform or generator pipeline. A second source type, csv, processes the file locally through the full pipeline and is required for features like generators and hooks — it is introduced in Part 3 — Modeling a Custom Application.

  • csv_filepath — path to the CSV, relative to the config file.

  • csv_provider_id: "!env" — tells oaa-runner to read the CSV provider ID from the environment variable CSV_PROVIDER_ID. The !env shorthand derives the env var name from the field name (uppercased). This syntax is deprecated — for connection parameters, use required_env with an explicit env var name instead. See How to use required env. csv_provider_id is a source-level field that currently only supports !env; new connection-level params (API keys, base URLs) should use required_env.


Step 3: Map columns to OAA fields

Each CSV column needs to be mapped to an OAA field. Add a field_mapping block inside the users source:

    field_mapping:
      user_id:
        destination_field: unique_id
      email:
        destination_field: email
      first_name:
        destination_field: given_name
      last_name:
        destination_field: family_name

Each key in field_mapping is a CSV column name. destination_field is the OAA field it maps to:

Destination field

OAA meaning

unique_id

Required. Stable identifier for the user within this app.

email

User’s email address. Used for cross-system identity matching.

given_name

First name, stored as a custom property.

family_name

Last name, stored as a custom property.


Complete config

Your config.yaml should now look like this:

version: v2
log_level: INFO

app:
  name: Example App - CSV Upload
  application_type: Custom App

sources:
  users:
    destination: LocalUser
    source_type: csv_push
    csv_filepath: data/users.csv
    csv_provider_id: "!env"
    field_mapping:
      user_id:
        destination_field: unique_id
      email:
        destination_field: email
      first_name:
        destination_field: given_name
      last_name:
        destination_field: family_name

Run it

From the 01-csv-upload directory:

uv tool run --from oaa-runner oaa app run --config-file=config.yaml --no-push

--no-push loads and validates the data locally without sending anything to Veza. Environment variables are not required for a dry run — the CSV_PROVIDER_ID value in your config is read but the push step is skipped. See How to run integrations locally for an explanation of the uv tool run --from oaa-runner command pattern.

You should see output like:

INFO - Skipping CSV push to Veza (no_push=True)
status: true
report: []

Note: You may also see a Pydantic serialization warning about data_plane_id. This is a known cosmetic issue in oaa-runner v0.2.x and does not affect the integration output.

If you see errors, check that:

  • You’re running from inside the 01-csv-upload directory

  • data/users.csv exists and has the correct column names

To push to your Veza instance, remove --no-push and set the required environment variables:

export VEZA_URL=https://your-veza-instance.veza.com
export VEZA_API_KEY=your_api_key
export CSV_PROVIDER_ID=your_csv_provider_id

uv tool run --from oaa-runner oaa app run --config-file=config.yaml

What’s next

This config maps columns directly — one CSV column to one OAA field, no modifications. The next example adds transforms to handle messy data: mixed-case emails, inconsistent boolean values, date strings, and columns that don’t map to a standard OAA field.

Next: Part 2 — Transforming Fields

See also