Recipe-driven · 8 named recipes · AR walkthrough

Vastu API Integration Guide

Connect room layouts and compass headings to Vastu analysis. Start with the endpoint guide, then use the curl, JavaScript and Python recipes. REST response text is English; the language examples use the separate AI Query path.

Two Integration Paths

Both are LIVE. You can mix freely — the most powerful integrations use both.

A. AI Query (natural language)

Send a free-text Vastu question. Receive a written answer in 30 languages. Preserve its source labels: verified verse citations apply only to supported rules; other advice is convention.

Endpoint: POST /api/v1/query

Best for: chatbots, voice assistants, content surfaces, free-form input.

B. REST Endpoints (structured data)

Send structured input (rooms, polygons, bearings). Receive deterministic JSON with score, defects, mandala coords.

Namespace: /v2/astrology/vastu/* (129 routes; five saved-scan routes are disabled by default — this guide covers a curated set of named recipes below; see the full catalog for every operation)

For traced courtyards and disconnected wings, see region geometry and unit conversion.

Best for: AR scanners, proptech audits, design tools, dashboards.

CAD and BIM plans

Import existing drawing geometry, assess the returned plan, and export editable DXF overlays. Successful CAD operations charge attributable compute and response delivery multiplied by four, with the existing $0.01 settlement minimum. Set maxChargeUsd to cap the settled debit. Invalid files, unsupported DWG and files without usable room geometry are unbilled.

DXF intake

POST /v2/vastu/plan/import-dxf accepts a JSON body with dxf containing the file text. Closed LWPOLYLINE, POLYLINE and supported HATCH boundaries become rooms; TEXT, MTEXT and room block attributes supply labels. Door blocks use block, attribute and layer heuristics. Override layers with layerRoles values room, plot, door, window, label, hole or ignore.

Drawing units come from $INSUNITS. Unitless drawings require an explicit unitsOverride code, such as 4 for millimetres, 6 for metres or 2 for feet. Provide trueNorthDeg as clockwise degrees from drawing +Y. Geometry is returned in metres with source units, origin and entity ids in cadMetadata and source.

Editable DXF export

POST /v2/vastu/plan/export-dxf accepts plan, optional analysis, and zones of 8, 16 or 32. It returns R2013 DXF text with VASTU-ROOMS, VASTU-ZONES, VASTU-DOORS, VASTU-FINDINGS and VASTU-DIMS layers. Findings need text and a point, roomId or unambiguous room name; each has a text annotation and leader. Source units, ids and room geometry survive re-import. Retain cadMetadata when editing the returned plan.

IFC storeys and spaces

POST /v2/vastu/plan/import-ifc accepts ifc containing IFC2X3 or IFC4 STEP text. Building, storey, space and door/window ids retain their GlobalIds. Each mapped storey has its own plan; names include space LongName. Supported space geometry includes polygon footprints, vertical swept solids and supported space-boundary surfaces. Project TrueNorth is retained; trueNorthDeg can override it.

Imports return a per-entity mappingReport, needsReview and reviewReasons. Missing north, inferred plot hulls, opening heuristics and unsupported geometry require review. Curves are tessellated to a maximum 1 mm chord deviation; unsupported splines and tilted geometry are listed rather than guessed. Files are limited to 1.5 MB, 20,000 source entities, 500 rooms and 1,024 vertices per boundary. IFC import supports up to 50 storeys.

DWG returns HTTP 415: convert DWG to DXF before submission. No server DWG conversion is offered. The complete request and response schemas are in OpenAPI; typed methods are available in the JavaScript, Python, Swift and Android SDKs.

Decision Tree — When to Use What

I want to...Use this
Answer a Vastu question someone typedPOST /api/v1/query
Show a 9-cell or 81-cell grid in AR scene/vastu/mandala/project/9-zone or /81-pada
Score an entire floor plan/vastu/audit/floor-plan
Score AND get heatmap AND remediation order/vastu/audit/floor-plan-detailed
Check one (room, zone) pair only/vastu/audit/single-room
Convert phone-compass bearing to true-north/vastu/direction/declination
Convert "north-east" / "ishanya" to a zone/vastu/direction/correct
Map bearing-degree to zone label/vastu/direction/zone-from-bearing
Classify the door's pada (auspicious/avoid)/vastu/entrance/pada
Check reported AR scan quality before audit/vastu/ar/scan-quality
Highlight Brahmasthan no-go zone/vastu/mandala/project/brahmasthan
Audit plot length:width ratio/vastu/plot/ratio
Classify plot polygon shape/vastu/plot/shape
Get the 9-zone reference table (cache once)/vastu/reference/mandala/9-zone

Recipes — Per Endpoint

Set VEDIKA_KEY env var to your vk_live_* token. Run keyed examples on your server. Use the keyless https://api.vedika.io/sandbox/v2/vastu/* routes for browser demos on localhost. From your own domain, call them through a browser proxy.

Recipe 1 — Floor plan audit (curl)

When: User finished tagging rooms; you want score + defect list.

curl -X POST https://api.vedika.io/v2/astrology/vastu/audit/floor-plan \
  -H "Authorization: Bearer $VEDIKA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "rooms": [
      { "roomType": "kitchen",        "zone": "SE" },
      { "roomType": "master_bedroom", "zone": "SW" },
      { "roomType": "pooja",          "zone": "NE" },
      { "roomType": "toilet",         "zone": "NE" }
    ]
  }'

# Read response.data.score, grade, totalRooms, defects and sources.
# Computed scores and traditional conventions are not verified verse claims.

Recipe 2 — Mandala projection (JavaScript)

When: AR app finished a scan and has a plot polygon.

const plotPolygon = [[0,0], [12,0], [12,12], [0,12]];
const r = await fetch(
  'https://api.vedika.io/v2/astrology/vastu/mandala/project/9-zone',
  {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.VEDIKA_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      plotPolygon,
      bearingDeg: 0  // geometry is already +X east, +Y north
    })
  }
);
const { data } = await r.json();
data.cells.forEach(cell => {
  // Rectangular grid cells use the input 2D frame and units.
  // Clip cells to your plot, then map to your AR world frame.
  arScene.addPolygon(clipAndMapToArWorld(cell.polygon, plotPolygon), {
    color: '#6b7280', // A grid cell alone is not an audit result.
    label: cell.zone
  });
});

Recipe 3 — AI Query in Hindi (curl)

When: User typed a Vastu question in their language.

curl -X POST https://api.vedika.io/api/v1/query \
  -H "Authorization: Bearer $VEDIKA_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "question": "मेरे मुख्य द्वार दक्षिण-पश्चिम मुखी है, क्या उपाय करूं?" }'

# Read the returned answer and its sources.
# Preserve the distinction between citations and traditional conventions.

Recipe 4 — Detailed audit (Python)

When: Premium audit — one call returns score + heatmap + remediation order.

import os, requests
r = requests.post(
    'https://api.vedika.io/v2/astrology/vastu/audit/floor-plan-detailed',
    headers={'Authorization': f'Bearer {os.environ["VEDIKA_KEY"]}'},
    json={
        'rooms': [
            {'roomType': 'kitchen', 'zone': 'SE'},
            {'roomType': 'toilet',  'zone': 'NE'},
        ],
        'plotPolygon': [[0,0],[12,0],[12,12],[0,12]],
        'bearingDeg': 0
    }
).json()

print(r['data']['score'], r['data']['grade'])
for step in r['data']['remediationOrder']:
    print(step['step'], step['room'], '-', step['action'])
# Render the returned action and its source/provenance; do not invent a citation.

Recipe 5 — Magnetic declination (true-north correction)

When: AR scan starts. Call once per scan to convert phone-compass to true-north.

curl "https://api.vedika.io/v2/astrology/vastu/direction/declination?lat=18.5204&lon=73.8567" \
  -H "Authorization: Bearer $VEDIKA_KEY"

# Read data.declinationDeg and data.verified from the response.
# The correction can be null outside the grid coverage.
function trueBearingFromCorrection(magneticBearing, data) {
  if (!Number.isFinite(magneticBearing) || data?.verified !== true ||
      !Number.isFinite(data.declinationDeg)) return null;
  return ((magneticBearing + data.declinationDeg) % 360 + 360) % 360;
}

Recipe 6 — 32-pada door classifier

When: User taps the door position in your AR scene.

curl -X POST https://api.vedika.io/v2/astrology/vastu/entrance/pada \
  -H "Authorization: Bearer $VEDIKA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "plotPolygon": [[0,0],[12,0],[12,12],[0,12]],
    "doorXY": [12, 6],
    "bearingDeg": 0
  }'

# Read response.data.pada and its source/provenance fields.
# This example places the door on the east plot boundary.
# Use your measured boundary door point for a real assessment.

Recipe 7 — AR scan quality (gate before audit)

When: Scan done; verify quality before paying for audit.

curl -X POST https://api.vedika.io/v2/astrology/vastu/ar/scan-quality \
  -H "Authorization: Bearer $VEDIKA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "pointCloudDensity": 1800,
    "polygonClosure": true,
    "compassConfidence": 0.9,
    "gpsConfidence": 0.95,
    "roomsTagged": 4,
    "roomCount": 4,
    "expectedRoomCount": 4,
    "scanDurationSec": 45,
    "scannedAreaM2": 30
  }'

# Read data.acceptForAudit and data.reScanSuggestions.
# Missing readings have null scores. Room counts must state the captured and expected scope.
# Acceptance checks caller-reported consistency; it does not attest a device scan.

Recipe 8 — Plot ratio audit (pre-construction)

When: Real-estate listing or builder's pre-construction audit.

curl -X POST https://api.vedika.io/v2/astrology/vastu/plot/ratio \
  -H "Authorization: Bearer $VEDIKA_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "plotPolygon": [[0,0],[18,0],[18,9],[0,9]] }'

# Read data.length, data.width, data.ratio and data.units.
# Dimensions stay in caller-supplied units; they are not converted to metres.
# Deprecated lengthM/widthM are null because this request has no metre scale.

AI Query — Trigger Modes & Custom Prompts

The AI Query path (POST /api/v1/query) supports three ways to invoke Vastu mode. Use the simplest one that fits your UX.

1. Auto-detection (default — 17 trigger patterns)

Send any natural-language Vastu question. The platform auto-detects intent across English keywords, Sanskrit zone names, and common placement triggers. No special syntax needed.

curl -X POST https://api.vedika.io/api/v1/query \
  -H "Authorization: Bearer vk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "question": "Where should I put my pooja room?" }'

# Auto-fires on: vastu, vaastu, ishanya, agneya, nairutya, vayavya, brahmasthana,
#                main door, pooja room, kitchen direction, sleeping direction,
#                home faces, bedroom NE/NW/SE/SW, etc.

2. Bracket override — {vastu} / {vaastu}

When the user's wording is ambiguous (e.g. "What about my place?"), prefix or inject a {vastu} bracket token to force-trigger the Vastu directive. Useful when:

  • Your UX has a "Vastu mode" toggle and you append the bracket programmatically
  • Multi-turn conversations where the user reverts to short questions ("what about the kitchen?")
  • Voice-input flows where speech-to-text strips obvious keywords
  • You want to be explicit and not rely on auto-detection edge cases
# Bracket-prefixed (recommended for explicit control)
curl -X POST https://api.vedika.io/api/v1/query \
  -H "Authorization: Bearer vk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "question": "{vastu} What about my master bedroom?" }'

# Equivalent — bracket anywhere in the question
curl -X POST https://api.vedika.io/api/v1/query \
  -H "Authorization: Bearer vk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "question": "Help with placement {vastu} please" }'

# Both forms ({vastu} and {vaastu}) trigger the same Vastu directive injection.

3. Enterprise custom Vastu preferences

Enterprise callers can add customDirective to JSON or streamed AI queries. Validated region, remedy, tone and editorial preferences join the existing custom persona before billing. They guide the Vastu part of the answer; model adherence is not guaranteed. They do not change computed scores, geometry, severity, safety rules or source citations.

Use a non-empty object with region (up to 80 characters), tone (120 characters), preferredRemedies (up to 16 strings of 120 characters), or additionalRules (up to 16 strings of 500 characters). Empty values, control characters and unknown fields return INVALID_CUSTOM_DIRECTIVE. The combined custom persona retains its 12,000-character and 400-line limits.

For detected Vastu queries, disclaimer (1–2,000 characters) and attribution (1–200 characters) append exact caller-supplied plain text to the answer. The server labels these notices and includes them in saved and replayed answers. They cannot replace safety advice or source citations. Markup and control characters are rejected.

suppressClaims: true withholds the entire generated Vastu answer before generation, with no wallet charge. Mandatory safety responses still appear. This is an all-or-nothing control; selective phrase/category suppression and severity weights are not supported. With false, normal generation and billing apply. Caller text is not a classical source or evidence that a remedy works.

curl -X POST https://api.vedika.io/api/v1/query \
  -H "Authorization: Bearer vk_ent_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "question": "{vastu} Master bedroom in NE — what should I do?",
    "customDirective": {
      "region": "south-india",
      "preferredRemedies": ["yantra", "color-therapy"],
      "tone": "formal-tamil",
      "disclaimer": "This guidance is subject to our terms.",
      "attribution": "Prepared for Acme Homes.",
      "suppressClaims": false,
      "additionalRules": [
        "Prefer non-structural options when supported",
        "Explain limits in short sentences"
      ]
    }
  }'

# Requires an authenticated Enterprise key or Enterprise plan.
# Preferences guide the answer; computed facts and source citations stay unchanged.

Trigger precedence: The bracket override {vastu} selects the Vastu path, followed by auto-detection. Enterprise customDirective adds caller preferences to the answer. Mandatory safety, grounded facts and source provenance take precedence over those preferences.

Multilingual AI Query

The AI Query path auto-detects 30 languages. No translation layer required.

LanguageSample question
Hindiमेरे मुख्य द्वार दक्षिण-पश्चिम मुखी है, क्या उपाय करूं?
HinglishBhaiya, mera kitchen south-east mein hai, kya yeh sahi hai?
Bengaliআমার পূজার ঘর কোন দিকে রাখা উচিত?
Tamilநான் தென்மேற்கு வாசலில் வீடு கட்டலாமா?
Teluguనా శయన గది పడమర దిక్కులో ఉంది, ఇది సరియేనా?
Marathiआग्नेय कोनात स्वयंपाकघर का असावे?
Gujaratiમારા ઘરમાં બ્રહ્મસ્થાન ખુલ્લું હોવું જરૂરી છે કે કેમ?
Kannadaನನ್ನ ಮನೆಯ ಮುಖ್ಯ ದ್ವಾರ ಯಾವ ದಿಕ್ಕಿಗೆ ಇರಬೇಕು?

AR Walkthrough — 7 Steps

Connect your capture app to a server proxy. The examples use north-aligned 2D geometry (+X east, +Y north), with an origin you choose. Your app supplies clipAndMapToArWorld to clip grid rectangles and transform them into its AR scene. A phone heading alone does not establish the plan coordinate frame.

1App boot — fetch reference data once

Cache the reference table locally.

// Server-side, behind your app's own authenticated proxy.
const apiBase = 'https://api.vedika.io';
const headers = {
  'Authorization': `Bearer ${process.env.VEDIKA_KEY}`,
  'Content-Type': 'application/json'
};
const zones = await fetch(`${apiBase}/v2/astrology/vastu/reference/mandala/9-zone`, { headers }).then(r => r.json());
const devatas = await fetch(`${apiBase}/v2/astrology/vastu/reference/mandala/45-devatas`, { headers }).then(r => r.json());
cacheLocally(zones.data, devatas.data);  // listed total $0.02 for these two reference calls

2Scan starts — get true-north correction

const decl = await fetch(
  `${apiBase}/v2/astrology/vastu/direction/declination?lat=${lat}&lon=${lon}`,
  { headers }
).then(r => r.json());
const trueBearing = trueBearingFromCorrection(compassBearing, decl.data);
if (trueBearing === null) { showMagneticHeadingOnly(); return; }
// Already-true native headings need no declination correction.

3Scan finishes — extract polygon and rooms

Your capture app must measure or trace the plot, align it to east/north, and map room positions to zone codes. Send each room as {roomType, zone}; an audit does not infer room zones from a plot polygon or semantic room tags.

4Check reported scan quality before paying for audit

const quality = await fetch(`${apiBase}/v2/astrology/vastu/ar/scan-quality`, {
  method: 'POST', headers,
  body: JSON.stringify({ pointCloudDensity, polygonClosure, compassConfidence,
                         gpsConfidence, roomsTagged, roomCount, expectedRoomCount,
                         scanDurationSec, scannedAreaM2 })
}).then(r => r.json());
// This gate checks caller-reported telemetry; it does not attest sensors or physical coverage.
if (!quality.data.acceptForAudit) { showReScanPrompt(quality.data); return; }

5One-shot detailed audit

const audit = await fetch(`${apiBase}/v2/astrology/vastu/audit/floor-plan-detailed`, {
  method: 'POST', headers,
  body: JSON.stringify({ rooms, plotPolygon, bearingDeg: 0 })
}).then(r => r.json());
// audit.data.score, .grade, .defects[], .devataHeatmap[], .mandalaProjection.cells[],
// .remediationOrder[]

6Render in AR scene

audit.data.mandalaProjection.cells.forEach(cell => {
  // Neutral means no flagged rule; it does not certify a surveyed or healthy zone.
  const color = audit.data.defects.some(d => d.zone === cell.zone) ? '#b42318' : '#6b7280';
  arScene.addPolygon(clipAndMapToArWorld(cell.polygon, plotPolygon), { color, label: cell.zone });
});

7User taps a defect → fetch narrative explanation

onUserTapDefect(defect => {
  fetch(`${apiBase}/api/v1/query`, {
    method: 'POST', headers,
    body: JSON.stringify({
      question: `Why is my ${defect.room} in the ${defect.zone} a problem?`
    })
  }).then(r => r.json()).then(r => showBottomSheet(r.response));
});

REST list prices checked in October 2026 against the public pricing API: reference data $0.01 per call; declination $0.02; scan-quality $0.02; floor-plan-detailed audit $0.05. This flow totals $0.09 per scan after caching, or $0.11 including the two reference calls at first launch. Refresh the current prices before budgeting. Step 7's AI Query call is priced separately; check its rate in your dashboard.

Browser Proxy — Keep the Key on Your Server

Never put a Vedika API key in HTML or browser JavaScript. Anyone who opens the page can read it and spend your balance. The AR embed (vastu-ar-embed.js) ignores an apiKey option for that reason. Browser code on your site also needs a same-origin route: api.vedika.io answers cross-origin browser calls only from Vedika's own sites and localhost, so even keyless sandbox calls from your domain are blocked.

What the reference handler does

  • It forwards only the exact method and path pairs in ROUTES. Any other request gets a 404 and is not sent to Vedika.
  • It adds your key from the server environment, and only to routes marked keyed. It builds upstream headers from scratch, so browser cookies and any browser-supplied key are dropped.
  • It forwards only the query parameters listed for each route and rejects request bodies over 64 KB with a 413.
  • It rejects a browser Origin that is not on your list. That stops other sites from using it through their visitors' browsers. It does not stop a script from calling the URL directly, so add a per-visitor rate limit in front of it (your platform's limiter or WAF). Every keyed call is billed to your account.

The AR embed sends one request, and only when you give it a location or turn on autoGeolocate: the keyless sandbox declination lookup. The embed does not apply that value to the compass, so the proxy does not add a true-north correction. Add the keyed routes your own code calls, one line each.

// Web-standard fetch handler for your server or edge runtime. No dependencies.
// Mount it at /vedika-proxy on your own site and set the embed's apiBase to '/vedika-proxy'.
const VEDIKA_API = 'https://api.vedika.io';
const PREFIX = '/vedika-proxy';
const MAX_BODY_BYTES = 64 * 1024;
const ALLOWED_ORIGINS = ['https://www.example.com']; // your site's own origins

// Exact method + path pairs. Anything else returns 404 and never reaches Vedika.
const ROUTES = {
  // The AR embed's only request. Keyless; the embed shows it and does not apply it.
  'GET /sandbox/astrology/vastu/direction/declination': { keyed: false, query: ['lat', 'lon'] },
  // Calls your own browser code makes. Each one is billed to your account.
  'POST /v2/astrology/vastu/ar/scan-quality': { keyed: true, query: [] },
  'POST /v2/astrology/vastu/audit/floor-plan-detailed': { keyed: true, query: [] }
};

function reject(status, error) {
  return new Response(JSON.stringify({ error }), {
    status, headers: { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' }
  });
}

async function readCapped(request, limit) {
  if (!request.body) return new Uint8Array(0);
  const reader = request.body.getReader();
  const chunks = [];
  let size = 0;
  for (;;) {
    const { done, value } = await reader.read();
    if (done) break;
    size += value.byteLength;
    if (size > limit) { await reader.cancel(); return null; }
    chunks.push(value);
  }
  const bytes = new Uint8Array(size);
  let offset = 0;
  for (const chunk of chunks) { bytes.set(chunk, offset); offset += chunk.byteLength; }
  return bytes;
}

async function handleVedikaProxy(request, env) {
  const origin = request.headers.get('Origin');
  if (origin !== null && !ALLOWED_ORIGINS.includes(origin)) return reject(403, 'origin not allowed');
  const url = new URL(request.url);
  const path = url.pathname.startsWith(PREFIX + '/') ? url.pathname.slice(PREFIX.length) : '';
  const route = ROUTES[`${request.method} ${path}`];
  if (!route) return reject(404, 'not found');

  const upstream = new URL(path, VEDIKA_API);
  for (const key of route.query) {
    const value = url.searchParams.get(key);
    if (value !== null) upstream.searchParams.set(key, value);
  }
  // Built fresh: browser cookies, Authorization and X-API-Key headers are never forwarded.
  const headers = { 'Accept': 'application/json' };
  const requestId = request.headers.get('X-Request-Id');
  if (requestId !== null && /^[A-Za-z0-9._:-]{1,128}$/.test(requestId)) headers['X-Request-Id'] = requestId;
  let body;
  if (request.method === 'POST') {
    if (Number(request.headers.get('Content-Length')) > MAX_BODY_BYTES) return reject(413, 'body too large');
    body = await readCapped(request, MAX_BODY_BYTES);
    if (body === null) return reject(413, 'body too large');
    headers['Content-Type'] = 'application/json';
  }
  if (route.keyed) {
    if (!env.VEDIKA_KEY) return reject(500, 'proxy key not configured');
    headers['Authorization'] = `Bearer ${env.VEDIKA_KEY}`;
  }

  const reply = await fetch(upstream, { method: request.method, headers, body });
  const out = { 'Content-Type': reply.headers.get('Content-Type') || 'application/json', 'Cache-Control': 'no-store' };
  for (const name of ['Retry-After', 'X-Request-Id']) {
    const value = reply.headers.get(name);
    if (value !== null) out[name] = value;
  }
  return new Response(reply.body, { status: reply.status, headers: out });
}
// On your page: the key never reaches the browser.
import { mountVastuAR } from 'https://vedika.io/js/vastu/vastu-ar-embed.js';
mountVastuAR(document.getElementById('vastu-ar'), { apiBase: '/vedika-proxy' });

Optional AR scene assets

Five POST operations return assets or coordinates for your scene. These calls are separate from the seven-step flow above and have their own listed prices. Asset responses include URLs and SHA-256 hashes. Cache the bytes by that hash.

Path under /vastu/ar/Result
heatmap-rasterPNG from the supplied room-zone audit. Observed and unobserved zones are separate; neutral cells do not certify safety or coverage.
anchor-recommendationsZone centroids inside your plot, mapped through your explicit metre-based scene frame.
zone-texturesNine-zone texture atlas and UV coordinates for glTF or USD.
yantra-meshesA modern nine-zone mandala in glTF or USDZ. This is a diagram, with no claim of canonical ritual geometry or remedy effect.
deity-iconsOriginal SVG and PNG devata nameplates. These are text labels, not deity portraits.
const anchors = await fetch(`${apiBase}/v2/astrology/vastu/ar/anchor-recommendations`, {
  method: 'POST', headers,
  body: JSON.stringify({
    plotPolygon: [[0,0], [9,0], [9,9], [0,9]], bearingDeg: 0,
    planToWorld: {
      units: 'metres', origin: [0,0,0],
      xAxis: [1,0,0], yAxis: [0,0,-1]
    }
  })
}).then(r => r.json());
// xAxis and yAxis map the two PLAN axes into your 3D scene.
// Your app must measure the origin, scale and north alignment.
// The API does not verify device registration or physical scan coverage.

Use the endpoint catalog for exact request bodies and response examples. Unsupported asset names reject; they are never replaced with a different design.

Device attestation for native apps

A native app can prove that a capture request came from a genuine, unmodified build of your app on a real device: Apple App Attest on iOS, Google Play Integrity on Android. The proof is bound to the exact request: the device signs a digest of the operation, the single-use challenge and the request body, so a proof cannot be reused with an edited capture or on another operation. The Vedika SDK runs inside your app, so the app identity (Apple Team ID and bundle ID, Android package and signing certificate) is yours, and you register it against your own account. Attestation proves the app, the device and the request as sent. It does not certify the measurements themselves. A browser cannot attest.

Nothing changes until you register. Without a registration every capture response carries deviceAttestation: {"status": "not_configured"} and the rest of the response is unchanged.

1. Prepare your app

PlatformWhat you doWhat Vedika needs from you
iOSEnable the App Attest capability for the App ID (developer.apple.com, Identifiers) and in Xcode under Signing & Capabilities. Set the entitlement com.apple.developer.devicecheck.appattest-environment to production for release builds (development for Xcode builds; ask us to allow development keys on a test account).Your 10-character Team ID and every bundle ID the app ships under. No secret: Apple's attestation root certificate is public.
AndroidIn Play Console, open Protected with Play, then Play Integrity API, and choose Link Cloud project to link a Google Cloud project to the app. In that project enable the Play Integrity API, then create a service account with the roles Service Account User and Service Usage Consumer, and a JSON key for it.The application ID, the SHA-256 of the app-signing certificate (Play Console, Protected with Play, Play Store distribution, Go to Play app signing), and the service-account key. Google only lets a service account in the app's own linked Cloud project decode an integrity token, which is why the key is yours. Never send the key by email or chat; ask your Vedika contact for the one-time secure hand-off. We store it in an encrypted secret store and keep only a reference.

2. Register the app identity with Vedika

Vedika sets the registration on your account today; there is no self-serve screen yet. Send your support contact the values from the table. Settings take effect within about a minute. An account with no registration is never verified against anyone else's app, and never against Vedika's.

{
  "apple": { "teamId": "A1B2C3D4E5", "bundleIds": ["com.example.rooms"] },
  "play": {
    "packageName": "com.example.rooms",
    "certSha256": ["AB:CD:...:EF"],
    "serviceAccountSecretArn": ""
  }
}

3. What the device signs

For a request to ar/room-capture, ar/scan-quality or scans/save, build the request body without deviceAttestation, then compute:

digest = SHA-256( "vedika-attest-v1\n" + operation + "\n" + challenge + "\n" + canonicalJson(body) )
  • operation is the route name: ar/room-capture, ar/scan-quality or scans/save.
  • canonicalJson is RFC 8785 (JSON Canonicalization Scheme): members sorted by UTF-16 code units, no whitespace, minimal string escapes, numbers in ECMAScript form (60 and 60.0 are the same). Keep integers within +-2^53.
  • Apple: the 32-byte digest is the clientDataHash for both attestKey and generateAssertion.
  • Google: send the digest as unpadded base64url (43 characters) in the classic nonce, or the standard requestHash.

Check your helper against this vector before shipping (more are in the Vedika monorepo under sdks/fixtures/vastu-attestation-binding; ask your support contact for a copy). Send exactly the body you hashed: a retry of the same request replays without a new proof, and any edit needs a new digest and a new challenge.

operation: ar/scan-quality
challenge: c2NhbnF1YWxpdHljaGFsbGVuZ2UtMDAwMDAwMDAwMDA
body:      {"scanDurationSec": 60, "scannedAreaM2": 41.5, "pointCloudDensity": 500,
            "polygonClosure": true, "roomsTagged": 3, "compassConfidence": 0.9, "gpsConfidence": 0.8}
canonical: {"compassConfidence":0.9,"gpsConfidence":0.8,"pointCloudDensity":500,"polygonClosure":true,"roomsTagged":3,"scanDurationSec":60,"scannedAreaM2":41.5}
digest:    ef33424724bdbf3ea1ca99dd19864ebb40e5f421a8e8bf7c8acaa1b9e0f2c20c
requestHash / nonce (base64url): 7zNCRyS9vz6hypndGYZOu0Dl9CGo6L98isqhueDywgw

4. The flow

  1. Call POST /v2/astrology/vastu/ar/attestation/challenge with {"platform": "ios"} or {"platform": "android"}. It is not billed. You get a single-use challenge that expires in 5 minutes and is bound to your account and platform. challenge: null with status not_configured means this account has no registration for that platform.
  2. iOS, once per install: generateKey() returns the keyId; keep it in the Keychain. The first request with a key sends attestKey(keyId, digest) as attestationObject; later requests send generateAssertion(keyId, digest) as assertion.
  3. Android: request a Play Integrity token whose nonce (classic) or requestHash (standard) is the digest, and send it as integrityToken.
  4. Send deviceAttestation inside the same request body you hashed (with snapshot.capture for scans/save).

Each challenge is spent by the first attempt, successful or not. Fetch a new one for every attempt. A request that does not verify is answered before anything is computed or charged: 400 ATTESTATION_INVALID when the proof is wrong or does not match the request, 503 ATTESTATION_UNAVAILABLE when a store or Google could not be reached. A verified response reports deviceAttestation: {"status": "verified", "platform": "..."} and deviceAttested: true on the scan-quality block. Scores never change.

Swift

The reference helper below is exact for numbers that are zero or between 1e-4 and 1e15, which covers the capture schema. Check it against the fixtures.

import CryptoKit
import DeviceCheck
import Foundation
import VedikaSDK

/// RFC 8785 canonical JSON of a request dictionary (without deviceAttestation).
func canonicalJSON(_ value: Any) -> String {
    switch value {
    case let d as [String: Any]:
        let keys = d.keys.sorted { Array($0.utf16).lexicographicallyPrecedes(Array($1.utf16)) }
        return "{" + keys.map { canonicalString($0) + ":" + canonicalJSON(d[$0]!) }.joined(separator: ",") + "}"
    case let a as [Any]: return "[" + a.map(canonicalJSON).joined(separator: ",") + "]"
    case let s as String: return canonicalString(s)
    case is NSNull: return "null"
    case let n as NSNumber:
        if CFGetTypeID(n) == CFBooleanGetTypeID() { return n.boolValue ? "true" : "false" }
        let x = n.doubleValue
        return (x == x.rounded() && abs(x) < 1e15) ? String(Int64(x)) : "\(x)"
    default: preconditionFailure("not JSON")
    }
}
func canonicalString(_ s: String) -> String {
    var out = "\""
    for u in s.unicodeScalars {
        switch u {
        case "\"": out += "\\\""
        case "\\": out += "\\\\"
        case "\u{8}": out += "\\b"
        case "\t": out += "\\t"
        case "\n": out += "\\n"
        case "\u{c}": out += "\\f"
        case "\r": out += "\\r"
        default: out += u.value < 0x20 ? String(format: "\\u%04x", u.value) : String(u)
        }
    }
    return out + "\""
}
func bindingDigest(operation: String, challenge: String, body: [String: Any]) -> Data {
    Data(SHA256.hash(data: Data(("vedika-attest-v1\n\(operation)\n\(challenge)\n" + canonicalJSON(body)).utf8)))
}

func attestedCapture(client: VedikaClient, capture: VastuRoomCapture, keyId: String, firstUse: Bool) async throws {
    let service = DCAppAttestService.shared
    guard service.isSupported else { return }  // simulator or old device: send without deviceAttestation

    let grant = try await client.vastu.vastuOperation(
        VastuContracts.arAttestationChallenge,
        request: VastuArAttestationChallengeRequest(platform: "ios"))
    guard let challenge = grant.data.challenge else { return }  // not registered: send without deviceAttestation

    // Hash the request exactly as it will be sent, without deviceAttestation.
    let plain = VastuArRoomCaptureRequest(capture: capture)
    let digest = bindingDigest(operation: "ar/room-capture", challenge: challenge, body: plain.dictionary)
    let proof = firstUse
        ? try await service.attestKey(keyId, clientDataHash: digest)
        : try await service.generateAssertion(keyId, clientDataHash: digest)

    let attestation = VastuDeviceAttestation(
        platform: "ios", challenge: challenge, keyId: keyId,
        attestationObject: firstUse ? proof.base64EncodedString() : nil,
        assertion: firstUse ? nil : proof.base64EncodedString())
    _ = try await RoomCaptureUploader(service: client.vastu)
        .upload(capture, deviceAttestation: attestation)
}

Kotlin

The reference helper below uses the shortest decimal form of each double, which matches RFC 8785 for magnitudes up to 1e21. Check it against the fixtures.

import com.google.android.play.core.integrity.IntegrityManagerFactory
import com.google.android.play.core.integrity.IntegrityTokenRequest
import io.vedika.sdk.*
import kotlinx.coroutines.tasks.await
import org.json.JSONArray
import org.json.JSONObject
import java.math.BigDecimal
import java.security.MessageDigest
import java.util.Base64

/** RFC 8785 canonical JSON of a request map (without deviceAttestation). */
fun canonicalJson(v: Any?): String = when (v) {
    null, JSONObject.NULL -> "null"
    is JSONObject -> canonicalJson(v.keys().asSequence().associateWith { v.get(it) })
    is JSONArray -> canonicalJson(List(v.length()) { v.get(it) })
    is Map<*, *> -> v.entries.sortedBy { it.key as String }  // String order is UTF-16 code-unit order
        .joinToString(",", "{", "}") { canonicalString(it.key as String) + ":" + canonicalJson(it.value) }
    is List<*> -> v.joinToString(",", "[", "]") { canonicalJson(it) }
    is String -> canonicalString(v)
    is Boolean -> v.toString()
    is Int, is Long -> v.toString()
    is Number -> BigDecimal(v.toDouble().toString()).stripTrailingZeros().toPlainString()
    else -> error("not JSON")
}

fun canonicalString(s: String): String = buildString {
    append('"')
    for (c in s) when {
        c == '"' -> append("\\\"")
        c == '\\' -> append("\\\\")
        c == '\b' -> append("\\b")
        c == '\t' -> append("\\t")
        c == '\n' -> append("\\n")
        c == '\u000c' -> append("\\f")
        c == '\r' -> append("\\r")
        c < ' ' -> append("\\u%04x".format(c.code))
        else -> append(c)
    }
    append('"')
}

fun bindingDigest(operation: String, challenge: String, body: Map<String, Any?>): ByteArray =
    MessageDigest.getInstance("SHA-256")
        .digest("vedika-attest-v1\n$operation\n$challenge\n${canonicalJson(body)}".toByteArray(Charsets.UTF_8))

suspend fun attestedCapture(context: Context, client: VedikaClient, capture: VastuRoomCapture) {
    val grant = client.vastu.vastuOperation(
        VastuContracts.arAttestationChallenge,
        VastuArAttestationChallengeRequest(platform = "android"))
    val challenge = grant.data.challenge ?: return  // not registered: send without deviceAttestation

    // Hash the request exactly as it will be sent, without deviceAttestation.
    val digest = bindingDigest("ar/room-capture", challenge,
        VastuArRoomCaptureRequest(capture = capture).toMap())
    val token = IntegrityManagerFactory.create(context)
        .requestIntegrityToken(
            IntegrityTokenRequest.builder()
                .setNonce(Base64.getUrlEncoder().withoutPadding().encodeToString(digest))
                .build())
        .await().token()

    client.vastu.vastuOperation(
        VastuContracts.arRoomCapture,
        VastuArRoomCaptureRequest(
            capture = capture,
            deviceAttestation = VastuDeviceAttestation(
                platform = "android", challenge = challenge, integrityToken = token)))
}

Play Integrity has a default quota per linked Cloud project (10,000 requests a day when this was written); it is your project's quota, shared by token requests and decodes. The Vedika SDKs carry the request types and the challenge call. The platform calls above (DeviceCheck, Play Integrity) and the digest helpers are yours to run, because they sign for your app's identity. The helpers are reference code to adapt, not part of a published SDK release.

Caching, Errors, Idempotency

Caching strategy

Endpoint / storageReuse policy
Reference data (11 endpoints)
Validated local table
Refresh online; compare referenceVersion. Keep the saved date visible offline.
Magnetic declination
Account-scoped local cache
Exact coordinates and explicit date; use an app expiry time.
Mandala projection / Audit / Entrance pada
Account-scoped local cache
Reuse unchanged inputs until the rule revision or app expiry changes.

Reuse unchanged computed data. The helper below sorts object keys and keeps array order, strings and parsed JSON numbers exact. It does not round coordinates or bearings. Include the complete body, exact endpoint path, language, account scope and your current rule-contract revision. Never use an API key as the account scope.

Store only successful data with its key, saved time and expiry. Start with a 24-hour app expiry and shorten it for your use case. Clear account entries on sign-out; invalidate them when inputs or the rule revision change. A cache hit reuses the old computed data, not a new billing receipt. This recipe applies to deterministic audit, projection and entrance results, not saved-scan writes, AI answers or current-time calculations without an explicit date.

A retained request ID protects retries of one logical call; it is not a permanent result cache. Set contractRevision from the rule version you track for the service. This is your app's cache policy, not a server cache guarantee.

async function vastuCacheKey(accountScope, contractRevision, path, body, language = 'en') {
  if (![accountScope, contractRevision, path, language].every(v => typeof v === 'string' && v.trim())) {
    throw new TypeError('Complete account, rule, path and language context is required');
  }
  const active = new Set();
  function canonical(value) {
    if (value === null || typeof value === 'string' || typeof value === 'boolean') {
      return JSON.stringify(value);
    }
    if (typeof value === 'number' && Number.isFinite(value)) return JSON.stringify(value);
    if (typeof value !== 'object' || active.has(value)) {
      throw new TypeError('Only finite, acyclic JSON values are supported');
    }
    active.add(value);
    let result;
    if (Array.isArray(value)) {
      result = '[' + Array.from(value, canonical).join(',') + ']';
    } else if (Object.prototype.toString.call(value) === '[object Object]') {
      result = '{' + Object.keys(value).sort().map(key =>
        JSON.stringify(key) + ':' + canonical(value[key])).join(',') + '}';
    } else {
      throw new TypeError('Only JSON objects and arrays are supported');
    }
    active.delete(value);
    return result;
  }
  const input = canonical({ accountScope, contractRevision, path, language, body });
  const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(input));
  return 'vastu-cache-v1:' + Array.from(new Uint8Array(digest), byte =>
    byte.toString(16).padStart(2, '0')).join('');
}

Error handling with retry + idempotency

Server-side example for the floor-plan audit. Create and retain one request ID per logical audit; reuse it with the same path and body after a timeout. A new audit needs a new ID. Send one retry header: X-Request-Id takes precedence over X-Idempotency-Key and Idempotency-Key.

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
async function callVedika(path, body, requestId, maxAttempts = 3) {
  if (!requestId || !Number.isInteger(maxAttempts) || maxAttempts < 1) {
    throw new Error('A retained request ID and a positive attempt count are required');
  }
  const payload = JSON.stringify(body);
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    let r;
    try {
      r = await fetch(`https://api.vedika.io${path}`, {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${VEDIKA_KEY}`,
          'Content-Type': 'application/json',
          'X-Request-Id': requestId
        },
        body: payload
      });
    } catch (error) {
      if (attempt === maxAttempts - 1) throw error;
      await sleep(1000 * 2 ** attempt);
      continue;
    }
    let data;
    try {
      data = await r.json();
    } catch (error) {
      if (r.ok) throw error;
      data = {};
    }
    if (r.ok) return data;
    const retryable = r.status === 429 || r.status >= 500;
    if (!retryable || attempt === maxAttempts - 1) {
      throw new Error(`Vedika request failed (${r.status})`);
    }
    const seconds = Number(r.headers.get('Retry-After') ?? data.retryAfter);
    await sleep(Number.isFinite(seconds) && seconds > 0
      ? seconds * 1000 : 1000 * 2 ** attempt);
  }
}

Status code map

StatusMeaningAuto-refund?
400Validation error (bad polygon, missing fields)Input validation happens before billing
401Missing or invalid API key—
402Insufficient wallet balance — top up at /dashboard—
429Rate limit exceeded — use Retry-After when present, or the JSON retryAfter value—
500Server error / calculation failureYes

SDK Examples

JavaScript / TypeScript (@vedika-io/sdk)

import { VedikaClient } from '@vedika-io/sdk';
const vedika = new VedikaClient({ apiKey: process.env.VEDIKA_KEY });

const audit = await vedika.vastuAudit('floor-plan', {
  rooms: [
    { roomType: 'kitchen', zone: 'SE' },
    { roomType: 'toilet',  zone: 'NE' }
  ]
});
console.log(audit.score, audit.grade, audit.defects);

Python (vedika-sdk)

from vedika import VedikaClient
vedika = VedikaClient(api_key="vk_live_...")

audit = vedika.vastu_audit("floor-plan", params={
    "rooms": [
        {"roomType": "kitchen", "zone": "SE"},
        {"roomType": "toilet",  "zone": "NE"},
    ]
})
print(audit["data"]["score"], audit["data"]["grade"], audit["data"]["defects"])

Migrating from Other Astrology APIs

What competitors offerVedika equivalentMigration notes
Generic "Vastu remedies" endpoint/vastu/audit/floor-planCleaner contract — structured rooms[] or free-text fallback
Direction lookup/vastu/reference/directions/8Static; cache once at boot
Mandala images (PNG)Polygon coords via /mandala/project/*Render yourself — gives full control over color/opacity/AR overlay
Vastu PDF reportCompose /audit/floor-plan-detailed + your PDF generatorYou control branding
32-pada door classification/vastu/entrance/padaUse the returned structured data in your own interface.
AR-ready mandala projection/vastu/mandala/project/*Use the returned structured data in your own interface.
Magnetic declination correction/vastu/direction/declinationUse the returned structured data in your own interface.

Why customers switch

  • Source citations where a verse exists. Read each rule’s source and classification. Verified verse claims cite Brihat Samhita Ch.53 or Manasara Ch.38. Other room/direction advice is convention, geometry and scores are computed, and remedies are modern practice without verified verse backing or measured efficacy.
  • AR-native. Plot-coordinate polygons, magnetic-declination correction and checks of caller-reported scan telemetry for your renderer. These checks do not attest device alignment or physical coverage.
  • Predictable pricing. Same per-call structure across all 704 operations. No surprise multipliers.
  • FREE Sandbox. Develop and test without subscribing. Production-shape fixtures, no API key. Observe the rate-limit response when a limit is reached.

FAQ

Do I need to build a mobile app?

No. You can call the REST endpoints from your server or a web interface. The supplied compass demo uses camera and heading data on a supported phone. It does not capture a room mesh. On a WebXR headset the same page can also draw the eight-zone grid around you in an immersive-vr session — a visualisation of the zone grid rather than a measurement (a headset has no compass, so you set north yourself), and not available on iPhone or iPad. The plan tracer accepts manually traced polygons.

What coordinate system do mandala projections use?

Plot-local units, an origin you choose, +X = east, +Y = north. Confirm this against your AR framework's convention (ARKit and Sceneform both default to a different origin) and remap before rendering — do not assume the origin matches your scene transform matrix.

How do I tell if I'm in sandbox or production?

The /sandbox/v2/vastu/ routes do not charge. A request with no body returns a synthetic example. When you supply a body, missing fields stay missing and explicit empty values reach validation. Check the host, path and response.meta.contentMode. The sandbox reports size limits; it does not prove paid or deployed behavior.

What inputs does plan generation require?

State positive width and length, a valid facing direction, and either explicit rooms or a complete room brief. The brief must state bedroom and toilet counts, floors, and each core room choice, including false choices. A BHK value states bedroom, living and kitchen choices; a half BHK adds a study. See the endpoint example for all required fields. Optimization also needs your plot dimensions and facing; it does not invent a canvas.

Can a detailed audit run without north?

Explicit room zones can still be audited. Without bearingDeg, the detailed audit marks the result partial and returns no mandala projection. A numeric zero is valid north. Supply the measured bearing before using projected geometry.

Can I cache the reference data?

Yes — the /reference/* group is static. Each response includes data.referenceVersion, an opaque SHA-256 revision of that table and its provenance. Fetch at launch, store the revision with the table, and replace the stored table when a later refresh has a different revision. Show the saved date when offline; a local cache avoids another API request while you reuse the stored data. A refresh on a paid route is still a normal billed request; the revision is not an HTTP conditional-cache token. The listed reference price checked in October 2026 is $0.01 per call; refresh the public pricing API for current rates.

What if a customer's plot is not rectangular?

Pass any polygon (≥3 vertices). The audit and projection endpoints accept arbitrary polygons. Use /plot/shape for computed geometry and conventional shape grades. The grade is not a verified classical verse claim.

Are saved scans private?

The five saved-scan routes are implemented but account storage is disabled by default. A disabled deployment returns 503 SCAN_STORAGE_DISABLED without charging. Sandbox examples reset on each call and never store or delete account data.

When enabled, save, retrieve, list, delete and history require authentication. The server isolates records by account. Save accepts a property ID, a unique scan ID, room/zone inputs and optional geometry or reported telemetry; it recomputes the audit and does not certify a physical survey. Access lasts the selected 1–30 days. Delete revokes access without a fee. Physical deletion uses separate storage lifecycle rules and is not immediate. Request and billing records have their own retention policy.

Use a new scan ID for each save and retain it for retries; never reuse an old ID. Retrieve, list and history require a body requestId; use a new value for each fresh operation and retain it for retries. These routes reject retry headers, so the general header-based retry example above does not apply. See the typed API contract for complete request fields and limits.

Courtyards, wings and units

Send polygon with holes for courtyards and shafts. Use multipolygons: [{polygon, holes}, …] for separate wings. Voids must lie inside their outer ring and filled components must be disjoint. Net area, centroid and zone intersections exclude voids; centroidInside and Brahmasthan poleInside show when a point falls in a void.

Structured plan inputs accept units or inputUnits: m, ft, mm, in. Computation uses metres and echoes the source scale. Tagged polygon extents must agree with declared dimensions. Fields suffixed M/M2 always retain metre units. Existing untagged requests retain legacy behavior. Native captures accept these codes in capture.frame.units, alongside the existing metres value.

Image and PDF imports also accept units/inputUnits. Calibrate with scaleInputUnitsPerUnit (physical units per pixel or PDF point), or the existing metric scaleMetersPerUnit. Both must agree if supplied together. Unit tags alone leave scale unknown and require review; calibrated geometry returns metres and the input unit is echoed.

POST /v2/astrology/vastu/plan/convert-units is free: send {plan, inputUnits, outputUnits}. Lengths scale linearly, areas quadratically; bearings, IDs and metre-suffixed fields remain intact. DXF export and plan/export-ifc accept outputUnits. Both formats retain region boundaries and interior rings. IFC export uses measured compute and response delivery cost ×4, rounded to cents with a positive $0.01 minimum. Errors cost $0.00.

Ready to build?

Try the Sandbox without an API key. Subscribe when you're ready to ship — plans from $12/mo with FREE Sandbox always included.

Pin rules and verify assessment receipts

GET /v2/vastu/rules/versions lists the current rules ID and two retained snapshots. Add rulesVersion to plan/analyze, assessments or score requests. The pin governs room-placement rules and weighting; geometry normalization is shared. Unknown versions fail before billing.

POST /v2/vastu/plan/compare-versions accepts fromVersion, toVersion, operation and input. It returns both assessments and per-rule reasons for changed findings. Successful comparison costs $0.01; errors cost $0.

Add receipt: true to return an Ed25519-signed JWS containing the input and result hashes, rules ID, score, source labels and timestamp. It adds no receipt fee. An unavailable signing key returns 503 with no charge. POST /v2/vastu/receipt/verify verifies the token and optionally matches its input for free. Public keys are at /.well-known/vastu-receipt-keys.json. Retain your exact input and the issued receipt.

Measured geometry, constraints and survey frames

Use headingErrorDeg and positionErrorM on zone and pada computations to request an additive uncertainty result. Without these fields the existing response stays identical. The result preserves the point computation, lists possible zones and padas, marks stability and reports the bearing margin to the nearest boundary. Heading is a symmetric angular bound; position is a Euclidean disc in metres. A room can override the bounds. The plot frame is held fixed; this does not estimate unprovided survey errors. Position bounds require point/room geometry and an explicit plot frame. Grid propagation uses analytical interval geometry and disc-buffered cells, with half-open cells so tangency respects the existing boundary assignment. A categorical zone retains its full interval; its bearing margin is null because no precise bearing was supplied.

plan/optimize accepts constraints: lockedRooms (room IDs), wetShafts or plumbingStacks, loadBearingWalls, columns and minSizes. Supply current room polygons and plot.units: "m"; declared setbacks are enforced using the same cardinal and relative setback rules as plan generation. Relative setbacks need plot.facing. Both wetShafts and plumbingStacks are enforced when supplied. Shaft polygons stay fixed. Explicit roomIds, or initial shaft adjacency, binds rooms to each shaft. Width and height minima refer to axis-aligned extents. Search evaluates translations, atomic swaps and minimum-size resizes deterministically. Every proposed alternative is feasible; each atomic suggestion includes its shared-score delta. Locked rooms remain fixed. unmetConstraints reports infeasible or unattainable requirements. This is a bounded local search, with no claim of a global optimum.

ar/capture-merge registers two to 32 room-capture payloads using explicit controls. Each link names fromCaptureId and toCaptureId, and provides at least two corresponding fromXY/toXY controls or shared door endpoints. Declare each floor's elevation and reference origin/heading. All linked controls are jointly fitted by anchored least-squares rigid registration per floor; scale stays fixed. Disconnected controls, degenerate baselines, excessive RMS/maximum residual or inconsistent control cycles return 400 without a charge. The response has one metre east/true-north frame, transformed rooms/openings, per-pair residuals, registrations and a floor stack.

plot/from-survey requires crs and units. Supported codes are EPSG:4326 (XY means longitude, latitude in degrees), EPSG:32642 through EPSG:32646 (WGS84 UTM 42N–46N), EPSG:7755 (WGS84 India NSF LCC) and EPSG:7756 (WGS84 Andhra Pradesh). Projected input uses metres or international feet. The output is a WGS84 local east/north frame with origin, converted boundary/control points, area and grid convergence. True north follows the local ellipsoidal meridian, rather than assuming grid north. Points must lie within the declared CRS area and 20 km of the local origin. Historical datums need a declared transformation and are not inferred.

For ar/capture-merge and plot/from-survey, price is measured attributable compute cost × 4, settled to USD cents with half-even rounding and a positive $0.01 minimum. The public pricing API quotes this minimum. Successful paid responses include pricing.attributableCostUsd, computedPriceUsd and settledChargeUsd. Set maxChargeUsd to a nonnegative decimal string such as "0.05" to bound the wallet debit. A charge above that bound returns 400 before debit. Billing runs once after a successful computation; invalid or uncomputed requests are uncharged. These operations make no model call and store no scan. All classical placement rules and their citations remain unchanged.

Drawing sheets and an integration workspace

POST /v2/vastu/report/drawing-sheet produces HTML or PDF from metric plan polygons at 1:50, 1:100 or 1:200 on A3/A2 landscape sheets. It includes dimensions, a scale bar, true north, project and architect fields, a modern directional overlay and up to six embedded field photographs. Oversize drawings fail rather than shrink. Each successful output costs $0.01; validation or renderer failures charge zero.

Sandbox keys use /sandbox/v2/vastu/workspace/{properties,jobs,get,list,reset,webhook,report} for isolated seven-day test state, assessment jobs, HTML sheets and exact signed callback envelopes. Test operations cost $0.00 and cannot address live data or the live debit path. Replay callbacks into your receiver; the workspace accepts no callback URL. The deployable Node reference app keeps its API key in server environment variables and handles manual, image and DXF input, report downloads and signature verification.

Request examples, SDK methods and deployment prerequisites

Property collaboration, activity and physical erasure

Owners create pending invitations by email or account ID. Every valid target returns HTTP 202 with an opaque invitationId and pending status, including addresses that have no account yet. No email is sent and no account lookup occurs. Pending recipient identities are stored only as hashes and are absent from members and activity. Viewers read; editors update and comment; reviewers comment and approve or reject a linked assessment at an exact revision and content hash. Revocation applies to subsequent requests. An approval stays with its original revision.

Use properties/collaboration/invite, get, members, revoke, comment, review and update under the collaboration prefix. The recipient accepts by repeating invite with ownerId and accept: true, using their own account ID or current verified email. Acceptance returns HTTP 200 with accepted status and grants the stored invited role; the supplied role cannot upgrade it. An address invited before signup can accept after verification. Pass ownerId when accessing a property shared by another account. Owners cancel a pending invitation through revoke with invitationId, or revoke an accepted member with accountId. Each caller may make 20 invitation requests per UTC hour; further requests return an unbilled 429 for every target. Pending invitations follow property retention and erasure. Control operations are free; a successful editor update is $0.01. Invalid or denied work keeps no charge.

properties/activity/list and properties/activity/export provide authorized, paged activity with actor, property/revision, action, timestamp and hash chain. Export uses JSONL. Entries are append-only until their retention deadline or property erasure. Retrieve your audit export before deletion.

Owner-only property and archive deletion fences new work, drains in-flight requests and physically removes active property rows, collaboration/review/activity rows, contained reports and linked or discovered captures, and every version of their scan/archive artifacts. A pending response is 202; retry at retryAfterEpoch. Completion returns a SHA-256 erasure receipt listing removed rows and artifact versions by identifier/hash. Permission failures or incomplete purges never return a completed receipt.

Encrypted recovery backups expire within a maximum 35-day recovery window; erasure fences must be replayed before restored data is served. Minimal pseudonymous suppression fences and the content-free receipt remain to prevent recreation. Financial and security records retain their own schedules. A property receipt covers active property storage; its hash is not a digital signature or a claim of instant backup destruction. See typed requests and responses.

Vastu property retention and deletion

Deleting a property, its saved scans, contained reports and archive exports is free. Archive retention is opt-in only: an explicit paid extension request can retain property records for up to 60 months (30-day months). Archives remain deletable at any time and covered by GDPR erasure. Without an archive extension, existing Vastu retention stays at 30 days. Export download links last one hour; export objects expire after one day.

Track remedy work and partner catalog items

Keep a property’s report JSON under data.assessments[reportRef] and include that reference in linkedAssessmentIds. A task’s findingRef points to a remedy in that saved report, such as /remedies/0. Copy the finding’s exact remedyKey. Source references stay fixed when the task changes.

POST /v2/vastu/remediation/tasks/upsert records a task’s status (pending, in_progress, completed or cancelled), assignee, dueDate (YYYY-MM-DD) and evidence references. New tasks start pending. Completion requires evidence; an evidence entry can carry a photoRef and a note. Use expectedRevision=0 for an absent board, then the revision returned by the last write. Each mutationId binds the full request. A stale revision returns 409; an exact immediate replay returns the stored result at zero charge. Follow reassessmentLink.operation with its method and request, then select resultPointer in the returned JSON; the assessmentId identifies that saved reading. A completed task can be reopened; its prior status history and reassessments remain recorded.

POST /v2/vastu/remediation/reassess accepts propertyId, taskId, expectedRevision, mutationId and a new plan. It computes a new supplied-input plan assessment after the recorded completion and places its assessmentId and reassessmentLink on the completed task. remediation/tasks/list reads the property’s board. remediation/tasks/delete removes the board with id and matching confirmId.

POST /v2/vastu/merchant/catalog/upload accepts catalogId, expectedRevision, mutationId, format (csv or json) and content. JSON content is an array. CSV content is a string with this header:

remedyKey,itemId,kind,label,availability,link,referralRef
vastu.remedy.zone.NE,site-visit,service,"Consultation, on site",on_request,https://partner.example/visit,visit-ref

Use keys from reference/remedies/catalog. kind is sku or service. availability is in_stock, out_of_stock, on_request or unavailable. link and referralRef are optional. At most 200 unique remedyKey/itemId pairs fit in the 64 KiB request/store limit. CSV supports quoted commas, doubled quotes and quoted newlines. Unknown fields, unknown remedy keys and unsafe links return 400 before charging.

merchant/catalog/get returns the owner’s catalog. merchant/remedies accepts catalogId and remedyKeys and returns mappedItems alongside the original modern-practice remedy and its provenance. Plan reports, room responses and plan assessments can also select an optional merchantCatalogId. Items retain partner-supplied availability and referral references. merchant/catalog/delete requires id and matching confirmId.

Each successful useful workflow operation costs $0.01 (attributable cost ×4, with a one-cent minimum). Deletions, empty task boards, unmatched catalog results and exact mutation replays are free. Validation errors retain no charge. Failed writes refund the charge; a pending refund is reported explicitly with its charged amount and refundPending. Catalogs and boards expire within 30 days; board expiry never exceeds the property’s expiry. Deleting a property also removes its board. Account erasure covers both stores and prevents new writes.

Compose the report and partner CTA

plan/report accepts an optional composition object with sections, intro, outro and ctaBlocks. sections is a unique ordered selection from summary, facing, plot-shape, compliance, rooms, zones, defects, remedies, elements and sources. intro/outro are plain text, each at most 4000 UTF-8 bytes. Up to eight CTA blocks carry a label, an absolute HTTPS link and an optional phone. HTML escapes every custom text value and validates links; quotations and assessment facts remain unchanged.

{"format":"html","composition":{"sections":["remedies","rooms","sources"],"intro":"Property review for the interiors team","outro":"Record completion before the next assessment.","ctaBlocks":[{"label":"Book a site visit","link":"https://partner.example/visit","phone":"+91 98765 43210"}]}}

Use format=html for the standalone HTML artifact or format=pdf for a server-rendered PDF. Decode a PDF artifact’s base64 content and save its filename. Both formats use the same section manifest and partner blocks. Rendering failure returns 503 without charge; report generation retains its published report price. The uncomposed report’s existing section order remains the default.

Property portfolios, usage and spending caps

Store city and tags in the property’s data object. Send the non-secret propertyId and tenantRef with each Vastu request. For GETs, use query parameters or x-vastu-property-id and x-vastu-tenant-ref headers. Body and header values must agree. The identifiers belong to the authenticated account; they do not grant access to another account’s properties.

POST /v2/astrology/vastu/portfolio/search filters city, tags, minScore, maxScore, zoneDefects, ruleset and inputSource. Sort by propertyId, scoreAsc, scoreDesc, city, createdAt, tags, zoneDefectsAsc or zoneDefectsDesc. Numeric rankings place unassessed properties last. The latest assessment is retained for the property lifetime independently of short-lived cost receipts. limit is 1–100; nextCursor identifies the last property in the same filtered query. Scores from different rulesets or input sources cannot be ranked together. The ruleset identifier is bound to the compiled scoring implementation.

POST portfolio/compare accepts 2–10 distinct propertyIds. It returns one column per property and comparableGroups. Every property must have a computed assessment. No score is synthesized across groups.

POST portfolio/analytics returns account and per-tag counts and daily observations for properties created, assessed, report-delivered and returning. A returning property has assessments on two distinct UTC dates in the window. Report delivery means a report artifact returned successfully; it does not mean an email was opened or a customer read it. Tags describe the most recently observed property metadata. Only observed properties and attributed calls after feature enablement are covered. Dated counts survive expiry and deletion; derived assessment content is cleared on deletion. Analytics supports tag and propertyId filters and historical windows at UTC-day granularity. tenantRef is a cost dimension, not an analytics filter.

POST portfolio/usage groups calls and exact decimal-string USD charges by propertyId and tenantRef. POST portfolio/usage/export returns generated CSV in data.csv; Accept: text/csv downloads it directly. CSV fields are quoted and spreadsheet formula prefixes are neutralized. fromEpoch and toEpoch select a window within the last 30 days. Use propertyId or tenantRef filters for costs. Completed receipts are retained for 40 days to cover native late-refund recovery and are then pruned on subsequent attributed calls; the query window remains 30 days. Dormant receipts remain until the next call or account erasure. Receipts with pending lifetime indexing are retained until that indexing is recovered.

POST portfolio/budgets/set accepts exactly one of propertyId or tenantRef and capUsd as a USD decimal string with up to four fractional digits. A null cap removes that limit. Caps are lifetime limits, not monthly resets. POST portfolio/budgets/get returns capUsd, spentUsd and reservedUsd. Both dimensions reserve the quote atomically before the wallet call. Wallet cent rounding and the positive one-cent minimum are included in the quote. 402 BUDGET_EXCEEDED means zero charge. A cap cannot be lowered below spent plus reserved funds.

Successful charges settle their reservation; confirmed failures release it. Concurrent duplicate identities return 409 while the first charge is pending. Replays do not consume another budget allocation. Uncertain outcomes retain their reservation; after the serving request bound, retry recovery confirms the native charge or fences its exact identity before releasing funds. Refund attribution reconciles from permanent native refund guards on cost/budget reads or before retrying a cap rejection, even if the first attribution write failed. Sandbox budget configuration uses a separate owner namespace.

All seven portfolio operations are explicitly free; the original Vastu calculation price still applies to successful calculations. The current bounded query supports 5,000 active properties, 10,000 attribution dimensions and up to 5,000 retained purchases per account, within a 3,300,000-byte budget snapshot written atomically across up to eleven storage chunks. Per-property lifetime summaries and dated counts use separate owner-scoped rows in the same store. Capacity failure is explicit and occurs before a new charge. No data is silently truncated and spending totals never expire with usage history. Portfolio data uses the property store’s GDPR fence and owner erasure.

{"tenantRef":"agency-team-7","capUsd":"5.00"}

The JavaScript, Python, Swift and Kotlin SDKs expose typed portfolio methods. The CSV response uses JSON by default so these SDKs retain their normal envelope handling.