# Virtual Try-On Platform — Public API Reference **Base URL:** `https://vton.kinvyt.com` All request and response bodies use `camelCase` JSON unless noted. All timestamps are ISO 8601 UTC. --- ## Table of Contents 1. [Authentication](#authentication) 2. [Error Responses](#error-responses) 3. [Rate Limits](#rate-limits) 4. [Try-On](#try-on) 5. [Jobs](#jobs) 6. [Sessions](#sessions) 7. [Credits](#credits) 8. [Spending](#spending) 9. [Usage & Billing](#usage--billing) 10. [Tenant Management](#tenant-management) 11. [Webhooks](#webhooks) 12. [Appendix](#appendix) --- ## Authentication The API uses four types of keys. You generate them yourself from the dashboard (see [Getting Your Keys](#getting-your-keys)); they are **not** issued automatically at signup. Each key is **shown only once** when generated — it is never stored in plain text after that point, only its prefix is displayed. ### Getting Your Keys Keys are self-service — no paid plan or manual provisioning is required, and your first project includes 50 trial credits. 1. Sign in at `https://vton.kinvyt.com/dashboard/login` with **Sign in with Google**. 2. Create a **project** (your tenant) when prompted, or via **New Project** in the sidebar. Keys and credits are scoped to a project. 3. Select the project in the top bar, then open **API Keys** in the sidebar (`/dashboard/keys`). 4. Click **Generate** on the key you need: - **Secret Key** (`sk_`) — backend integrations (most common). - **Master Key** (`mk_`) — key rotation and tenant self-management via the API. - **Publishable Key** (`pk_`) — browser/mobile session flows only. 5. Copy the key immediately — it is shown once. **Regenerate** replaces a key and invalidates the previous value. > If the API Keys page shows "No project selected," pick a project in the top bar first (or create one). ### Key Types | Prefix | Type | Scope | |--------|------|-------| | `sk_` | **Secret Key** | Server-side operations: submit jobs, poll status, read credits, read usage | | `mk_` | **Master Key** | Tenant self-management: rotate keys, update settings, revoke sessions | | `pk_` | **Publishable Key** | Client-side only: create session tokens. Never use in server code | | `sess_*` | **Session Token** | Short-lived token issued from a `pk_` key. Used for try-on jobs from frontend | ### How to Authenticate **Secret or Master Key** — include in the `X-Api-Key` header: ```http X-Api-Key: sk_live_xxxxxxxxxxxxxxxxxxxx ``` **Session Token** — include in the `Authorization` header: ```http Authorization: Bearer sess_xxxxxxxxxxxxxxxxxxxx ``` **Publishable Key** (session creation only) — include in the `X-Api-Key` header: ```http X-Api-Key: pk_live_xxxxxxxxxxxxxxxxxxxx ``` > **Security rule:** Never expose `sk_`, `mk_` keys in frontend code, client-side JavaScript, or mobile apps. Only `pk_` keys are safe for browser use. Use sessions to proxy try-on requests from your frontend. --- ## Error Responses All errors return a JSON body with `errorCode` and `message`. **Standard error:** ```json { "errorCode": "UNAUTHORIZED", "message": "Tenant context not found." } ``` **Credit exhaustion (HTTP 402):** ```json { "errorCode": "CREDITS_EXHAUSTED", "message": "No credits remaining. Please add credits to continue.", "currentBalance": 0 } ``` **Spending limit exceeded (HTTP 402):** ```json { "errorCode": "SPENDING_LIMIT_EXCEEDED", "message": "Spending limit exceeded.", "currentSpending": 1500.00, "limit": 1000.00 } ``` ### Error Code Reference | HTTP | Error Code | Cause | |------|-----------|-------| | 400 | `MISSING_PERSON_IMAGE` | `PersonImage` field is required | | 400 | `MISSING_GARMENT_IMAGE` | `GarmentImage` field is required | | 400 | `INVALID_IMAGE_FORMAT` | Only JPEG and PNG are accepted | | 400 | `INVALID_AMOUNT` | Credit amount must be a positive integer | | 400 | `INVALID_PACKAGE` | `packageId` is required | | 400 | `INVALID_KEY_TYPE` | Key type must be `sk` or `pk` | | 400 | `INVALID_VALUE` | A settings value is out of its allowed range | | 400 | `TIER_UNAVAILABLE` | The requested `Quality` tier is currently disabled; the message lists the available tiers | | 401 | `UNAUTHORIZED` | Missing or invalid API key / session token | | 402 | `CREDITS_EXHAUSTED` | No credits remaining | | 402 | `NO_PAYMENT_METHOD` | No saved payment method on file | | 402 | `CHARGE_FAILED` | Payment charge failed | | 403 | `ACCESS_DENIED` | Operation requires a higher-privilege key | | 403 | `ORIGIN_NOT_ALLOWED` | `Origin` header not in tenant's allowed origins | | 403 | `CAPTCHA_REQUIRED` | `X-Captcha-Token` header missing | | 403 | `CAPTCHA_FAILED` | CAPTCHA verification failed | | 404 | `JOB_NOT_FOUND` | Job does not exist or belongs to another tenant | | 404 | `TENANT_NOT_FOUND` | Tenant does not exist | | 429 | `RATE_LIMITED` | Too many requests — wait and retry | | 429 | `DAILY_PUBLIC_LIMIT_REACHED` | Daily public credit cap exhausted for this tenant | | 503 | `SERVICE_UNAVAILABLE` | Temporary backend issue — retry after `Retry-After` seconds | --- ## Rate Limits | Scope | Limit | Notes | |-------|-------|-------| | Session creation per IP | 10 / minute | Global, non-configurable | | Session creation per tenant | 100 / minute | Configurable via `PUT /tenant/settings` (10–1,000) | | Daily public credit cap | 500 credits/day | Configurable via `PUT /tenant/settings` (10–10,000) | When a rate limit is hit, the response is HTTP 429. There is no `Retry-After` header on rate limit responses; wait at least 60 seconds before retrying. --- ## Try-On ### `POST /tryon` Submit a virtual try-on job. Returns immediately with a job ID. Poll `GET /jobs/{jobId}` to check for the result. **Authentication:** Secret key (`sk_`) or session token (`sess_*`) **Content-Type:** `multipart/form-data` **Max request size:** 25 MB #### Request Fields | Field | Type | Required | Description | |-------|------|----------|-------------| | `PersonImage` | File (JPEG/PNG) | Yes | Photo of the person to wear the garment | | `GarmentImage` | File (JPEG/PNG) | Yes | Photo of the garment | | `Quality` | string | No | `Standard`, `Enhanced`, or `Premium`. Defaults to the first available tier (normally `Standard`). Tiers can be disabled by the operator — requesting a disabled tier returns `400 TIER_UNAVAILABLE`. | | `IdempotencyKey` | string | No | If provided and a job with this key already exists for the tenant, the existing job is returned without creating a new one | | `BodyMeasurements` | JSON string | No | Body measurements in cm for fit analysis. See [Fit Analysis](#fit-analysis) | | `GarmentMeasurements` | JSON string | No | Garment measurements in cm for fit analysis. See [Fit Analysis](#fit-analysis) | #### Response — `202 Accepted` ```json { "jobId": "3f1a2b4c-8d6e-4f2a-9b1c-0e7d5f3a2b1c", "status": "Pending" } ``` #### Error Responses | HTTP | Error Code | Condition | |------|-----------|-----------| | 400 | `MISSING_PERSON_IMAGE` | `PersonImage` not provided | | 400 | `MISSING_GARMENT_IMAGE` | `GarmentImage` not provided | | 400 | `INVALID_IMAGE_FORMAT` | File is not JPEG or PNG | | 400 | `TIER_UNAVAILABLE` | Requested `Quality` tier is disabled; message lists available tiers | | 400 | `INVALID_BODY_MEASUREMENTS` | `BodyMeasurements` is not valid JSON | | 400 | `INVALID_GARMENT_MEASUREMENTS` | `GarmentMeasurements` is not valid JSON | | 401 | `UNAUTHORIZED` | Missing or invalid key / session | | 402 | `CREDITS_EXHAUSTED` | Credit balance is zero | | 402 | `SPENDING_LIMIT_EXCEEDED` | Monthly spending limit reached | | 503 | `SERVICE_UNAVAILABLE` | Credit check or blob storage unavailable — retry after `Retry-After` seconds | #### Notes - The credit pre-check happens before any image processing. If credits are insufficient the job is never created. - Spending limit checks are non-blocking: if the spending service is down the request proceeds. - If `IdempotencyKey` matches an existing job, the existing `jobId` and `status` are returned with `202 Accepted` and no new job is created. - Output image URLs are valid for **24 hours** from the time the job completes. #### Example ```bash curl -X POST https://vton.kinvyt.com/tryon \ -H "X-Api-Key: sk_live_xxxx" \ -F "PersonImage=@person.jpg" \ -F "GarmentImage=@shirt.jpg" \ -F "Quality=Standard" \ -F "IdempotencyKey=order_123_attempt_1" ``` #### Example with Fit Analysis ```bash curl -X POST https://vton.kinvyt.com/tryon \ -H "X-Api-Key: sk_live_xxxx" \ -F "PersonImage=@person.jpg" \ -F "GarmentImage=@shirt.jpg" \ -F "Quality=Premium" \ -F 'BodyMeasurements={"chestCm":96,"waistCm":82,"hipsCm":98,"shoulderCm":44,"sleeveCm":64}' \ -F 'GarmentMeasurements={"sizeLabel":"M","garmentType":"top","chestCm":104,"waistCm":90,"shoulderCm":46,"sleeveCm":65}' ``` --- ## Jobs ### `GET /jobs/{jobId}` Poll a try-on job to check its status and retrieve the output URL when complete. **Authentication:** Secret key (`sk_`) or session token (`sess_*`) #### Path Parameters | Parameter | Description | |-----------|-------------| | `jobId` | The job ID returned by `POST /tryon` | #### Response — `200 OK` ```json { "jobId": "3f1a2b4c-8d6e-4f2a-9b1c-0e7d5f3a2b1c", "status": "Completed", "quality": "Standard", "outputImageUrl": "https://storage.blob.core.windows.net/output/tenant/job/result.png?sv=...", "outputImageUrls": [ "https://storage.blob.core.windows.net/output/tenant/job/result.png?sv=..." ], "errorMessage": null, "createdAt": "2024-01-15T10:30:00Z", "updatedAt": "2024-01-15T10:30:08Z", "processingDurationMs": 7500, "creditsUsed": 15, "fitAnalysis": null, "bodyMeasurements": null, "garmentMeasurements": null } ``` #### Response with Fit Analysis When both `BodyMeasurements` and `GarmentMeasurements` were provided in the `POST /tryon` request, the response includes fit analysis data: ```json { "jobId": "3f1a2b4c-8d6e-4f2a-9b1c-0e7d5f3a2b1c", "status": "Completed", "quality": "Premium", "outputImageUrl": "https://storage.blob.core.windows.net/output/tenant/job/result.png?sv=...", "outputImageUrls": ["..."], "errorMessage": null, "createdAt": "2024-01-15T10:30:00Z", "updatedAt": "2024-01-15T10:31:15Z", "processingDurationMs": 72000, "creditsUsed": 45, "fitAnalysis": { "fitScore": 0.82, "fitCategory": "Regular", "sizeRecommendation": "Good fit with size M.", "ease": { "chestEaseCm": 8.0, "waistEaseCm": 8.0, "hipsEaseCm": null, "shoulderEaseCm": 2.0, "sleeveEaseCm": 1.0, "lengthEaseCm": null }, "fitNotes": [ "Chest has 8cm of ease — regular fit", "Waist has 8cm of ease — regular fit", "Shoulders has 2cm of ease — fitted", "Sleeve length has 1cm of ease — very close fit" ], "confidenceScore": 0.85 }, "bodyMeasurements": { "heightCm": null, "chestCm": 96, "waistCm": 82, "hipsCm": 98, "shoulderCm": 44, "sleeveCm": 64, "inseamCm": null, "neckCm": null, "estimationSource": null, "confidenceScore": null }, "garmentMeasurements": { "sizeLabel": "M", "garmentType": "top", "chestCm": 104, "waistCm": 90, "hipsCm": null, "lengthCm": null, "sleeveCm": 65, "shoulderCm": 46, "inseamCm": null } } ``` #### Job Status Values | Status | Meaning | |--------|---------| | `Pending` | Queued, waiting to be picked up | | `Processing` | Being processed by the AI provider | | `Completed` | Finished — `outputImageUrl` is populated | | `Failed` | Failed — `errorMessage` contains the reason. No credits are deducted | #### Error Responses | HTTP | Error Code | Condition | |------|-----------|-----------| | 401 | `UNAUTHORIZED` | Missing or invalid key | | 404 | `JOB_NOT_FOUND` | Job does not exist or belongs to a different tenant | #### Polling Strategy - Poll every **2–3 seconds** - `Standard` quality: typically completes in **5–8 seconds** - `Enhanced` / `Premium` quality: may take up to **2 minutes** - Stop polling once status is `Completed` or `Failed` - `outputImageUrl` is a SAS URL valid for **24 hours** #### Example ```bash curl https://vton.kinvyt.com/jobs/3f1a2b4c-8d6e-4f2a-9b1c-0e7d5f3a2b1c \ -H "X-Api-Key: sk_live_xxxx" ``` --- ## Sessions ### `POST /sessions` Create a short-lived, credit-capped session token for safe use in client-side code (browser / mobile app). The session token can then call `POST /tryon` and `GET /jobs/{jobId}` without exposing your secret key. **Authentication:** Publishable key (`pk_`) in `X-Api-Key` header **Content-Type:** `multipart/form-data` or `application/x-www-form-urlencoded` (body may be empty) #### Required Headers | Header | Description | |--------|-------------| | `X-Api-Key` | Your publishable key (`pk_*`) | | `Origin` | Must match one of the tenant's configured `allowedOrigins` (validated if origins are configured) | | `X-Captcha-Token` | A valid Cloudflare Turnstile token. Required unless CAPTCHA is disabled for your tenant | #### Response — `201 Created` ```json { "sessionId": "a1b2c3d4e5f6g7h8", "sessionToken": "sess_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "expiresAt": "2024-01-15T10:35:00Z", "allowedCredits": 3, "remainingCredits": 3 } ``` Use the returned `sessionToken` as: ```http Authorization: Bearer sess_xxxxxxxxxxxx ``` #### Error Responses | HTTP | Error Code | Condition | |------|-----------|-----------| | 401 | `UNAUTHORIZED` | Key is not a publishable key (`pk_`) | | 402 | `CREDITS_EXHAUSTED` | Tenant credit balance is zero | | 403 | `ORIGIN_NOT_ALLOWED` | `Origin` header not in the tenant's `allowedOrigins` list | | 403 | `CAPTCHA_REQUIRED` | `X-Captcha-Token` header is missing | | 403 | `CAPTCHA_FAILED` | Cloudflare Turnstile verification failed | | 429 | `RATE_LIMITED` | Per-IP (10/min) or per-tenant rate limit exceeded | | 429 | `DAILY_PUBLIC_LIMIT_REACHED` | Tenant's daily public credit cap reached | #### Session Limits (Configurable per Tenant) | Setting | Default | Range | Description | |---------|---------|-------|-------------| | `sessionCreditCap` | 3 | 1–20 | Max credits a session token may consume | | `sessionTtlSeconds` | 300 | 120–600 | Session lifetime in seconds (5 min default) | | `dailyPublicCreditCap` | 500 | 10–10,000 | Max credits consumed via sessions per day | | `maxSessionsPerMinute` | 100 | 10–1,000 | Max session creation requests per minute per tenant | A session expires when: - Its TTL elapses - Its credit cap is exhausted - All sessions are revoked via `POST /tenant/sessions/revoke-all` #### Example ```javascript // Browser — create session using pk_ key const res = await fetch('https://vton.kinvyt.com/sessions', { method: 'POST', headers: { 'X-Api-Key': 'pk_live_xxxx', 'X-Captcha-Token': turnstileToken, 'Origin': window.location.origin } }); const { sessionToken } = await res.json(); // Then use sessionToken to submit try-on const formData = new FormData(); formData.append('PersonImage', personImageFile); formData.append('GarmentImage', garmentImageFile); await fetch('https://vton.kinvyt.com/tryon', { method: 'POST', headers: { 'Authorization': `Bearer ${sessionToken}` }, body: formData }); ``` --- ## Credits ### `GET /credits/status` Get the current credit balance with provider cost breakdowns and remaining job estimates. **Authentication:** Secret key (`sk_`) or master key (`mk_`) #### Response — `200 OK` ```json { "tenantId": "your-tenant-id", "creditBalance": 250, "totalCreditsUsed": 1500, "providerCosts": [ { "providerName": "fashn", "totalCost": 15.00, "jobCount": 3 } ], "remainingEstimates": [ { "quality": "Standard", "costPerJob": 15, "estimatedJobs": 16 }, { "quality": "Enhanced", "costPerJob": 25, "estimatedJobs": 10 }, { "quality": "Premium", "costPerJob": 45, "estimatedJobs": 5 } ], "availablePackages": null } ``` > When `creditBalance` is `0`, `availablePackages` is populated with purchasable packages instead of `null`. --- ### `GET /credits/history` Get the credit transaction history for your tenant in reverse-chronological order. **Authentication:** Secret key (`sk_`) or master key (`mk_`) #### Query Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `limit` | integer | No | Maximum number of entries to return. Omit for all entries | #### Response — `200 OK` ```json [ { "transactionType": "deduction", "creditAmount": -15, "resultingBalance": 235, "description": "Job deduction for job abc123 (Standard quality)" }, { "transactionType": "purchase", "creditAmount": 500, "resultingBalance": 250, "description": "Credit purchase — 500 credits" }, { "transactionType": "admin_adjustment", "creditAmount": 100, "resultingBalance": 100, "description": "Trial grant" } ] ``` #### Transaction Types | Type | Description | |------|-------------| | `purchase` | Credits added via Razorpay payment | | `deduction` | Credits consumed by a completed try-on job | | `admin_adjustment` | Credits added or adjusted by the system admin | | `trial_grant` | Credits given as a free trial or bonus | --- ### `GET /credits/packages` List all active credit packages available for purchase. **Authentication:** Secret key (`sk_`) or master key (`mk_`) #### Response — `200 OK` ```json [ { "packageId": "pack_500", "credits": 500, "priceInSmallestUnit": 49900, "currency": "INR", "isActive": true }, { "packageId": "pack_1000", "credits": 1000, "priceInSmallestUnit": 89900, "currency": "INR", "isActive": true } ] ``` > `priceInSmallestUnit` is in paise for INR (divide by 100 to get rupees). The value `49900` = ₹499. --- ### `GET /credits/payment-method` Check whether a payment method has been saved for automated credit purchases. **Authentication:** Secret key (`sk_`) or master key (`mk_`) #### Response — `200 OK` ```json { "hasSavedPaymentMethod": true, "displayInfo": "Visa •••• 4242" } ``` > `displayInfo` is `null` when `hasSavedPaymentMethod` is `false`. --- ### `POST /credits/purchase` Charge the tenant's saved payment method to purchase a credit package. No browser or checkout UI is required — this is a server-to-server charge. **Prerequisites:** A payment method must first be saved via the **dashboard**. Use `GET /credits/payment-method` to confirm. **Authentication:** Secret key (`sk_`) or master key (`mk_`) **Content-Type:** `application/json` #### Request Body ```json { "packageId": "pack_500" } ``` | Field | Type | Required | Description | |-------|------|----------|-------------| | `packageId` | string | Yes | The ID of the package to purchase (from `GET /credits/packages`) | #### Response — `200 OK` ```json { "orderId": "order_OLkAbcd1234", "paymentId": "pay_OLkAbcd1234", "packageId": "pack_500", "status": "processing", "message": "Payment initiated. Credits will be added once the payment is confirmed." } ``` Credits are **not** added immediately. They are added asynchronously when the payment captured webhook fires from Razorpay. #### Error Responses | HTTP | Error Code | Condition | |------|-----------|-----------| | 400 | `INVALID_PACKAGE` | `packageId` is missing or empty | | 401 | `UNAUTHORIZED` | Missing or invalid key | | 402 | `NO_PAYMENT_METHOD` | No saved payment method — save one via the dashboard first | | 402 | `CHARGE_FAILED` | Payment charge was declined or failed | | 404 | `TENANT_NOT_FOUND` | Tenant account not found | --- ## Spending ### `GET /spending/status` Get your spending status for the current billing cycle. `effectiveRemaining` is the real number of credits you can use — the minimum of your credit balance and your remaining budget. **Authentication:** Secret key (`sk_`) #### Response — `200 OK` ```json { "tenantId": "your-tenant-id", "cycleStart": "2024-01-01T00:00:00Z", "cycleEnd": "2024-02-01T00:00:00Z", "currentSpending": 150.00, "spendingLimit": 1000.00, "remainingBudget": 850.00, "percentageUsed": 15.0, "creditBalance": 250, "effectiveRemaining": 250 } ``` | Field | Description | |-------|-------------| | `spendingLimit` | Your configured monthly spending cap. `null` if no limit is set | | `remainingBudget` | `spendingLimit - currentSpending`. `null` if no limit is set | | `effectiveRemaining` | `min(creditBalance, remainingBudget)` — how many credits you can actually use | --- ## Usage & Billing ### `GET /usage` Get usage aggregates for your tenant within a date range. **Authentication:** Secret key (`sk_`) #### Query Parameters | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `from` | `yyyy-MM-dd` | No | 30 days ago | Start date (UTC) | | `to` | `yyyy-MM-dd` | No | Today | End date (UTC) | | `granularity` | string | No | `daily` | `daily` or `monthly` | #### Response — `200 OK` ```json { "tenantId": "your-tenant-id", "records": [ { "period": "2024-01-15", "requestCount": 25, "successCount": 24, "failureCount": 1, "totalProcessingTimeMs": 187500, "totalCost": 375 } ] } ``` > For `monthly` granularity, `period` is formatted as `2024-01`. --- ### `GET /usage/billing` Get billing totals broken down by period. **Authentication:** Secret key (`sk_`) #### Query Parameters | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `from` | `yyyy-MM-dd` | No | 30 days ago | Start date | | `to` | `yyyy-MM-dd` | No | Today | End date | | `period` | string | No | `monthly` | `daily` or `monthly` | #### Response — `200 OK` ```json { "tenantId": "your-tenant-id", "fromDate": "2024-01-01", "toDate": "2024-01-31", "totalCost": 1500, "lineItems": [ { "period": "2024-01", "jobCount": 100, "cost": 1500 } ] } ``` --- ## Tenant Management These endpoints require your **master key** (`mk_`). They allow you to manage your own tenant configuration. --- ### `POST /tenant/rotate-key` Rotate your secret key (`sk_`) or publishable key (`pk_`). The old key is immediately invalidated. The new key is returned once in plain text — store it securely. **Authentication:** Master key (`mk_`) #### Query Parameters | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `type` | string | No | `sk` | `sk` (secret key) or `pk` (publishable key) | #### Response — `200 OK` ```json { "tenantId": "your-tenant-id", "keyType": "secret", "key": "sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "keyPrefix": "sk_live_XXXX" } ``` > The new key is shown only once. Copy and store it securely immediately. #### Error Responses | HTTP | Error Code | Condition | |------|-----------|-----------| | 400 | `INVALID_KEY_TYPE` | `type` is not `sk` or `pk` | | 403 | `ACCESS_DENIED` | Not authenticated with a master key | | 404 | `TENANT_NOT_FOUND` | Tenant account not found | --- ### `GET /tenant/settings` View your current session configuration, allowed origins, and key prefixes. **Authentication:** Master key (`mk_`) #### Response — `200 OK` ```json { "tenantId": "your-tenant-id", "tenantName": "Acme Fashion", "allowedOrigins": ["https://acme.com", "https://app.acme.com"], "sessionCreditCap": 3, "sessionTtlSeconds": 300, "dailyPublicCreditCap": 500, "maxSessionsPerMinute": 100, "requireCaptcha": true, "apiKeyPrefix": "sk_live_XXXX", "publishableKeyPrefix": "pk_live_XXXX" } ``` --- ### `PUT /tenant/settings` Update your session configuration and allowed origins. All fields are optional — only provided fields are updated. **Authentication:** Master key (`mk_`) **Content-Type:** `application/json` #### Request Body (all fields optional) ```json { "allowedOrigins": ["https://acme.com", "https://app.acme.com"], "sessionCreditCap": 3, "sessionTtlSeconds": 300, "dailyPublicCreditCap": 500, "maxSessionsPerMinute": 100, "requireCaptcha": true } ``` #### Field Constraints | Field | Type | Range | Description | |-------|------|-------|-------------| | `allowedOrigins` | string[] | — | Origins allowed to call `POST /sessions`. Empty array disables origin validation | | `sessionCreditCap` | integer | 1–20 | Max credits per session token | | `sessionTtlSeconds` | integer | 120–600 | Session lifetime in seconds | | `dailyPublicCreditCap` | integer | 10–10,000 | Max credits consumed via sessions per calendar day (UTC) | | `maxSessionsPerMinute` | integer | 10–1,000 | Max `POST /sessions` requests per minute for your tenant | | `requireCaptcha` | boolean | — | Whether Cloudflare Turnstile CAPTCHA is required for session creation | #### Response — `200 OK` ```json { "message": "Settings updated successfully." } ``` #### Error Responses | HTTP | Error Code | Condition | |------|-----------|-----------| | 400 | `INVALID_VALUE` | A field value is outside its allowed range | | 403 | `ACCESS_DENIED` | Not authenticated with a master key | | 404 | `TENANT_NOT_FOUND` | Tenant account not found | --- ### `POST /tenant/sessions/revoke-all` Immediately revoke all active session tokens for your tenant. Any in-flight requests using those tokens will receive HTTP 401. **Authentication:** Master key (`mk_`) #### Response — `200 OK` ```json { "message": "All active sessions have been revoked." } ``` --- ## Webhooks ### `POST /webhooks/razorpay` Receives payment event callbacks from Razorpay. This endpoint is called directly by Razorpay — your backend does not need to call it. It is documented here for completeness and for verifying the signature in your own logging or testing flows. **Authentication:** HMAC-SHA256 signature verification. The `X-Razorpay-Signature` header must equal `HMAC-SHA256(raw_request_body, webhook_secret)` in hex. > The raw request body must be used for verification — do not parse the body before verifying the signature. #### Required Headers | Header | Description | |--------|-------------| | `X-Razorpay-Signature` | `HMAC-SHA256(raw_body, webhook_secret)` in lowercase hex | | `x-razorpay-event-id` | Unique event ID for idempotency (provided by Razorpay) | | `Content-Type` | `application/json` | #### Processed Event Types | Event | Action | |-------|--------| | `payment.captured` | **Primary credit trigger.** Verifies payment signature, adds credits to tenant, marks order as paid | | `order.paid` | Backup credit trigger. Same as `payment.captured` — idempotent via ledger | | `payment.failed` | Marks payment order as `failed`. Does **not** affect credit balance | | `token.confirmed` | Saves the tenant's recurring payment token for future API charges | All other event types are acknowledged with HTTP 200 and ignored. #### Idempotency Each event is checked against the `x-razorpay-event-id`. Duplicate deliveries with the same event ID are skipped. Credit additions are also idempotent at the ledger level — the same payment cannot add credits twice regardless of how many times the webhook fires. #### Response — `200 OK` Always returns HTTP 200. Returning non-2xx would cause Razorpay to retry for 24 hours. #### Signature Validation Failure Returns HTTP 400 if `X-Razorpay-Signature` is missing or invalid. --- ## Appendix ### Credit Costs by Quality | Quality | Credits per Job | |---------|----------------| | Standard | 15 | | Enhanced | 25 | | Premium | 45 | > Failed jobs are never charged credits. ### Key Prefix Reference | Prefix | Key Type | |--------|----------| | `sk_live_` | Secret key (production) | | `mk_live_` | Master key (production) | | `pk_live_` | Publishable key (production) | | `sess_` | Session token | ### Authentication Quick Reference | Operation | Required Key | |-----------|-------------| | Submit try-on job | `sk_` or `sess_*` | | Poll job status | `sk_` or `sess_*` | | Create session token | `pk_` | | Read credit balance / history | `sk_` or `mk_` | | Purchase credits | `sk_` or `mk_` | | Read spending status | `sk_` | | Read usage / billing | `sk_` | | Rotate keys | `mk_` | | View / update settings | `mk_` | | Revoke all sessions | `mk_` | ### Payment Flow (Dashboard) Credit purchases via the dashboard follow this flow — no API endpoints are involved for the checkout itself: 1. **Dashboard** → calls Razorpay to create a payment order 2. **Client-side** → Razorpay Checkout UI completes the payment 3. **Dashboard** → verifies payment and adds credits 4. **Webhook** (`/webhooks/razorpay`) → backup credit addition if step 3 fails For **automated / server-to-server** purchases after a payment method is saved: 1. `POST /credits/purchase` → charges the saved card/UPI via Razorpay recurring 2. **Webhook** (`payment.captured`) → adds credits asynchronously ### Fit Analysis When both `BodyMeasurements` and `GarmentMeasurements` are provided in a `POST /tryon` request, the API computes a fit analysis and includes it in the `GET /jobs/{jobId}` response. Fit analysis is included at no extra credit cost. #### BodyMeasurements Schema All fields are optional. Provide what you have — more measurements yield higher confidence. | Field | Type | Description | |-------|------|-------------| | `heightCm` | number | Full height in cm | | `chestCm` | number | Bust/chest circumference in cm | | `waistCm` | number | Natural waist circumference in cm | | `hipsCm` | number | Hip circumference in cm (widest point) | | `shoulderCm` | number | Shoulder width in cm (seam to seam) | | `sleeveCm` | number | Arm length in cm (shoulder to wrist) | | `inseamCm` | number | Inseam length in cm (for bottoms) | | `neckCm` | number | Neck circumference in cm | #### GarmentMeasurements Schema All fields are optional. These are the physical garment measurements (not the body size the garment is designed for). | Field | Type | Description | |-------|------|-------------| | `sizeLabel` | string | Size label as displayed to shoppers (e.g. "S", "M", "L", "42") | | `garmentType` | string | `top`, `bottom`, `dress`, or `outerwear` | | `chestCm` | number | Full chest circumference in cm | | `waistCm` | number | Full waist circumference in cm | | `hipsCm` | number | Full hip circumference in cm | | `lengthCm` | number | Total garment length in cm | | `sleeveCm` | number | Sleeve length in cm | | `shoulderCm` | number | Shoulder width in cm (seam to seam) | | `inseamCm` | number | Inseam length in cm (for pants/bottoms) | #### FitCategory Values | Category | Ease Range | Description | |----------|-----------|-------------| | `VeryTight` | < 0 cm | Garment is smaller than the body | | `Tight` | 0–2 cm | Very close fit, minimal room | | `Fitted` | 2–6 cm | Follows body shape with some room | | `Regular` | 6–12 cm | Comfortable standard fit | | `Relaxed` | 12–18 cm | Noticeably loose | | `Oversized` | 18+ cm | Intentionally oversized | #### How Fit Score Works The fit score (0.0–1.0) measures how close the garment's ease is to the ideal for its type. A score of 1.0 means the ease matches the ideal perfectly. The ideal ease varies by garment type: tops target ~6cm, bottoms ~4cm, outerwear ~12cm. #### Prompt-Based Visual Hints For **Premium** quality jobs with fit analysis, the API sends a fit-based prompt hint to the rendering provider (e.g. "oversized garment, loose baggy draping" for oversized fits). This is a best-effort visual influence — the rendering is approximate, not physically accurate. The fit metadata (score, category, ease values) is the authoritative fit information. ### Frontend Integration Flow The recommended flow for integrating try-on into a frontend application: 1. **Backend** → create a session token server-side using your `sk_` key, or expose a thin endpoint that calls `POST /sessions` with your `pk_` key 2. **Frontend** → receive the `sessionToken` from your backend 3. **Frontend** → call `POST /tryon` using `Authorization: Bearer sess_...` 4. **Frontend** → poll `GET /jobs/{jobId}` every 2–3 seconds until status is `Completed` or `Failed` 5. **Frontend** → display `outputImageUrl` This keeps your `sk_` and `mk_` keys entirely server-side while letting your UI submit try-on requests directly.