Integrations v0.4¶
A fitted model is useful only when other applications can use it. For example, a planning
tool scores budgets and a spreadsheet gets a forecast. A dashboard shows a chart, and a
pipeline gets a message when a fit is complete. This page tells about the integration concepts
of v0.4 and the contracts in mapi_spec that they use. The
HTTP API reference gives the endpoints.
Endpoints and webhooks shows how to use them with
curl.
Endpoints and aliases¶
An endpoint is a stable alias for one model version: endpoint://growth/geo-mmm-prod.
Integrations call the alias. Thus you can promote a better model, or roll back a bad model,
and the integrations do not change. Each promotion and rollback is a numbered revision in the
history of the endpoint. The revision records who did it and why.
PUT /v1/endpoints/geo-mmm-prod
X-Mapi-Project: acme/growth
{"model": "model://growth/eu-geo-mmm@v3", "expected_revision": 3}
The promotion gate¶
The sanity scorecard and the EvaluationPolicy of the endpoint
control promotion (mapi_spec.scorecard.promotion_gate):
| The version | Gate | Result |
|---|---|---|
| The scorecard passes | allowed |
The platform promotes the version |
The scorecard warns, or there is no scorecard. Or an evaluate_then_refit fit has a scorecard with no holdout check (no data.split, or no evaluate.metrics: the platform did not evaluate it). Or the checks of the template of a guided version did not all run (NOT_EVALUATED). |
needs_override |
409 OVERRIDE_REQUIRED, unless a signed-in project admin sends override: true with a reason. The audit log records the override (endpoint.promote_override). |
The scorecard fails, or the fit stopped before it was complete (partial) |
blocked |
409 PROMOTION_BLOCKED. No override is possible. |
The fit mode was train_only |
blocked |
Endpoints need evaluate_then_refit (Q13), because the context and terminal state must come from all the data. A policy with fit_mode: train_only accepts exploration fits. |
| The version needs input that the callers do not send | blocked (policy schema_change: block), needs_override (warn) or ignored (allow) |
A dim that is removed or changed, a stricter dim policy, a new necessary input, or an input with changed dims or type (schema_breaks) |
An override is a human decision. Only project admins have the manage permission on
endpoints, and no token scope gives it, not even admin. A token with
endpoints:promote (for CI) can promote versions that pass and can roll back. It can never
override.
Scoring an endpoint¶
POST /v1/endpoints/{alias}/score takes an EndpointScoreCall. This is a score request
without model. data and scenario can be dataset:// references. They can also be small
inline XrJSON datasets (the xr.Dataset.to_dict() shape, a maximum of 5 MB). Thus
spreadsheets and scripts do not need an upload. The control plane does a check of only the
envelope of inline data (names, nesting, sizes). The sandbox decodes the values.
Send Prefer: wait=60 to get the result in the response if the run is complete in time
(200). If the run is not complete in time, the response is 202 with the run to follow.
GET …/schema tells what to send (EndpointSchema). It gives the input schema that the
platform froze at promotion, the score modes, the dims and an example payload.
POST …/validate does a check of a call and does not run it. A forecast call can also contain
the actuals that arrived after the fit and the dates to forecast. Refer to
Forecasts with actuals and the actuals_example of the schema.
A score:<alias> token can read the endpoint, its schema and its snippets, and can score it.
It can do nothing else.
Snippets¶
GET /v1/endpoints/{alias}/snippets returns code that is ready to run. The code scores the
endpoint with its example payload. These languages are available:
- Python (
mapi-client) - TypeScript (
fetch) - curl
- R (
httr2) - Google Sheets (Apps Script)
- a notebook cell (
Client().endpoint(alias).score(...), which returns anxarray.Dataset).
The generators are pure functions (mapi_spec.snippets.render_snippets). Thus the server, the
CLI, the web app and these docs make the same code. The Integrations hub shows and copies the
snippets of the server with no changes. Snippets never contain a secret. They read the token
from MAPI_TOKEN (a script property in Apps Script). The generators write each value with the
literal syntax of the language. Tests parse the generated code to prove that a dangerous
payload cannot go out of its string.
curl -sS -X POST 'https://api.mapi.example/v1/endpoints/geo-mmm-prod/score' \
-H "Authorization: Bearer $MAPI_TOKEN" \
-H 'X-Mapi-Project: acme/growth' \
-H 'Prefer: wait=60' \
--data '{"mode": "forecast", "horizon": {"dim": "time", "periods": 8},
"future_covariates": "carry_last"}'
Webhooks¶
A webhook calls your URL when an event occurs in a project:
| Event | When |
|---|---|
run.succeeded, run.failed |
A run is complete |
model.version.ready |
A fit made a model version (with the result of its scorecard) |
endpoint.promoted |
An alias points to a new version (promotion or rollback) |
score.completed |
A score run is complete |
tenant.deletion.completed |
The platform purged the tenant (this is the last event that the tenant sends) |
Each delivery is a JSON envelope {id, type, created, tenant, project, api_version, data}.
Envelopes contain ids and states. They never contain data values, signed URLs or secrets. Use
the API to get the results.
Verifying a delivery¶
The platform signs each delivery with the secret of the webhook. The platform shows the secret one time, when you make the webhook or rotate its secret:
v1 is the hex HMAC-SHA256 of "<t>.<raw body>". To verify a delivery, do these steps:
- Calculate the HMAC again over the raw body.
- Compare the two values in constant time.
- Refuse timestamps that are more than 300 seconds from your clock. This limits replays.
During a rotation, the header contains one v1 for each active secret.
from mapi_spec.webhooks import verify_signature
if not verify_signature(raw_body, request.headers["Mapi-Signature"], secret):
return 401
The platform tries failed deliveries again after 1 min, 5 min, 30 min, 2 h, 6 h, 12 h and
24 h. If a receiver answers 410 Gone, the platform disables it immediately. The platform
disables a webhook after 20 failed events in sequence. The delivery log shows each attempt
(status, response code, duration). You can replay each delivery.
SSRF protection¶
A webhook URL is a request that the platform makes for a tenant. Thus the server delivers only
under a strict policy (mapi_spec.webhooks.SSRF_POLICY):
- The URL must use
httpsonly, on port 443 or 8443. It must have no credentials or fragment. It must have a host name. An IP literal,localhostor an internal suffix (.internal,.local, ...) is not permitted. - The server resolves the host itself. It refuses the URL if one address is private,
loopback, link-local, cloud metadata (
169.254.169.254), CGNAT, unique-local or multicast. It also refuses addresses that are not globally routable for other reasons. This includes IPv4 addresses in IPv6 addresses. - The server then connects to the address that it checked. It does no second lookup, thus DNS rebinding cannot change the address. The server follows no redirects. It stops after 5 seconds and reads a maximum of 256 KiB of the answer.
If a URL does not obey the policy, the server refuses it with 422 WEBHOOK_URL_BLOCKED. A URL
can start to resolve to a blocked address later. Then its deliveries fail as blocked. The
audit log records this as a security signal (webhook.url_blocked).
Plots: rendering, sharing and embeds¶
- Saved plots keep a plot of a model version, or a plot that you wrote, under a name.
- Static rendering (
POST /v1/plots:render) makes PNG, SVG or PDF files in the sandbox for reports. It uses vl-convert for Vega-Lite, with no network access. Plotly needs kaleido and a Chrome in the runner image. Until an operator turns on Plotly, the platform refuses it withMODE_NOT_SUPPORTED. - Embed links are read-only links with a token and an expiry date, for BI tools, Slack and
Notion. The platform shows the token one time. You can revoke links.
allowed_originslimits the sites that can show them in a frame. You can embed only platform recipes. These are plots that the recipes of the platform made from a model version. From v0.13, thecausal.*recipes are also platform recipes. You cannot embed plots that a plugin or a user wrote. A sandboxed iframe can still navigate itself. Thus shared views run only code that the platform wrote. Requests for other plots get422 EMBED_NOT_ALLOWED. Links that expired or that you revoked answer410 LINK_EXPIRED. The link opens the embed page of the plot sandbox. This page can get only the public embed view of the API. Refer to Sharing plots.
The trust lifecycle¶
Public sign-up starts with cut line A. Public tenants run only declarative specs and built-in plugins. The data is not trusted, but the code is trusted.
- Sign-up needs an e-mail address, a name and accepted terms. The answer is the same for new and known addresses. Thus nobody can use it to find accounts. A policy hook that you can replace refuses disposable e-mail domains.
- E-mail verification uses a link that you can use one time (valid for 24 hours). It makes
a tenant on the
publictier with adefaultproject. The new user is the admin of the project. - Identity verification is necessary before any uploaded code. Tenants on the
verifiedtier (allow-listed, M4) can upload code after verification. Public tenants can upload code only after cut line B (planned). Until then,VerificationStatus.uploaded_code_allowedis false for all public tenants. - Deleting a tenant starts a grace period of 7 days. During this period, the tenant is suspended (read-only) and you can cancel the deletion. Then the platform purges each store: database rows, objects, queued work, logs and webhooks. It shows the progress for each store. The platform keeps the audit log (pseudonymised) and the cost ledger (amounts only, for tax law) for their legal retention period.
- A GDPR export puts all the data that the tenant stored into one archive. It gives a download link that expires after a short time. A suspended tenant can also ask for it.