Skip to content

The command line v0.2

mapi (package mapi-cli, Apache-2.0) works in two locations:

  • Locally, without a server. You scaffold a spec, check it, fit it and score it in your own Python process, with the installed plugins (new, check, fit, score, catalog, plugin, spec).
  • Remotely, against the HTTP API of a platform that runs. You upload datasets, fit, follow runs and score model versions (datasets, runs, and --remote on check, fit, score and catalog).

The generated CLI reference lists all commands and options. The examples on this page are scripts that the test suite runs. The default suite runs the local scripts. make e2e runs the remote scripts.

Running the commands

make install puts mapi into the workspace environment. You can activate the environment (source .venv/bin/activate). Alternatively, you can put uv run before each command (uv run mapi --help).

Without a server

Scaffold a spec from a template. --with-data also writes the sample data of the conformance fixture that matches. This option needs the mapi-conformance package. The workspace has this package.

mapi new tabular -o spec.yaml --with-data     # an XGBoost demand model plus its sample data
Template What it is Conformance fixture
geo-mmm Hierarchical geo MMM (weekly EUR, adstock, saturation) E33
forecast HSGP trend and seasonality forecast C08
tabular XGBoost demand with quantile uncertainty xgb_store_demand
glm Hierarchical GLM, long layout E02

Check the spec statically against the installed plugins and the schema of the data. --stages shows the schema after each pipeline step. If there are errors, the exit status is 1. Thus the command works in CI:

mapi check spec.yaml --data data.zarr.zip --stages

Fit in-process. The bundle goes to bundles/<name>/. It contains the zarr zip, sidecars, plots, metrics and the evaluation bundle of evaluate_then_refit. --side-table NAME=PATH supplies side tables:

mapi fit spec.yaml --data data.zarr.zip       # in-process: writes bundles/<name>/
ls bundles/tabular

mapi score request.yaml --bundle bundles/<name> --data future.zarr.zip -o result.zarr.zip scores a local bundle with a ScoreRequest document.

Local means trusted

Local fit and score import and run the plugins in your own process (isolation NONE), exactly as a notebook does. Use them only with code and data that you trust. On the platform, the same work runs in a sandbox.

Inspect the catalog of installed plugins. Upgrade spec documents across apiVersions:

mapi catalog models
mapi --json catalog steps | jq -r '.steps[].id' | head -n 5
mapi spec upgrade spec.yaml --check           # exit 0: already the current apiVersion

mapi plugin init NAME scaffolds a plugin package (model, entry point, contract-kit tests). mapi plugin test NAME runs its tests. See Write a plugin.

With a server

First, start the stack (make dev, then make seed). See the quickstart.

Select the API

Each remote command finds the API in the same way as the Python client. The first match in this list wins:

  1. --url and --token. These are global options. Put them before the command: mapi --url http://… runs ls.
  2. MAPI_URL, MAPI_TOKEN and MAPI_PROFILE. If MAPI_URL is not set, the CLI uses MAPI_API_URL, the name that make dev uses.
  3. The selected profile in ~/.config/mapi/config.toml. If $MAPI_CONFIG or $XDG_CONFIG_HOME/mapi/config.toml is set, the CLI uses that file.
  4. http://127.0.0.1:8000.
# ~/.config/mapi/config.toml
profile = "local"

[profiles.local]
url = "http://127.0.0.1:8000"

[profiles.other-ports]
url = "http://127.0.0.1:18000"
timeout = 60

mapi config show shows the resolved URL, profile, project and credentials. It also shows the source of each value. It never shows a token. An API token shows as mapi_<prefix>_…. A sign-in shows as the person who signed in and the time when the sign-in ends. mapi config path shows the location of the file.

mapi config set KEY VALUE and mapi config unset KEY change the selected profile. The selected profile is --profile. If you do not give it, it is MAPI_PROFILE, and then the default of the file. These commands write the file again with mode 0600. They do not keep comments. You cannot set secrets in this way. Use mapi login --with-token.

--remote @ on check, fit, score and catalog means "the configured API". --remote URL overrides it for one command.

Tokens

v0.2 is single-tenant and has no authentication. The API listens only on 127.0.0.1. When MAPI_TOKEN is set, the client sends it as a bearer token. The client refuses to send it over plain HTTP to an address that is not a loopback address.

Sign in and choose a project v0.3

A multi-tenant API (v0.3) needs credentials and a project (Tenancy and authentication).

mapi --profile prod --url https://api.mapi.example login \
    --issuer https://accounts.google.com --client-id 1234-abc.apps.googleusercontent.com
mapi --profile prod login --device              # no browser here: enter a code elsewhere
mapi --profile ci login --with-token < ci.token # an API token, read from stdin
mapi whoami                                     # who, how, and your projects and roles
  • mapi login opens the identity provider in your browser. It uses OpenID Connect and the authorization code with PKCE. The provider redirects to a one-time listener on 127.0.0.1.
    • --device signs in with a code that you type on a different device.
    • --no-browser only shows the address.
    • The CLI saves --issuer, --client-id and --discovery-url in the profile. Thus later logins do not need them.
    • Against a local MAPI_PROFILE=dev stack, use mapi login --issuer https://dev-idp.mapi.localhost --discovery-url http://127.0.0.1:8000/dev/idp. Then select an account on the page that opens.
    • The CLI keeps the sign-in in credentials.json next to the config file (mode 0600). It renews the sign-in automatically with its refresh token. It sends the sign-in only to the API that it was made for.
  • mapi login --with-token reads an API token from stdin and checks its checksum. Thus it finds an incorrect character before it sends a request. Then it verifies the token with GET /v1/me and saves it in the profile. It writes the config file again with mode 0600. An API token in the profile or in MAPI_TOKEN has priority over a browser sign-in.
  • mapi whoami shows who you are and how you signed in. It also shows each project where you can act, with your role there.
  • mapi logout removes the sign-in and revokes its refresh token at the identity provider. It also removes the token of the profile. --revoke also revokes that token on the server.

Each v0.2 resource (datasets, specs, runs, models, plots) is now in a project. mapi use checks that you are a member. Then it saves the selection in the profile. The CLI sends the project as X-Mapi-Project, but only on project-scoped requests. --project overrides it for one command. MAPI_PROJECT overrides it for one shell. An API token acts only in its own project.

mapi use acme/growth                            # sent as X-Mapi-Project from now on
mapi --project acme/retail runs ls              # another project, for one command
mapi use                                        # show the selection and where it came from

Tokens, usage and cost v0.3

mapi tokens create budget-planner -s score:geo-mmm-prod -s models:read \
    --expires-in-days 30 --rate-limit 120 > planner.token   # the secret, once, on stdout
mapi tokens ls
mapi tokens revoke tok_01jaf3r1s2t3v4w5x6y7z8a9b0      # asks first; --yes for scripts
mapi usage --group-by resource_class            # this month: cost, compute, quota headroom
mapi usage --tenant acme --period day           # the whole tenant today (tenant admins)
mapi runs get run_01jaf3q2x8m9v7k5h4g3f2e1d0    # includes the run's metered cost

mapi tokens create shows the secret of the new token one time, alone on stdout. Thus, if you redirect stdout to a file, the file contains exactly the token. --save stores the token in the profile instead. No other command ever shows a token. Lists, whoami and config show use the redacted form mapi_<prefix>_….

mapi usage shows these data:

  • The exact cost of the period, in integer micro-units of the currency. A named price table gives the prices.
  • The vCPU-seconds and GiB-seconds behind the cost.
  • How much of each quota is used, and when it resets.

mapi runs get and mapi runs ls show the cost of each run. While the cost is still an estimate, it has a ~.

When the API answers 429, the client does these actions:

  • For RATE_LIMITED, the client waits and then tries again. It waits for the Retry-After of the server, and then uses exponential back-off.
  • For QUOTA_EXCEEDED, the client stops immediately. It shows the reason and the time when the quota resets (retry after 1h 30m). With --json, it also gives retry_after_seconds.

Operators v0.3

Platform operators use mapi admin to manage tenants, quotas, kill switches and executors. Each change shows a before-and-after table and asks for confirmation. For runbooks, --yes skips the confirmation. The server audits each change. Kill-switch changes need --reason.

mapi admin kill-switches set --untrusted-code off --reason "incident 2026-10-15"
mapi admin quotas set tenant acme --vcpu-seconds-per-day 172800 --concurrent-runs 40 \
    --storage 200GiB --note "beta customer"
mapi admin tenants update acme --status suspended --reason "payment overdue"
mapi admin executors ls
mapi admin executors disable cloudrun-jobs --reason "incident 2026-10-15: contain"
mapi admin audit --tenant acme --action token.create --since 2026-10-01

mapi admin quotas set replaces the full policy of a subject. It removes the limits that you do not give. The tier defaults and the other levels still apply.

Upload data

mapi datasets upload does these steps:

  1. If the dataset does not exist, it creates the dataset.
  2. It sends the file to the signed URL that the API returns.
  3. It starts the sandboxed ingest run.
  4. It waits until the version is ready.

CSV and Parquet files need --mapping mapping.yaml. This file is an IngestMapping that gives the column of each dim and variable:

python scripts/seed.py --export demo/      # the E33 tables, score payloads and spec
mapi datasets upload demo/eu_media_future.zarr.zip --name eu_media_future
mapi datasets ls

A zip of images with a manifest table (v0.14) also needs a mapping, with format: image_folder. Refer to Image data. For a folder of images and for network weights (v0.15), use mapi datasets upload-images and mapi datasets upload-weights (see Image collections and weights).

Fit and follow

mapi fit SPEC --remote @ creates the spec. If the text changed, it adds a revision. Then it submits a fit run and follows its event stream until the run ends. It shows warnings, diagnostics and metrics when they arrive. With --no-wait, the command returns after it submits the run. mapi runs watch RUN follows a run at a later time.

mapi check demo/eu-geo-mmm.yaml --remote @   # the server's static validation
mapi fit demo/eu-geo-mmm.yaml --remote @     # create or revise the spec, fit, follow
mapi runs ls --kind fit --limit 3

mapi runs cancel RUN asks for cancellation. mapi runs retry RUN --resource-class L starts a new run from a run that failed.

From v0.11, mapi runs watch and mapi fit --remote also show the metrics that the model logs. They show a maximum of one metrics line each 5 seconds, for example metrics loss=1.23 (step 400).

Track metrics, params and tags v0.11

Each run keeps metrics, params, tags, a name and a note. To see them, do these steps:

  1. Show the metric series of a run: mapi runs metrics RUN.
  2. Show the points of one key: mapi runs metrics RUN --key step_size --points 200.
  3. Show the params: mapi runs params RUN.

You can add values to a run, also after it finished. You need the runner role. A token needs the runs:write scope.

mapi runs log-metric RUN revenue_lift 0.042 --label region=north
mapi runs log-param RUN analyst kim
mapi runs tag RUN stage=production team=growth --delete old_tag
mapi runs rename RUN "weekly sales, adstock 4"
echo "Holiday weeks removed." | mapi runs note RUN -

To find runs by their tracking data, use the options of mapi runs ls:

mapi runs ls --filter 'metric.rmse < 12' --filter 'param.fit.engine = nutpie' \
  --order-by metric.rmse --column metric.rmse --column param.settings.adstock.l_max
mapi runs ls --tag stage=candidate --name adstock --parent RUN

For the grammar of --filter, --order-by and --column, see Search runs.

Media, experiments and comparison v0.14

mapi runs media RUN                                  # key, labels, step, kind, size, caption
mapi runs media RUN --key samples --download out/    # files KEY-LABELS-STEP.EXT
mapi runs log-image RUN holdout_map map.png --step 3 --label split=val --caption "Holdout error"

mapi experiments create "adstock length study" --description "Adstock 2, 4 and 8 weeks."
mapi experiments add "adstock length study" RUN RUN
mapi experiments ls
mapi experiments show "adstock length study"
mapi experiments rename "adstock length study" "adstock study"
mapi experiments archive "adstock study"            # unarchive makes it accept runs again
mapi experiments remove "adstock study" RUN
mapi experiments rm "adstock study"                 # the runs stay
mapi fit spec.yaml --remote @ --experiment "adstock study"
mapi workflows run weekly-readiness -i data=dataset://growth/sales@latest \
  --experiment "adstock study"                     # the node runs join it too
mapi runs ls --experiment "adstock study" --column metric.rmse

mapi runs compare RUN RUN RUN --key loss --key rmse --agg min
mapi runs views save "best fits" --filter 'metric.rmse < 12' --order-by metric.rmse \
  --column metric.rmse
mapi runs views ls
mapi runs ls --view "best fits"
mapi runs views rm "best fits"

An experiment argument (EXP) is an id or the exact name of the experiment. mapi runs compare shows a table of the metrics (one column for each run), then the params that are different (--all-params shows all params). mapi runs ls --view uses the search of the view. The other options add to the search or replace a part of it. mapi runs watch shows a line for each image that the run logs (a maximum of one line each 5 seconds).

Image collections and weights v0.15

mapi datasets missing-images product_photos ./photos            # new and present images
mapi datasets upload-images product_photos ./photos --label-from-folder --size 64x64 --wait
mapi datasets upload-images product_photos ./photos --manifest labels.csv \
  --label-column label --split-column split --state photos.upload.json
mapi datasets upload-images product_photos ./photos --no-base   # upload all images again
mapi datasets upload-weights tiny-resnet resnet.safetensors --arch arch.json

upload-images uploads only the images that the base version does not have (by default the latest ready collection version). If the upload stops, type the same command again. With --state, the command skips the parts that arrived and signs the expired URLs again. The options are in Image data: collections.

upload-weights uploads a safetensors file as a weights dataset (the mapping {format: safetensors}). --arch gives the architecture when the file has no mapi_arch metadata. Read Deep learning.

--resource-class accepts GPU wherever the CLI offers the option, for example mapi runs retry RUN --resource-class GPU. Use it only for a model that uses a GPU. See Deep learning: use the GPU class.

Score

cat > forecast.yaml <<'EOF'
apiVersion: mapi/v1
kind: ScoreRequest
model: model://default/eu-geo-mmm@latest
mode: forecast
horizon: {dim: time, periods: 8}
data: dataset://default/eu_media_future@latest
side_tables: {fx: dataset://default/fx_daily@latest}
output: {kind: expected, summary: [mean, hdi_90]}
EOF
# the CLI has no `models` command yet: take the model id and version from the API
# (with the same credentials as the CLI when MAPI_TOKEN is set)
AUTH=(-H "X-Mapi-Project: ${MAPI_PROJECT:-default/default}")
[ -z "${MAPI_TOKEN:-}" ] || AUTH+=(-H "Authorization: Bearer $MAPI_TOKEN")
MODEL=$(curl -s "${AUTH[@]}" "${MAPI_API_URL:-http://127.0.0.1:8000}/v1/models" \
  | jq -c '.items[] | select(.name == "eu-geo-mmm")')
mapi score forecast.yaml --remote @ --model "$(jq -r .id <<<"$MODEL")" \
  --version "$(jq -r .latest_version <<<"$MODEL")" -o forecast.zarr.zip

mapi score --remote @ sends Prefer: wait=60. To change the time, use --wait. If the run takes longer, the command follows the run. It shows the outputs with their sizes and CRC32C. With -o, it downloads the result tree and checks it against that CRC32C.

The request must give data to score: data, inline values, a scenario dataset or a forecast horizon. If it gives none of them, the server refuses the request before a run starts (SCHEMA_INVALID on /data, "the score request has no data").

Endpoints, webhooks and sharing v0.4

mapi endpoints promote geo-mmm-prod model://growth/eu-geo-mmm@v3   # asks first; --yes skips
mapi endpoints ls
mapi endpoints schema geo-mmm-prod --example > call.json           # a valid score call
mapi endpoints score geo-mmm-prod call.json -o result.zarr.zip
mapi endpoints score geo-mmm-prod --mode forecast --periods 8 --draws 100
mapi endpoints score geo-mmm-prod --data plan.nc                   # inline XrJSON (≤ 5 MB)
mapi endpoints snippets geo-mmm-prod -l python > score.py
mapi endpoints rollback geo-mmm-prod --reason "v3 over-forecasts"

promote goes through the promotion gate. A scorecard with warnings needs --override --reason TEXT from a signed-in project admin. The gate blocks a scorecard that fails. Promotions and rollbacks change what each integration that calls the alias receives. Thus both commands show the current version and ask before they act.

score sends a JSON EndpointScoreCall. The call comes from a file, from stdin (-) or from --example. Flags set these values:

  • The mode and the horizon.
  • --data/--scenario: a dataset:// reference, an XrJSON .json, a .zarr.zip or a netCDF file. The command sends the file inline.
  • --draws.

score waits in the same way as mapi score --remote. -o downloads the result tree. With one --language, snippets shows only the code.

mapi endpoints schema ts-forecast                           # the Input table's columns
mapi endpoints forecast ts-forecast input.csv               # actuals + blank-target future rows
mapi endpoints forecast ts-forecast --actuals new.csv --periods 6
mapi endpoints forecast ts-forecast --actuals new.csv --future 1961-04-01 --future 1961-05-01
mapi endpoints forecast ts-forecast input.csv --dry-run     # print the call, send nothing

forecast sends actuals and the dates to forecast without a new fit. The CSV has a header row with the columns of the input table of the endpoint: the time column, the target and the drivers. schema lists them.

  • Rows with a target value are actuals.
  • Rows after the actuals with an empty target are the forecast.
  • If there are no such rows, the command adds --periods (default 8) after the last actual.
  • The command carries empty drivers on future rows forward (--future-covariates).

The command shows the data that the forecast used ("Forecast updated with 3 actuals …") and the summary table. score also shows them.

mapi webhooks create https://hooks.example.com/mapi -e endpoint.promoted -e run.failed > hook.secret
mapi webhooks ls
mapi webhooks deliveries WEBHOOK_ID --status failed
mapi webhooks replay WEBHOOK_ID DELIVERY_ID
mapi webhooks update WEBHOOK_ID --enable           # back on after --inactive or an auto-disable
mapi webhooks update WEBHOOK_ID -e run.failed --description "on-call"
mapi webhooks rotate WEBHOOK_ID > hook.secret      # asks first; the old secret signs for 24 h
mapi webhooks rm WEBHOOK_ID                        # asks first

create and rotate show the signing secret of a webhook one time, alone on stdout, as for tokens. Lists show whsec_…<last 4>. update changes the events, --url or --description. -e replaces the full list of events. --enable/--disable turns deliveries on or off.

A webhook that you created with --inactive goes back on with --enable. This also applies to a webhook that the platform disabled automatically after many failures. --enable also resets the failure count. Delivery responses are the text of the receiver. The CLI shows them with control sequences removed. Receiving webhooks tells how to verify deliveries.

mapi plots render --model MODEL_ID --version 3 --plot mmm.response_curves -f pdf -o curves.pdf
mapi plots render --saved PLOT_ID --width 1200
mapi plots embed PLOT_ID --expires-in-days 30 --allow-origin https://bi.example.com
mapi plots embeds PLOT_ID
mapi plots revoke PLOT_ID EMBED_ID                 # asks first

render draws the plot in the sandbox (vl-convert) and downloads the file. embed shows the link one time. The link contains its token (Sharing plots).

Guided mode and business priors v0.4

mapi guided templates [geo_mmm]
mapi guided detect geo_mmm --csv media.csv          # or --dataset dataset://growth/media@v2
mapi guided draft geo_mmm --name eu-geo-mmm --dataset eu_media --csv media.csv --detect \
    -a spend=tv_spend,search_spend -a holdout_periods=8 --yaml-out spec.yaml
mapi priors preview --beliefs beliefs.yaml --spec SPEC_ID --revision 2 -o priors.json

detect and draft --csv profile the file locally. They find the types, counts, ranges and some sample values of each column. They send only those summaries, as the web app does.

  • --detect fills each answer that detection is sure of.
  • -a QUESTION=VALUE answers one question, or overrides the answer. Use a,b,c for lists and JSON for numbers.

A draft stores nothing. Examine spec.yaml. Then upload the data with the mapping (--mapping-out) and fit. priors preview changes ROI ranges or contribution-share ranges into model priors in a sandboxed run. It shows what the model then believes, for each channel (Guided modelling).

v0.12 and v0.13 add the templates mediated_mmm, multi_kpi_mmm, campaign_impact and geo_lift (see Measure the effect of a change). Upgrade the CLI to v0.13 or later. A v0.12 CLI cannot read the list of templates from a v0.13 server: mapi guided templates fails.

Sign-up and your workspace v0.4

mapi --url https://api.mapi.example signup --email ana@acme.example --name "Ana Analyst" --accept-terms
mapi --url https://api.mapi.example verify-email mev_…   # the token from the e-mail link
mapi tenant status                                       # verification, any scheduled deletion
mapi tenant export -o acme-export.zip                    # GDPR export, waited for and downloaded
mapi tenant delete                                       # type the tenant id to confirm
mapi tenant delete --cancel                              # within the grace period

signup and verify-email send no credentials. tenant delete tells what the platform purges and what it keeps. Then it asks you to type the tenant id. Scripts give --confirm TENANT. The tenant is --tenant. If you do not give it, the tenant is the tenant of the selected project. If there is no selected project, it is your only tenant (Deleting and exporting data).

Workflows, governance and explorers v0.5

Help me choose and About cards, in the terminal:

mapi choose                                   # questions, one at a time; Enter skips one
mapi choose -a goal=forecast -a data_shape=one_series -a drivers=yes -a experience=beginner
mapi about ts.sarimax                         # one item's About card
mapi about ts.sarimax --expert                # ... with the maths and references

Workflows: start from the gallery and check the file on the server. Then save it, run it and watch each node live. If the run fails, --watch exits with status 1. Thus it works in CI:

mapi workflows templates                      # the gallery, in plain words
mapi workflows init ts_readiness_arimax --name weekly-readiness   # writes weekly-readiness.yaml
mapi workflows validate weekly-readiness.yaml # checked by the server: pointers to every problem
mapi workflows apply weekly-readiness.yaml -m "first version"     # create, or save a revision
mapi workflows run weekly-readiness \
  --input data=dataset://growth/weekly_sales@latest \
  --param 'drivers=["price"]' --param window=104 \
  --watch                                     # live node status; exit 1 if the run fails

validate and apply show the sequence in which the nodes will run. This output is from a local stack, with the document of the cookbook:

weekly-readiness.yaml: valid (23 nodes)
order: adf → kpss → pp → za → seas → folds → holdout → wanders → seasonal → naive → order → sdiff → backtest → fit → diff → compare → dm → acf → select → governance → report → trusted → pack

The cookbook shows the live board of --watch and the summary at its end. Then examine a run, one node at a time:

RUN=$(mapi --json workflows runs weekly-readiness -n 1 | jq -r '.[0].run_id')
mapi workflows status "$RUN"                  # nodes, what each found, decisions, outputs
mapi workflows node "$RUN" order              # one node: parameters, result, outputs
mapi workflows output "$RUN" diff data --out diff.zarr.zip   # a derived dataset, verified
 node        order
 uses        search.arma_order@1.0.0
 status      succeeded
 attempt     1
 child runs  run_01m53hawz719exdgb6kcw5ysp3
 params      {"d": 1, "exog": ["price"], "max_p": 3, "max_q": 3, "trend": "c", "criterion": "aic"}
 result      {"best_p": 1, "best_q": 1, "best_order": [1, 1, 1], "criterion": "aic",
              "best_score": 430.4823790539326, "n_failed": 0, "suggested_p": 0, "suggested_q": 0}
 outputs     table: table

This is the output of mapi workflows node "$RUN" order. Here, we folded the JSON onto fewer lines. The differencing decision gave d = 1 to the search, and the run gave the price driver. The search selected ARIMA(1, 1, 1) by AIC.

mapi workflows schedules add weekly-readiness "0 6 * * 1" --tz Europe/Berlin \
  --input data=dataset://growth/weekly_sales@latest
mapi workflows triggers add weekly-readiness dataset://growth/weekly_sales --debounce 600
mapi workflows schedules ls weekly-readiness

-i name=ref binds a workflow input. When the run starts, the platform pins @latest to a version. -p name=value sets a parameter. If the value parses as JSON, the CLI sends it as JSON (-p 'horizons=[1,4,13]'). The workflow cookbook has complete documents that you can start from.

Since v0.9, mapi workflows templates also lists the recipes for Bayesian and science models. Start each one with mapi workflows init <id>. The cookbook shows the commands for each one:

Template Cookbook section
sci_spatial_ladder Disease map: which spatial model?
sci_network_ladder Network ties: which model?
sci_phylo_signal Trait regression across species
sci_calibrate_then_design Calibrate, then design
bayes_workflow_audit Bayesian workflow audit
sci_image_model_check Image model check

From v0.13, the template geo_lift_mmm measures a geo-lift test and gives the result to mmm.bayesian as a lift test. See Measure the effect of a change.

To get the data of a science case study, use python -m mapi_plugin_science.export <case>. Then upload the file with mapi datasets upload.

Governance: the check catalog, policies, validations and approval by a second person. mapi validate model waits for the checks. It shows each result in plain words with the suggested action. mapi approve needs a signed-in reviewer who did not submit the model:

mapi governance checks                        # what a policy can require
mapi governance checks --about null.permutation_target --expert
mapi governance policies ls                   # in force here: tenant-wide first
mapi governance policies create forecast-policy.yaml
mapi validate model model://growth/wf-weekly-readiness-fit@v1   # runs the checks, waits
mapi validations ls --queue                   # reviewers: what waits for a decision
mapi approve "$VALIDATION" --comment "Null tests convincing; beats naive by 25%"
mapi validations pack "$VALIDATION" --out evidence.html          # for the auditor

Explorers and budget plans:

mapi explore dataset dataset://growth/weekly_sales@latest \
  -s overview -s stationarity -s ccf -s stl --kpi sales --out views/
mapi explore dataset weekly_sales --show stationarity            # one view as JSON
mapi explore model model://growth/wf-weekly-readiness-fit@v1 -s params -s residuals
mapi explore compare model://growth/arimax@v3 model://growth/ets@v1 --horizon 4
mapi optimize model://growth/geo-mmm@v7 --change-pct 0 \
  --channel 'tv={"min_multiplier": 0.5, "max_multiplier": 1.5}' \
  --objective p10 --frontier 8

These are the forms of a model reference: model://project/name@vN, name@vN, name with -v N, or a model id. Dataset references have the same forms with dataset://. All commands accept --json.

Use --json in scripts

--json is a global option. It shows one JSON document on stdout instead of tables. runs watch --json and workflows watch --json show one JSON event on each line. Errors become {"error": {...}} with exit status 1. Usage errors exit with status 2.

RUN=$(mapi --json runs ls --kind score --limit 1 | jq -r '.[0].id')
mapi --json runs get "$RUN" | jq '{state, attempt, kind}'
mapi runs logs "$RUN" | tail -n 5

The CLI removes ANSI and control sequences from text that came from the server, a plugin or a run log. Thus a hostile log line cannot write over your terminal.

mapi serve

mapi serve runs mapi-server dev. It does the database migrations. Then it runs the API and the orchestrator in one process. It uses an embedded PostgreSQL, unless DATABASE_URL is set. It uses an in-process GCS shim, unless a shim already listens at MAPI_GCS_ENDPOINT. This is the smallest way to get an API for the CLI or the client. If you also want the web app, use make dev (Local deployment).