Skip to content

API documentation

A JSON REST API for the marketplace, plus the licence verification endpoints authors embed in their own software. Every response uses the same envelope, and result codes are a stable contract — you can branch on them safely.

Base URL

https://mitrait.in/api/v1

Licence verification

The endpoints you embed in your own software. They are unauthenticated by design — a deployed customer application holds a purchase code, not a marketplace token — and are rate limited per IP and per licence key.

POST /license/verify

Check a licence without changing activation state.

Request body
{
    "license_key": "SMKT-8F72-29KD-4B1P",
    "product_id": 12,
    "domain": "customer-site.com"
}
POST /license/activate

Bind a licence to an installation. Re-activating the same host is free.

Request body
{
    "license_key": "SMKT-8F72-29KD-4B1P",
    "domain": "customer-site.com",
    "environment": "production"
}
POST /license/deactivate

Release an installation and return its activation slot.

Request body
{
    "license_key": "SMKT-8F72-29KD-4B1P",
    "domain": "customer-site.com"
}
POST /license/status

Current status and entitlement windows for a licence.

Request body
{
    "license_key": "SMKT-8F72-29KD-4B1P"
}
GET /license/{key}/updates

Whether a newer version is available to this licence holder.

Catalogue (public)

Read-only catalogue access. No token required.

GET /products

List published products. Supports q, category, type, framework, free, author, min_rating, sort, per_page.

GET /products/{slug}

Full detail for one product, including licence options.

GET /categories

The full category tree with product counts.

GET /categories/{slug}

One category with its products.

GET /authors

List approved authors.

GET /authors/{slug}

One author with their catalogue.

Account (token required)

Send your token as Authorization: Bearer <token>. Create tokens under Account → API tokens.

POST /auth/token

Exchange credentials for a token. Blocked for accounts with 2FA — create those from the account settings screen.

Request body
{
    "email": "you@example.com",
    "password": "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",
    "device_name": "ci-pipeline"
}
GET /auth/me

Who this token belongs to, and what it can do.

GET /me/purchases

Your purchase history.

GET /me/licenses

Your licences, with entitlement windows and activation counts.

GET /me/downloads

Your download history.

GET /downloads/{file}/url

Mint a short-lived signed download URL after an entitlement check.

GET /wallet

Wallet balances.

GET /wallet/transactions

Wallet statement, with the correlation reference on every row.

Author (token required)

Available to tokens belonging to an approved author.

GET /seller/products

Your catalogue with status and stats.

GET /seller/sales

Your sales, with the commission split on each.

GET /seller/earnings

Balances, lifetime earnings and withdrawal eligibility.

Result codes

Licence endpoints answer 200 whenever the request was understood — including when the verdict is "invalid". Branch on code, not on the HTTP status, so you can tell "this licence is not valid" apart from "my integration is broken".

CodeMeaning
valid The licence is valid and, where applicable, activated for this installation.
invalid The key is not recognised, or does not belong to the product supplied.
expired The licence has passed its expiry date.
revoked The licence was revoked, refunded or suspended.
activation_limit_reached No activation slots remain. The holder must deactivate an installation first.
domain_mismatch This installation is not activated for the supplied licence.
product_disabled The marketplace has disabled this product.
not_activated The product requires activation and no identifier was supplied.
activation_not_required This product does not use activation; the key alone is sufficient.

Response envelope

{
    "success": true,
    "code": "valid",
    "message": "License is valid.",
    "data": {
        "license_key": "SMKT-8F72-29KD-4B1P",
        "product": {
            "id": 12,
            "name": "Android Recharge App",
            "version": "2.1.0"
        },
        "license_type": "Regular License",
        "status": "active",
        "buyer": {
            "name": "Anita Sharma",
            "email": "anita@example.com"
        },
        "purchased_version": "2.0.0",
        "support_active": true,
        "supported_until": "2026-08-08T00:00:00+05:30",
        "updates_active": true,
        "updates_until": null,
        "activation_mode": "domain",
        "activation_limit": 1,
        "activations_used": 1,
        "activations_remaining": 0
    }
}

A null in updates_until or supported_until means lifetime, not "unknown".

Integration examples

Verify on install and then periodically — not on every request. Cache the result and fail open on a network error, so a marketplace outage never takes your customer's site down.

<?php

function verifyLicense(string $key, string $domain): array
{
    $ch = curl_init('https://mitrait.in/api/v1/license/verify');

    curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 10,
        CURLOPT_HTTPHEADER     => ['Content-Type: application/json', 'Accept: application/json'],
        CURLOPT_POSTFIELDS     => json_encode([
            'license_key' => $key,
            'domain'      => $domain,
        ]),
    ]);

    $body = curl_exec($ch);
    $error = curl_error($ch);
    curl_close($ch);

    // Fail open on a network problem: an outage on our side must not
    // take the customer's installation offline.
    if ($error || $body === false) {
        return ['success' => true, 'code' => 'offline_grace', 'message' => $error];
    }

    return json_decode($body, true) ?? ['success' => false, 'code' => 'invalid'];
}

$result = verifyLicense(getenv('LICENSE_KEY'), $_SERVER['HTTP_HOST']);

if (! $result['success']) {
    exit('Licence problem: ' . $result['message']);
}
const BASE = 'https://mitrait.in/api/v1';

export async function verifyLicense(key, domain) {
  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), 10_000);

  try {
    const response = await fetch(`${BASE}/license/verify`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
      body: JSON.stringify({ license_key: key, domain }),
      signal: controller.signal,
    });

    return await response.json();
  } catch (error) {
    // Fail open — never hard-block on a transient network fault.
    return { success: true, code: 'offline_grace', message: error.message };
  } finally {
    clearTimeout(timeout);
  }
}
# Verify
curl -X POST https://mitrait.in/api/v1/license/verify \
  -H "Content-Type: application/json" \
  -d '{"license_key":"SMKT-8F72-29KD-4B1P","domain":"customer-site.com"}'

# Activate
curl -X POST https://mitrait.in/api/v1/license/activate \
  -H "Content-Type: application/json" \
  -d '{"license_key":"SMKT-8F72-29KD-4B1P","domain":"customer-site.com"}'

# Authenticated call
curl https://mitrait.in/api/v1/me/licenses \
  -H "Authorization: Bearer mk_xxxxxxxx_yyyyyyyyyyyy" \
  -H "Accept: application/json"

Errors & rate limits

StatusWhen it happens
200 Request understood. Check the "code" field for the verdict.
401 Missing or invalid API token.
403 Token lacks the required ability, is IP-restricted, or the account is inactive.
404 No such resource, or it is not published.
422 Validation failed — the response lists the offending fields.
429 Rate limit exceeded. Back off and retry after the Retry-After header.
500 Something broke on our side. Retry with backoff; if it persists, contact support.

Rate limits

  • Licence endpoints: 120 requests per minute per IP, and 30 per minute per licence key.
  • Public catalogue: 60 requests per minute per IP.
  • Authenticated endpoints: 60 per minute by default, configurable per token.