mnrvAR API quickstart
Create, publish and measure WebAR experiences with an API key. This page is written for developers and for AI agents. The machine readable reference is the OpenAPI document at https://mnrvar.com/api/v1/openapi.json. A short index for agents is at https://mnrvar.com/llms.txt.
Get an API key
- Sign in at https://mnrvar.com.
- Open Settings, then API keys.
- Name the key, choose its scopes and select Create key.
- Copy the key now. It is shown once. Keys look like
mnrv_live_followed by a secret.
API keys need a paid plan, Creator or higher. A key acts as its owner and sees only that account's experiences. If the plan lapses, the key answers 402 plan_required until the account is upgraded again.
Send the key on every request:
export MNRV_API="https://mnrvar.com"
export MNRV_API_KEY="mnrv_live_REPLACE_WITH_YOUR_KEY"
curl -s "$MNRV_API/api/v1/templates" -H "Authorization: Bearer $MNRV_API_KEY"Scopes
Each key carries one or more scopes. A request needs the scope of its operation, shown as x-required-scope in the OpenAPI document. A key without it gets 403 scope_missing.
| Scope | Allows |
|---|---|
experiences | Templates, creating, reading, editing, duplicating and deleting experiences, slots, objects, validation, folders, geo fence links |
publish | Publishing and unpublishing |
assets | Uploading files, importing files by URL, scoring target images, previewing and deleting draft assets |
analytics | Scan, conversion, link, survey and geo analytics, and CSV exports |
leads | Reading and exporting leads |
Keys cannot reach billing, team, admin, client portal, transfer or key management routes. Those answer 403 endpoint_forbidden.
A complete session: video on a poster
This session builds a video_on_surface experience: a visitor scans a poster and a video plays on top of it. The template needs no visual editor. Use a key with the scopes experiences, publish, assets and analytics.
1. List templates
curl -s "$MNRV_API/api/v1/templates" -H "Authorization: Bearer $MNRV_API_KEY"The response is a list. The entry for video_on_surface, trimmed:
{
"template_type": "video_on_surface",
"description": "Plays an uploaded video on top of a scanned image, with optional transparency or a shape mask.",
"slots": [
{ "slot_id": "target_image", "accepts": "image", "required": true, "accepted_extensions": [".gif", ".jpeg", ".jpg", ".png", ".webp"], "max_bytes": 5242880 },
{ "slot_id": "video", "accepts": "video", "required": true, "accepted_extensions": [".mov", ".mp4", ".webm"], "max_bytes": 104857600 },
{ "slot_id": "video_mask", "accepts": "image", "required": false, "accepted_extensions": [".gif", ".jpeg", ".jpg", ".png", ".webp"], "max_bytes": 5242880 }
],
"plan_required": null,
"addon_required": null,
"editor_required": false
}Only templates the account may create are listed. editor_required: false means the experience can be finished through the API alone.
2. Create the experience
curl -s -X POST "$MNRV_API/api/v1/experiences/" \
-H "Authorization: Bearer $MNRV_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: spring-poster-create-1" \
-d '{"name": "Spring poster", "template_type": "video_on_surface"}'Response, trimmed:
{
"id": "3f2b8c1e-0d4a-4c8e-9a57-6d1f2b7c9e10",
"name": "Spring poster",
"template_type": "video_on_surface",
"status": "draft",
"hosted_url": null,
"scan_count": 0
}Keep the id:
export EXP_ID="3f2b8c1e-0d4a-4c8e-9a57-6d1f2b7c9e10"3. Upload the poster
curl -s -X POST "$MNRV_API/api/v1/assets/upload?experience_id=$EXP_ID" \
-H "Authorization: Bearer $MNRV_API_KEY" \
-F "[email protected]"Response:
{
"asset_id": "a1b2c3d4e5f6",
"filename": "poster.jpg",
"cdn_path": "drafts/3f2b8c1e-0d4a-4c8e-9a57-6d1f2b7c9e10/a1b2c3d4e5f6_poster.jpg",
"size_bytes": 412345,
"content_type": "image/jpeg"
}export POSTER_PATH="drafts/3f2b8c1e-0d4a-4c8e-9a57-6d1f2b7c9e10/a1b2c3d4e5f6_poster.jpg"To let the server download a public file instead of uploading it, use import by URL, see below.
4. Score the target image
A poster that is blurry, flat or repetitive tracks badly. Score it before you build on it.
curl -s -X POST "$MNRV_API/api/v1/assets/target-quality?cdn_path=$POSTER_PATH" \
-H "Authorization: Bearer $MNRV_API_KEY"Response, trimmed:
{
"score": 78,
"grade": "good",
"issues": [
{ "code": "large_blank_areas", "severity": "low" }
]
}grade is poor, fair, good or excellent. On poor, ask for a different image. issues says what to improve.
5. Fill the target image slot
curl -s -X POST "$MNRV_API/api/v1/experiences/$EXP_ID/slots/target_image" \
-H "Authorization: Bearer $MNRV_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"cdn_path\": \"$POSTER_PATH\"}"Response:
{
"slot_id": "target_image",
"component_type": "image",
"asset_path": "drafts/3f2b8c1e-0d4a-4c8e-9a57-6d1f2b7c9e10/a1b2c3d4e5f6_poster.jpg",
"filename": "poster.jpg",
"created_object_id": null
}6. Import the video by URL and fill the video slot
Import downloads a public https file, stores it as a draft asset and can fill a slot in the same call. Always send video_aspect_ratio, the video width divided by its height. The server cannot read video dimensions, and without it the video plane defaults to 16:9.
The server fits the video plane to the target image, so no manual resizing is needed. fit chooses how, and defaults to contain:
contain: the whole video is visible, centred, and never larger than the target.cover: the video covers the whole target and overflows on one axis.stretch: the plane matches the target exactly and the video is distorted.
Setting or replacing target_image later re-fits the plane, so the order of the two calls does not matter.
curl -s -X POST "$MNRV_API/api/v1/assets/import-url" \
-H "Authorization: Bearer $MNRV_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: spring-poster-video-1" \
-d "{\"experience_id\": \"$EXP_ID\", \"url\": \"https://example.com/promo.mp4\", \"slot_id\": \"video\", \"video_aspect_ratio\": 1.7778, \"fit\": \"contain\"}"Response, trimmed:
{
"filename": "promo.mp4",
"cdn_path": "drafts/3f2b8c1e-0d4a-4c8e-9a57-6d1f2b7c9e10/f6e5d4c3b2a1_promo.mp4",
"content_type": "video/mp4",
"slot": {
"slot_id": "video",
"component_type": "video",
"created_object_id": "7c0a5e52-9d3b-4f1a-8b66-2a4d9e1f3c58"
}
}created_object_id is the video plane the server created. Filling the video slot of a video_on_surface experience creates it for you. If you uploaded the video yourself, fill the slot with POST /api/v1/experiences/{experience_id}/slots/{slot_id} and send video_aspect_ratio and optionally fit in the body. The response then carries video_plane_transform, the size the plane was given in units of the target image height.
7. Validate
curl -s -X POST "$MNRV_API/api/v1/experiences/$EXP_ID/validate" \
-H "Authorization: Bearer $MNRV_API_KEY"Validation is a dry run of publish. It reports every blocking problem at once and writes nothing.
{ "ok": true, "problems": [] }With a slot still empty it returns, for example:
{
"ok": false,
"problems": [
{ "code": "slot_missing", "message": "Required slot not filled: video", "field": "video" }
]
}Fix every problem, then validate again. A problem with an upgrade_url needs the account owner to change plan.
8. Publish
curl -s -X POST "$MNRV_API/api/v1/experiences/$EXP_ID/publish" \
-H "Authorization: Bearer $MNRV_API_KEY" \
-H "Idempotency-Key: spring-poster-publish-1"Response, trimmed:
{
"id": "3f2b8c1e-0d4a-4c8e-9a57-6d1f2b7c9e10",
"status": "live",
"hosted_url": "https://cdn.mnrvar.com/experiences/3f2b8c1e-0d4a-4c8e-9a57-6d1f2b7c9e10/index.html"
}Give hosted_url to the person who will scan. Publishing can answer 402 with a publish_limit or addon_required code and an upgrade_url when the plan has no room. Tell the account owner to open that URL. To take an experience offline, call POST /api/v1/experiences/{experience_id}/unpublish. That removes the published files and returns the experience to draft, and publishing again makes it live.
9. Read results later
curl -s "$MNRV_API/api/v1/analytics/summary?experience_id=$EXP_ID&period=7d" \
-H "Authorization: Bearer $MNRV_API_KEY"
curl -s "$MNRV_API/api/v1/leads?experience_id=$EXP_ID&period=30d&limit=50" \
-H "Authorization: Bearer $MNRV_API_KEY"The summary returns total_scans, unique_sessions, a daily timeline of {date, count} and a by_experience breakdown. Leads return {leads, total}. Page with limit and offset. GET /api/v1/analytics/export and GET /api/v1/leads/export return CSV files.
Other templates without the editor
| Template | Needs |
|---|---|
video_on_surface | A target image and a video. This session. |
product_float | A target image and one 3D model, added with POST /api/v1/experiences/{experience_id}/objects. The model gets the default transform. |
vr_360_video | One 360 video in the video slot. Needs the Studio plan and the 360 video add-on. |
GET /api/v1/templates is the source of truth. Check editor_required on each template.
Errors
Every 4xx response is JSON. Branch on code, show detail to people.
{
"detail": "You have used all of your published experience slots. Upgrade to Creator, Studio or Agency, or add a slot from Billing.",
"code": "publish_limit",
"upgrade_url": "https://mnrvar.com/billing"
}The wording of detail varies and the example above is illustrative. field names the offending input when there is one. upgrade_url appears on 402. Request validation failures keep a list in detail and add code invalid_request, a message string and field.
| Code | Status | Meaning | What to do |
|---|---|---|---|
invalid_api_key | 401 | The key is missing, malformed, revoked or unknown. | Check the Authorization: Bearer header. Create a new key if it was revoked. |
scope_missing | 403 | The key lacks the scope this operation needs. field names it. | Create a key with that scope. |
endpoint_forbidden | 403 | API keys cannot call this route. | Use only operations from the OpenAPI document. |
team_member_key | 403 | Team members cannot use API keys. | Use a key owned by the account owner. |
rate_limited | 429 | Too many requests. | Wait the seconds in the Retry-After header, then retry. |
plan_required | 402 | The plan does not include this feature, including API access itself. | Give the user the upgrade_url. |
addon_required | 402 | The template needs an add-on slot. | Give the user the upgrade_url. |
publish_limit | 402 | The plan has no room for another published experience. | Unpublish one, or give the user the upgrade_url. |
create_limit | 402 | The plan has no room for another experience. | Delete one, or give the user the upgrade_url. |
template_unavailable | 403 | The template is not available to this account. | Choose another template from GET /api/v1/templates. |
invalid_request | 422 | A field is missing, malformed or out of range. | Fix the input named in field. |
not_found | 404 | The experience, folder or other resource does not exist for this account. | Check the id. |
slot_unknown | 422 | The template has no slot with that id. | Use a slot_id from GET /api/v1/templates. |
slot_missing | 422 | A required slot is empty, so the experience cannot publish. | Fill the slot named in field. |
no_objects | 422 | The template needs at least one 3D model. | Add one with POST /api/v1/experiences/{experience_id}/objects. |
asset_invalid | 400, 413 or 422 | The file or path is not acceptable: type, size or location. | Check extension and size limits from the template. Use a cdn_path from your own upload. |
asset_missing | 404 or 422 | A referenced file is not in storage. | Upload or import the file again. |
capability_violation | 422 | The config uses a feature the template does not support. | Remove that setting. |
schedule_ended | 422 | The experience's ends_at is in the past. | Change or clear ends_at, then publish. |
url_not_allowed | 422 | The import URL is not a public https URL. | See import by URL below. |
download_failed | 422 | The server could not download the file. | Check the URL is public and serves the file. |
idempotency_key_reused | 422 | The Idempotency-Key was used with a different request. | Use a new key for a new request. |
idempotency_in_progress | 409 | A request with this key is still running. | Retry after Retry-After seconds. |
key_limit | 422 | The account already has 10 active keys. | Revoke one. |
Limits
- Rate limit: 120 requests per minute per key. Past it the API answers 429
rate_limitedwithRetry-After. - Import by URL: 30 imports per 10 minutes per account, also answered with 429
rate_limited. - File size and type limits are per slot in
GET /api/v1/templates. - Plan quotas for experiences, published experiences and add-ons answer 402 with an
upgrade_url. - Scopes
experiences,publish,assets,analyticsandleadsare the only ones.
Idempotency
Create, publish, add object and import by URL accept an Idempotency-Key header, 1 to 255 visible ASCII characters. Repeat the same request with the same key within 24 hours and you get the first response back, with Idempotent-Replayed: true. No second experience is created and nothing is published twice. Use one key per intended action. Send a different request with a used key and you get 422 idempotency_key_reused. If the first request is still running you get 409 idempotency_in_progress.
Import by URL
POST /api/v1/assets/import-url makes the server download a file, so it is strict:
- https only, default port, no credentials in the URL.
- The host must resolve to a public address. Private, loopback and link local addresses are refused with 422
url_not_allowed. - At most 3 redirects, each checked again.
- The file type comes from the filename in
Content-Disposition, else the last segment of the URL path. It must be an extension the template accepts, within the size limit. - 30 imports per 10 minutes per account.
What still needs the visual editor
The API fills template slots and adds 3D models with a default transform. It does not place or arrange a scene. Open the editor at https://mnrvar.com for:
- Templates marked
editor_required: trueinGET /api/v1/templates. - Positioning, rotating and scaling models precisely.
- Call to action buttons, lead forms, hotspots, timelines and animations.
- Catalog items and
magic_cubefaces. - Creating geo fences, which the API can only link to an experience.
You can still create and publish those experiences through the API, but a person should check the result in the editor first.
Reference
- OpenAPI document: https://mnrvar.com/api/v1/openapi.json
- Agent index: https://mnrvar.com/llms.txt
- This page as markdown: https://mnrvar.com/docs/api.md