License Verification API
License Verification API
When you sell software on 3DIMLI, each buyer receives a unique Order Item ID after purchase. This acts as their license key. Your software or license server can call 3DIMLI's verification API to confirm whether a key is legitimate and which license tier was purchased.
This page is for developers listing software on 3DIMLI who want to add license activation and verification to their products. For a step-by-step integration walkthrough, see the Software License Integration Guide.
How It Works
| Step | What happens | Who |
|---|---|---|
| 1 | Buyer purchases your software on 3DIMLI | Buyer |
| 2 | Buyer receives an Order Item ID (license key) via email and in their dashboard | 3DIMLI |
| 3 | Buyer enters the license key in your software | Buyer |
| 4 | Your app sends the key + your product slug to the verify endpoint | Your software |
| 5 | 3DIMLI responds: valid: true/false + productName + licenseName | 3DIMLI |
| 6 | Your software unlocks features based on the license tier, optionally enforces seat limits | Your software |
3DIMLI answers one question: "Was this key legitimately purchased?"
You handle everything else: feature gating based on licenseName, and optionally seat counts, device limits, and concurrent sessions on your own server.
API Reference
Endpoint
https://www.3dimli.com/api/software/v1/verify
No authentication required. The endpoint is public and rate-limited by IP.
Request Body
{
"key": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"product_slug": "/my-tool"
}
| Field | Type | Required | Description |
|---|---|---|---|
key | string (UUID) | Yes | The buyer's Order Item ID (their license key) |
product_slug | string | Yes | Your product's URL slug from the store (e.g., /my-tool) |
The key must be a valid UUID (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx). The product_slug must be a non-empty string.
The API normalizes the slug automatically. All of these are accepted: /my-tool, my-tool, software/my-tool.
Responses
- Valid License
- Invalid License
- Rate Limited
{
"valid": true,
"productName": "My Tool",
"licenseName": "Professional"
}
{
"valid": false
}
Retry-After: 45
X-RateLimit-Limit: 30
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 45
Response Fields
| Field | Type | Description |
|---|---|---|
valid | boolean | true if the key is a legitimate, active, non-refunded purchase |
productName | string | null | The product title on 3DIMLI (e.g., "My Tool") |
licenseName | string | null | The license tier name the buyer purchased (e.g., "Standard License", "Team License") |
When valid Returns false
A license is invalid if any of the following are true:
- The Order Item ID does not exist
- The order status is not
COMPLETED - The order has been refunded
- The
product_slugdoes not match the purchased product - The product is not a Software type
The API never leaks information about why a key is invalid. It always returns { "valid": false } with no additional details.
Validation Sequence
The API processes each request in this order:
| Step | Check | Failure response |
|---|---|---|
| 1 | Client IP present | 400 |
| 2 | Rate limit: 30 req / 60s / IP | 429 with Retry-After |
| 3 | key is a valid UUID | 400 |
| 4 | product_slug is a non-empty string | 400 |
| 5 | OrderItem exists with matching key AND product slug | { valid: false } |
| 6 | Order status is COMPLETED | { valid: false } |
| 7 | Order is not refunded | { valid: false } |
| 8 | Product type is Software | { valid: false } |
Steps 5–8 always return identical { valid: false } — no information is leaked about why it failed.
Status Codes
| Code | Meaning |
|---|---|
200 | Request processed. Check the valid field for the result |
400 | Missing or invalid fields (key must be a valid UUID, product_slug must be non-empty) |
429 | Rate limit exceeded. Check Retry-After header |
500 | Server error. Retry later |
Rate Limiting
| Limit | 30 requests per 60 seconds |
| Scope | Per IP address |
| Headers | Retry-After, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset |
If all your users' requests go through a single license server, those requests share one IP rate limit. Cache verification results on your server to stay within limits.
Finding Your Product Slug
Your product slug is the URL path of your product in the store. Find it in the browser address bar when viewing your product page:
https://www.3dimli.com/<your-product-slug>
└── product_slug ──┘
For example, if your product page is at https://www.3dimli.com/my-tool, your product slug is /my-tool.
The product slug is not a secret — it's visible in the store URL. It acts as a scope guard so a key purchased for Product A cannot be used to activate Product B.
Integration Examples
Quick Test
curl -X POST https://www.3dimli.com/api/software/v1/verify \
-H "Content-Type: application/json" \
-d '{
"key": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"product_slug": "/my-tool"
}'
Direct Integration (No License Server)
The simplest approach — call 3DIMLI directly from your software:
const PRODUCT_SLUG = "/my-tool"; // from your store URL, not a secret
async function activateLicense(userKey) {
const res = await fetch("https://www.3dimli.com/api/software/v1/verify", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ key: userKey, product_slug: PRODUCT_SLUG }),
});
const data = await res.json();
if (res.status === 429) {
showError("Too many attempts. Try again in " + res.headers.get("Retry-After") + "s");
return;
}
if (!data.valid) {
showError("Invalid license key.");
return;
}
// Unlock features based on tier
if (data.licenseName === "Enterprise") enableAllFeatures();
else if (data.licenseName === "Professional") enableProFeatures();
else enableBasicFeatures();
saveLicenseLocally(userKey); // cache so user doesn't re-enter every time
}
License Server with Seat Enforcement
A complete license server that verifies keys against 3DIMLI and enforces per-tier seat limits.
- Node.js
- Python
const express = require("express");
const app = express();
app.use(express.json());
// ── Config ──────────────────────────────────────────────
const PRODUCT_SLUG = "/my-tool";
const SEAT_LIMITS = {
"Standard License": 1,
"Team License": 5,
"Company License": 50,
"Enterprise License": -1, // unlimited
};
// ── Store (use a database in production) ────────────────
const activations = new Map(); // key -> { licenseName, devices: Set }
// ── Activate ────────────────────────────────────────────
app.post("/activate", async (req, res) => {
const { licenseKey, deviceId } = req.body;
if (!licenseKey || !deviceId) {
return res.status(400).json({ error: "licenseKey and deviceId required" });
}
// Verify with 3DIMLI
const response = await fetch(
"https://www.3dimli.com/api/software/v1/verify",
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ key: licenseKey, product_slug: PRODUCT_SLUG }),
}
);
const result = await response.json();
if (!result.valid) {
return res.status(403).json({ error: "Invalid license key" });
}
// Get or create activation record
let record = activations.get(licenseKey);
if (!record) {
record = { licenseName: result.licenseName, devices: new Set() };
activations.set(licenseKey, record);
}
// Already activated on this device
if (record.devices.has(deviceId)) {
return res.json({ activated: true, licenseName: record.licenseName });
}
// Enforce seat limit
const limit = SEAT_LIMITS[record.licenseName] ?? 1;
if (limit !== -1 && record.devices.size >= limit) {
return res.status(403).json({
error: `Seat limit reached (${limit}). Deactivate a device first.`,
});
}
record.devices.add(deviceId);
res.json({ activated: true, licenseName: record.licenseName });
});
// ── Deactivate ──────────────────────────────────────────
app.post("/deactivate", (req, res) => {
const { licenseKey, deviceId } = req.body;
const record = activations.get(licenseKey);
if (record) record.devices.delete(deviceId);
res.json({ deactivated: true });
});
app.listen(3000, () => console.log("License server on :3000"));
from flask import Flask, request, jsonify
import requests
app = Flask(__name__)
# ── Config ──────────────────────────────────────────────
PRODUCT_SLUG = "/my-tool"
SEAT_LIMITS = {
"Standard License": 1,
"Team License": 5,
"Company License": 50,
"Enterprise License": -1, # unlimited
}
# ── Store (use a database in production) ────────────────
activations = {} # key -> { "licenseName": str, "devices": set }
# ── Activate ────────────────────────────────────────────
@app.route("/activate", methods=["POST"])
def activate():
data = request.json
license_key = data.get("licenseKey")
device_id = data.get("deviceId")
if not license_key or not device_id:
return jsonify({"error": "licenseKey and deviceId required"}), 400
record = activations.get(license_key)
if not record:
# Verify with 3DIMLI
resp = requests.post(
"https://www.3dimli.com/api/software/v1/verify",
json={"key": license_key, "product_slug": PRODUCT_SLUG},
)
result = resp.json()
if not result.get("valid"):
return jsonify({"error": "Invalid license key"}), 403
record = {"licenseName": result.get("licenseName"), "devices": set()}
activations[license_key] = record
if device_id in record["devices"]:
return jsonify({"activated": True, "licenseName": record["licenseName"]})
limit = SEAT_LIMITS.get(record["licenseName"], 1)
if limit != -1 and len(record["devices"]) >= limit:
return jsonify({"error": f"Seat limit reached ({limit})"}), 403
record["devices"].add(device_id)
return jsonify({"activated": True, "licenseName": record["licenseName"]})
# ── Deactivate ──────────────────────────────────────────
@app.route("/deactivate", methods=["POST"])
def deactivate():
data = request.json
record = activations.get(data.get("licenseKey"))
if record:
record["devices"].discard(data.get("deviceId"))
return jsonify({"deactivated": True})
if __name__ == "__main__":
app.run(port=3000)
Client-Side Activation (In Your Software)
How the buyer's application calls your license server on startup:
- JavaScript
- Python
const os = require("os");
const crypto = require("crypto");
function getDeviceId() {
const raw = `${os.hostname()}-${os.platform()}-${os.cpus()[0]?.model}`;
return crypto.createHash("sha256").update(raw).digest("hex").slice(0, 32);
}
async function activate(licenseKey) {
const response = await fetch("https://your-license-server.com/activate", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ licenseKey, deviceId: getDeviceId() }),
});
const result = await response.json();
if (result.activated) {
// Save key locally so user doesn't re-enter it
saveToStorage("licenseKey", licenseKey);
unlockFeatures(result.licenseName);
return true;
}
showError(result.error);
return false;
}
import hashlib
import platform
import requests
def get_device_id():
raw = f"{platform.node()}-{platform.system()}-{platform.processor()}"
return hashlib.sha256(raw.encode()).hexdigest()[:32]
def activate(license_key):
response = requests.post(
"https://your-license-server.com/activate",
json={"licenseKey": license_key, "deviceId": get_device_id()},
)
result = response.json()
if result.get("activated"):
save_to_storage("licenseKey", license_key)
unlock_features(result["licenseName"])
return True
print(f"Activation failed: {result.get('error')}")
return False
Architecture
┌─────────────────────────────────┐
│ YOUR LICENSE SERVER │
│ │
┌───────────────┐ POST │ 1. Receive key + deviceId │ POST ┌──────────────┐
│ │ /activate│ 2. Call 3DIMLI verify API ───────────▶│ │
│ BUYER'S APP │─────────▶│ 3. Cache result │ │ 3DIMLI │
│ │ │ 4. Check seat limits │◀─────────│ API │
│ License key │◀─────────│ 5. Register device │ valid │ │
│ + device ID │ Result │ 6. Return activated/denied │ + name │ /verify │
└───────────────┘ │ │ └──────────────┘
│ YOUR RESPONSIBILITY: │
│ • Seat enforcement │
│ • Device tracking │
│ • Session management │
│ • Feature gating │
│ • Offline grace periods │
└─────────────────────────────────┘
Best Practices
Caching
Don't call the 3DIMLI API on every app launch. Cache the result on your server and re-verify periodically.
const PRODUCT_SLUG = "/my-tool";
const CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours
async function verifyWithCache(licenseKey) {
const cached = activations.get(licenseKey);
// Use cache if fresh
if (cached && Date.now() - cached.verifiedAt < CACHE_TTL) {
return cached;
}
// Re-verify with 3DIMLI
const res = await fetch("https://www.3dimli.com/api/software/v1/verify", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ key: licenseKey, product_slug: PRODUCT_SLUG }),
});
const result = await res.json();
if (!result.valid) {
activations.delete(licenseKey); // Key revoked (e.g., refund)
return null;
}
// Update cache timestamp
if (cached) cached.verifiedAt = Date.now();
return cached;
}
Offline & Refunds
- Offline grace period. If your server or 3DIMLI is unreachable, let the user continue for a set time (e.g., 7 days) before requiring re-verification.
- Refund handling. When a buyer gets a refund, 3DIMLI returns
valid: false. On your next re-verify cycle, revoke access gracefully with a clear message. Don't delete user data.
Device IDs
Generate stable, unique device identifiers using hardware fingerprints (hostname + OS + CPU). Store the ID locally so it persists across sessions.
Rate Limits
With 30 requests/minute per IP, a centralized server handling many users should cache aggressively. Batch or schedule re-verifications rather than verifying on every request.
FAQ
Where does the buyer find their license key?
The Order Item ID is available in:
- The purchase confirmation email from 3DIMLI
- Dashboard → Orders → expand order → License Product ID
- Dashboard → Downloads → license details → Item ID
All locations include a copy button for convenience.
What happens if a buyer gets a refund?
The API returns { "valid": false } for refunded orders. Your license server should handle this on the next re-verification by revoking access gracefully.
Do I need an API key?
No. The verification endpoint is public and rate-limited by IP address. No authentication tokens are required.
Can one key work across multiple products?
No. Each key (Order Item ID) is tied to a specific product. The product_slug in the request must match the product the buyer purchased.
What does licenseName return?
The exact name of the license tier the buyer selected during purchase, for example "Standard License", "Team License", or whatever custom names you defined when creating your product.
What does productName return?
The title of your product on 3DIMLI, for example "My Tool". This is included in valid responses so your software can display which product was activated.
What is product_slug and where do I find it?
The product slug is the URL path of your product in the 3DIMLI store. For example, if your product page is at https://www.3dimli.com/my-tool, the slug is /my-tool. It's not a secret — it prevents keys bought for one product from activating a different product.
Related
- Software Product Type. Creating and listing your software product
- Software License Integration Guide. Full step-by-step developer walkthrough
- Activate Software License (Buyers). Buyer-facing activation guide