ExisOne API

All requests must include your access token:

Authorization: ExisOneApi <access-token>

All API endpoints enforce tokens (no request is processed without a valid token).

Public Key

GET /api/crypto/keys/public

Returns the current RSA public key in PEM format.

Activation

POST /api/license/activate
{
  "activationKey": "AAAA-BBBB-CCCC-DDDD",
  "email": "user@example.com",
  "hardwareId": "HW-123",
  "productName": "MyProduct"
}

Generate Activation Key

POST /api/activationkey/generate
{
  "productName": "MyProduct",
  "email": "user@example.com",
  "planId": 1,
  "validityDays": 365
}

Plans

GET /api/plan
POST /api/plan
{
  "id": null,
  "name": "365 days",
  "durationDays": 365,
  "isActive": true
}

Validation

Two modes are supported:

  1. Licensed validation (activation key present):
POST /api/license/validate
{
  "activationKey": "AAAA-BBBB-CCCC-DDDD",
  "hardwareId": "HW-123",
  "productName": "MyProduct",
  "version": "1.0"
}

Returns { isValid: true|false, status: "licensed"|"invalid", expirationDate, features: [], serverVersion?, minimumRequiredVersion? }.

Note (v0.6.0): expirationDate is now always returned, even for invalid licenses. For invalid cases, it's calculated from the device record creation date + trial days.

  1. Trial validation (activation key blank):
POST /api/license/validate
{
  "activationKey": "",
  "hardwareId": "HW-NEW",
  "productName": "MyProduct",
  "version": "1.0"
}

Device History

GET /api/license/device-history/{hardwareId}?page=1&pageSize=10

Returns paginated license history for a specific hardware ID in reverse chronological order.

Response: { items: [...], page: 1, pageSize: 10, totalCount: 100, totalPages: 10 }

Encrypted Payloads

Encrypt JSON with the public key (RSA-OAEP-SHA256), base64 encode, and send:

POST /api/license/validate-encrypted
{
  "payload": "<base64 ciphertext>"
}

Response body is JSON; if encrypted variant is called, response will be base64 of RSA-encrypted JSON.

API Licensing

ExisOne supports API key licensing for validating your customers' API access. This allows your customers to generate API keys for their end-users with quota limits and rate limiting.

Create API License

POST /api/apilicense
{
  "productId": 123,
  "email": "enduser@example.com",
  "clientName": "Acme Corp",
  "clientIdentifier": "customer-123",
  "validityDays": 365,
  "maxApiCalls": 10000
}

Returns: { apiKey, expirationDate, maxApiCalls, productName, clientName, clientIdentifier }

Validate API Key

Your customers call this endpoint to validate their end-users' API requests:

POST /api/apilicense/validate
{
  "apiKey": "XXXX-XXXX-XXXX-XXXX",
  "units": 10,
  "consumeUnits": true
}

Response:

{
  "isValid": true,
  "unitsConsumed": 10,
  "quota": { "limit": 10000, "used": 510, "remaining": 9490 },
  "rateLimits": {
    "calls_minute": { "limit": 100, "used": 5, "remaining": 95, "resetsAt": "..." }
  },
  "features": ["api:rate:minute:100", "api:tier:premium"],
  "expiresAt": "2027-01-14T23:59:59Z"
}

Check-Only Validation (No Consumption)

GET /api/apilicense/validate/{apiKey}?units=10

Returns the same response but does not consume quota units.

Get API License Details

GET /api/apilicense/{apiKey}

Get Usage Statistics

GET /api/apilicense/{apiKey}/usage

Reset Usage Counters

POST /api/apilicense/{apiKey}/reset?periodType=all

Period types: all, lifetime, minute, hour, day, month

Revoke API License

DELETE /api/apilicense/{apiKey}

Rate Limit Features

Define rate limits as product features with these naming conventions:

Error Codes

Response Headers

When rate limited, the response includes:

Integration Examples

These examples show how to validate an API key from your backend when your end-user makes an API request.

Python

import requests

EXISONE_URL = "https://www.exisone.com"
ACCESS_TOKEN = "your-access-token"

def validate_api_key(api_key: str, units: int = 1) -> dict:
    """Validate an API key and consume quota units."""
    response = requests.post(
        f"{EXISONE_URL}/api/apilicense/validate",
        headers={"Authorization": f"ExisOneApi {ACCESS_TOKEN}"},
        json={"apiKey": api_key, "units": units, "consumeUnits": True}
    )
    return response.json()

# Example: Middleware for your API
def api_middleware():
    api_key = request.headers.get("X-API-Key")
    if not api_key:
        return {"error": "API key required"}, 401

    result = validate_api_key(api_key, units=1)

    if not result.get("isValid"):
        error_code = result.get("errorCode", "invalid")
        if error_code == "quota_exhausted":
            return {"error": "API quota exhausted"}, 429
        elif error_code.startswith("rate_limit"):
            retry_after = result.get("retryAfterSeconds", 60)
            return {"error": "Rate limit exceeded", "retryAfter": retry_after}, 429
        else:
            return {"error": result.get("errorMessage", "Invalid API key")}, 401

    # API key is valid - proceed with request
    return None

.NET (C#)

using System.Net.Http;
using System.Text.Json;

public class ApiKeyValidator
{
    private readonly HttpClient _http;
    private readonly string _baseUrl = "https://www.exisone.com";
    private readonly string _accessToken;

    public ApiKeyValidator(string accessToken)
    {
        _accessToken = accessToken;
        _http = new HttpClient();
        _http.DefaultRequestHeaders.Add("Authorization", $"ExisOneApi {accessToken}");
    }

    public async Task<ApiValidationResult> ValidateAsync(string apiKey, int units = 1)
    {
        var response = await _http.PostAsJsonAsync(
            $"{_baseUrl}/api/apilicense/validate",
            new { apiKey, units, consumeUnits = true }
        );

        var json = await response.Content.ReadAsStringAsync();
        return JsonSerializer.Deserialize<ApiValidationResult>(json);
    }
}

public record ApiValidationResult(
    bool IsValid,
    string? ErrorCode,
    string? ErrorMessage,
    int UnitsConsumed,
    QuotaInfo? Quota,
    int? RetryAfterSeconds
);

public record QuotaInfo(long Limit, long Used, long Remaining);

// Example: ASP.NET Core middleware
public class ApiKeyMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ApiKeyValidator _validator;

    public ApiKeyMiddleware(RequestDelegate next, ApiKeyValidator validator)
    {
        _next = next;
        _validator = validator;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        var apiKey = context.Request.Headers["X-API-Key"].FirstOrDefault();
        if (string.IsNullOrEmpty(apiKey))
        {
            context.Response.StatusCode = 401;
            await context.Response.WriteAsJsonAsync(new { error = "API key required" });
            return;
        }

        var result = await _validator.ValidateAsync(apiKey);
        if (!result.IsValid)
        {
            context.Response.StatusCode = result.ErrorCode?.StartsWith("rate_limit") == true ? 429 : 401;
            if (result.RetryAfterSeconds.HasValue)
                context.Response.Headers["Retry-After"] = result.RetryAfterSeconds.Value.ToString();
            await context.Response.WriteAsJsonAsync(new { error = result.ErrorMessage });
            return;
        }

        // Store quota info for the request if needed
        context.Items["ApiQuota"] = result.Quota;
        await _next(context);
    }
}

JavaScript / Node.js

const EXISONE_URL = 'https://www.exisone.com';
const ACCESS_TOKEN = 'your-access-token';

async function validateApiKey(apiKey, units = 1) {
  const response = await fetch(`${EXISONE_URL}/api/apilicense/validate`, {
    method: 'POST',
    headers: {
      'Authorization': `ExisOneApi ${ACCESS_TOKEN}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ apiKey, units, consumeUnits: true })
  });
  return response.json();
}

// Example: Express.js middleware
async function apiKeyMiddleware(req, res, next) {
  const apiKey = req.headers['x-api-key'];

  if (!apiKey) {
    return res.status(401).json({ error: 'API key required' });
  }

  try {
    const result = await validateApiKey(apiKey, 1);

    if (!result.isValid) {
      const status = result.errorCode?.startsWith('rate_limit') ? 429 : 401;
      if (result.retryAfterSeconds) {
        res.set('Retry-After', result.retryAfterSeconds);
      }
      return res.status(status).json({
        error: result.errorMessage,
        code: result.errorCode
      });
    }

    // Attach quota info to request for downstream use
    req.apiQuota = result.quota;
    req.apiFeatures = result.features;
    next();
  } catch (error) {
    console.error('API key validation failed:', error);
    return res.status(500).json({ error: 'Validation service unavailable' });
  }
}

// Usage with Express
// app.use('/api', apiKeyMiddleware);
// app.get('/api/data', (req, res) => {
//   console.log('Remaining quota:', req.apiQuota?.remaining);
//   res.json({ data: 'your response' });
// });

Variable Unit Consumption

For APIs where different operations consume different amounts (e.g., fetching 10 records = 10 units):

// Python - consuming variable units
def fetch_records(api_key: str, count: int):
    # Validate and consume `count` units
    result = validate_api_key(api_key, units=count)
    if not result.get("isValid"):
        raise QuotaExceededError(result)

    # Proceed with fetching records
    return database.fetch(count)

// JavaScript - consuming variable units
app.get('/api/records', apiKeyMiddleware, async (req, res) => {
  const count = parseInt(req.query.count) || 10;

  // Re-validate with actual unit count
  const result = await validateApiKey(req.headers['x-api-key'], count);
  if (!result.isValid) {
    return res.status(429).json({ error: result.errorMessage });
  }

  const records = await db.fetchRecords(count);
  res.json({ records, remaining: result.quota?.remaining });
});