mnrvAR

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

  1. Sign in at https://mnrvar.com.
  2. Open Settings, then API keys.
  3. Name the key, choose its scopes and select Create key.
  4. 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.

ScopeAllows
experiencesTemplates, creating, reading, editing, duplicating and deleting experiences, slots, objects, validation, folders, geo fence links
publishPublishing and unpublishing
assetsUploading files, importing files by URL, scoring target images, previewing and deleting draft assets
analyticsScan, conversion, link, survey and geo analytics, and CSV exports
leadsReading 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

TemplateNeeds
video_on_surfaceA target image and a video. This session.
product_floatA target image and one 3D model, added with POST /api/v1/experiences/{experience_id}/objects. The model gets the default transform.
vr_360_videoOne 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.

CodeStatusMeaningWhat to do
invalid_api_key401The key is missing, malformed, revoked or unknown.Check the Authorization: Bearer header. Create a new key if it was revoked.
scope_missing403The key lacks the scope this operation needs. field names it.Create a key with that scope.
endpoint_forbidden403API keys cannot call this route.Use only operations from the OpenAPI document.
team_member_key403Team members cannot use API keys.Use a key owned by the account owner.
rate_limited429Too many requests.Wait the seconds in the Retry-After header, then retry.
plan_required402The plan does not include this feature, including API access itself.Give the user the upgrade_url.
addon_required402The template needs an add-on slot.Give the user the upgrade_url.
publish_limit402The plan has no room for another published experience.Unpublish one, or give the user the upgrade_url.
create_limit402The plan has no room for another experience.Delete one, or give the user the upgrade_url.
template_unavailable403The template is not available to this account.Choose another template from GET /api/v1/templates.
invalid_request422A field is missing, malformed or out of range.Fix the input named in field.
not_found404The experience, folder or other resource does not exist for this account.Check the id.
slot_unknown422The template has no slot with that id.Use a slot_id from GET /api/v1/templates.
slot_missing422A required slot is empty, so the experience cannot publish.Fill the slot named in field.
no_objects422The template needs at least one 3D model.Add one with POST /api/v1/experiences/{experience_id}/objects.
asset_invalid400, 413 or 422The 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_missing404 or 422A referenced file is not in storage.Upload or import the file again.
capability_violation422The config uses a feature the template does not support.Remove that setting.
schedule_ended422The experience's ends_at is in the past.Change or clear ends_at, then publish.
url_not_allowed422The import URL is not a public https URL.See import by URL below.
download_failed422The server could not download the file.Check the URL is public and serves the file.
idempotency_key_reused422The Idempotency-Key was used with a different request.Use a new key for a new request.
idempotency_in_progress409A request with this key is still running.Retry after Retry-After seconds.
key_limit422The account already has 10 active keys.Revoke one.

Limits

  • Rate limit: 120 requests per minute per key. Past it the API answers 429 rate_limited with Retry-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, analytics and leads are 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: true in GET /api/v1/templates.
  • Positioning, rotating and scaling models precisely.
  • Call to action buttons, lead forms, hotspots, timelines and animations.
  • Catalog items and magic_cube faces.
  • 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