OneManage Logo
OneManage Docs

REST API Guide

Who is this for? This guide is for developers who are not using the KMP SDK — for example, teams building Python, Node.js, Go, Ruby, PHP, or plain HTTP clients. Every feature available in the SDK is also available directly over HTTP.


Base URL

https://onemanage.quest

All endpoints are served over HTTPS. Substitute your self-hosted domain if you are running a private OneManage instance.


Authentication

OneManage uses two independent authentication schemes depending on the type of endpoint.

1. API Key (for SDK-style ingestion endpoints)

Used by your application (mobile app, backend service) to send data to OneManage. Credentials are passed as HTTP request headers — never in the URL.

Header Description
X-API-KEY Your account's API key (starts with sk-live-)
X-APP-ID The identifier of the registered app sending data

Both headers are required for all ingestion endpoints.

Get your credentials: Log in to the OneManage Dashboard, navigate to Settings → API Key and Apps to find your App ID.

Example (curl)

curl -X POST https://onemanage.quest/logs/ingest/app \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: sk-live-xxxxxxxxxxxx" \
  -H "X-APP-ID: my-android-app" \
  -d '{ ... }'

Dashboard management endpoints (e.g. listing configs, resolving bugs) are protected by a JWT session cookie issued at login. These endpoints are intended for dashboard access and are not designed for programmatic API consumers in external integrations.

Note: For machine-to-machine use, stick to the API Key–protected ingestion endpoints documented in this guide. Management endpoints may require interactive login and are subject to change.


Response Format

All API responses are wrapped in a standard envelope:

Success

{
  "statusCode": 200,
  "message": "Logs ingested successfully",
  "data": { ... }
}

Failure

{
  "statusCode": 401,
  "message": "Missing API Key",
  "data": null
}
Field Type Description
statusCode Int HTTP status code mirrored in the body
message String Human-readable status message
data Object | null Response payload, or null on error

Endpoints


1. Log Ingestion

1.1 Ingest App Logs

Use this endpoint for mobile or frontend apps (Android, iOS, Desktop). It accepts enriched device and user context alongside the log batch.

POST /logs/ingest/app

Headers:

Header Required Description
Content-Type application/json
X-API-KEY Your account API key
X-APP-ID The app identifier

Request Body:

{
  "schemaVersion": "1.0",
  "source": "APP",
  "batchId": "batch_a1b2c3d4",

  "packageName": "com.example.myapp",
  "appVersion": "2.1.0",
  "environment": "production",

  "userInfo": {
    "userId": "user_123456",
    "isAnonymous": false
  },

  "deviceInfo": {
    "model": "Pixel 8",
    "manufacturer": "Google",
    "osName": "Android",
    "osVersion": "14",
    "locale": "en-US",
    "country": "US"
  },

  "logs": [
    {
      "logId": "log_unique_id_001",
      "level": "INFO",
      "logger": "HomeScreen",
      "timestamp": 1750000000000,
      "thread": "main",
      "event": "screen_opened",
      "message": "User opened the home screen",
      "stacktrace": null,
      "errorCode": null,
      "metadata": {
        "referrer": "splash_screen"
      }
    },
    {
      "logId": "log_unique_id_002",
      "level": "ERROR",
      "logger": "CheckoutService",
      "timestamp": 1750000001000,
      "thread": "io-pool-1",
      "event": "payment_failed",
      "message": "Payment timed out after 30s",
      "stacktrace": "java.net.SocketTimeoutException: timeout\n\tat ...",
      "errorCode": "ERR_TIMEOUT",
      "metadata": {
        "cart_total": "149.99",
        "currency": "USD"
      }
    }
  ]
}

Field Reference — Top Level:

Field Type Required Description
schemaVersion String Schema version — use "1.0"
source String Must be "APP"
batchId String Unique ID for this batch, used for deduplication
packageName String App package/bundle identifier (e.g. com.example.app)
appVersion String App version string (e.g. "2.1.0")
environment String "production", "staging", or "debug"
userInfo Object See userInfo schema below
deviceInfo Object See deviceInfo schema below
logs Array<Log> One or more log entries. Max 50 per batch

userInfo Object:

Field Type Required Description
userId String? Authenticated user ID, or null for anonymous
isAnonymous Boolean true if no authenticated user is present

deviceInfo Object:

Field Type Required Description
model String? Device model name (e.g. "Pixel 8", "iPhone 15")
manufacturer String? Device manufacturer (e.g. "Google", "Apple")
osName String Operating system name (e.g. "Android", "iOS")
osVersion String OS version string (e.g. "14", "17.2")
locale String? BCP-47 locale tag (e.g. "en-US")
country String? ISO 3166-1 alpha-2 country code (e.g. "US")

logs[] Object:

Field Type Required Description
logId String Unique ID for this log entry (use UUID v4)
level String "DEBUG", "INFO", "WARN", "ERROR"
logger String? Source tag / logger name (e.g. "HomeScreen")
timestamp Long Unix epoch in milliseconds
thread String? Thread name at time of logging
event String Machine-readable event key (e.g. "payment_failed")
message String Human-readable log message
stacktrace String? Full stack trace string for errors
errorCode String? Application-defined error code (e.g. "ERR_TIMEOUT")
metadata Object Flat String → String map for extra context

Success Response (200 OK):

{
  "statusCode": 200,
  "message": "Logs ingested successfully",
  "data": null
}

Error Responses:

Status Reason
401 Unauthorized Missing or invalid X-API-KEY
400 Bad Request Malformed JSON body
500 Internal Server Error Server-side failure

1.2 Ingest Server / Backend Logs

Use this endpoint for backend services (Node.js, Python, Go, Java, etc.). Provides a runtime context block instead of device info.

POST /logs/ingest/server

Headers: Same as App Logs (Content-Type, X-API-KEY, X-APP-ID).

Request Body:

{
  "schemaVersion": "1.0",
  "source": "SERVER",
  "batchId": "batch_srv_xyz789",
  "serviceName": "user-service",
  "serviceVersion": "3.4.1",
  "environment": "production",

  "runtime": {
    "osName": "Linux",
    "osVersion": "5.15.0",
    "jvmVersion": "21.0.2"
  },

  "logs": [
    {
      "logId": "srv_log_001",
      "level": "INFO",
      "logger": "UserController",
      "timestamp": 1750000000000,
      "thread": "ktor-worker-1",
      "event": "user_registered",
      "message": "New user registered: user@example.com",
      "stacktrace": null,
      "errorCode": null,
      "metadata": {
        "plan": "free"
      },
      "requestInfo": null
    },
    {
      "logId": "srv_log_002",
      "level": "ERROR",
      "logger": "PaymentService",
      "timestamp": 1750000005000,
      "thread": "ktor-worker-3",
      "event": "payment_gateway_error",
      "message": "Stripe webhook validation failed",
      "stacktrace": "com.stripe.StripeException: ...",
      "errorCode": "STRIPE_SIG_INVALID",
      "metadata": {},
      "requestInfo": {
        "requestId": "req_abc123",
        "method": "POST",
        "path": "/webhooks/stripe",
        "statusCode": 400,
        "durationMs": 12
      }
    }
  ]
}

Field Reference — Top Level (differences from App Logs):

Field Type Required Description
source String Must be "SERVER"
serviceName String Name of your backend service (e.g. "user-service")
serviceVersion String Version of your backend service
runtime Object See runtime schema below

runtime Object:

Field Type Required Description
osName String Host OS (e.g. "Linux", "Windows")
osVersion String OS version string
jvmVersion String Runtime version (use "N/A" for non-JVM runtimes)

logs[].requestInfo Object (optional — attach to logs generated during an HTTP request):

Field Type Required Description
requestId String Unique request/trace ID
method String HTTP method ("GET", "POST", etc.)
path String Request path (e.g. "/api/users")
statusCode Int HTTP response status code
durationMs Long Request duration in milliseconds

Success Response (200 OK): Same as App Logs.


Code Examples — Log Ingestion

Node.js (fetch)
const BASE_URL = 'https://onemanage.quest';
const API_KEY  = 'sk-live-xxxxxxxxxxxx';
const APP_ID   = 'my-node-service';

async function sendLogs(logs) {
  const batch = {
    schemaVersion: '1.0',
    source: 'SERVER',
    batchId: `batch_${Date.now()}`,
    serviceName: 'my-node-service',
    serviceVersion: '1.0.0',
    environment: process.env.NODE_ENV ?? 'production',
    runtime: {
      osName: process.platform,
      osVersion: process.version,
      jvmVersion: 'N/A'
    },
    logs: logs.map(l => ({
      logId: crypto.randomUUID(),
      level: l.level,
      logger: l.tag,
      timestamp: Date.now(),
      event: l.event,
      message: l.message,
      metadata: l.metadata ?? {}
    }))
  };

  const res = await fetch(`${BASE_URL}/logs/ingest/server`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-API-KEY': API_KEY,
      'X-APP-ID': APP_ID
    },
    body: JSON.stringify(batch)
  });

  if (!res.ok) {
    console.error('OneManage ingest failed:', await res.text());
  }
}
Python (requests)
import requests, uuid, time, platform

BASE_URL = "https://onemanage.quest"
API_KEY  = "sk-live-xxxxxxxxxxxx"
APP_ID   = "my-python-service"

def send_logs(entries: list[dict]):
    batch = {
        "schemaVersion": "1.0",
        "source": "SERVER",
        "batchId": f"batch_{uuid.uuid4().hex[:8]}",
        "serviceName": "my-python-service",
        "serviceVersion": "1.0.0",
        "environment": "production",
        "runtime": {
            "osName": platform.system(),
            "osVersion": platform.release(),
            "jvmVersion": "N/A"
        },
        "logs": [
            {
                "logId": str(uuid.uuid4()),
                "level": entry.get("level", "INFO"),
                "logger": entry.get("tag", "app"),
                "timestamp": int(time.time() * 1000),
                "event": entry.get("event", "log"),
                "message": entry["message"],
                "metadata": entry.get("metadata", {})
            }
            for entry in entries
        ]
    }

    resp = requests.post(
        f"{BASE_URL}/logs/ingest/server",
        json=batch,
        headers={
            "X-API-KEY": API_KEY,
            "X-APP-ID": APP_ID
        },
        timeout=10
    )
    resp.raise_for_status()
Go
package onemanage

import (
    "bytes"
    "encoding/json"
    "fmt"
    "net/http"
    "runtime"
    "time"

    "github.com/google/uuid"
)

const (
    baseURL = "https://onemanage.quest"
    apiKey  = "sk-live-xxxxxxxxxxxx"
    appID   = "my-go-service"
)

type LogEntry struct {
    LogID     string            `json:"logId"`
    Level     string            `json:"level"`
    Logger    string            `json:"logger"`
    Timestamp int64             `json:"timestamp"`
    Event     string            `json:"event"`
    Message   string            `json:"message"`
    Metadata  map[string]string `json:"metadata"`
}

type Batch struct {
    SchemaVersion  string     `json:"schemaVersion"`
    Source         string     `json:"source"`
    BatchID        string     `json:"batchId"`
    ServiceName    string     `json:"serviceName"`
    ServiceVersion string     `json:"serviceVersion"`
    Environment    string     `json:"environment"`
    Runtime        Runtime    `json:"runtime"`
    Logs           []LogEntry `json:"logs"`
}

type Runtime struct {
    OsName     string `json:"osName"`
    OsVersion  string `json:"osVersion"`
    JvmVersion string `json:"jvmVersion"`
}

func SendLog(level, event, message string) error {
    batch := Batch{
        SchemaVersion:  "1.0",
        Source:         "SERVER",
        BatchID:        uuid.New().String(),
        ServiceName:    "my-go-service",
        ServiceVersion: "1.0.0",
        Environment:    "production",
        Runtime: Runtime{
            OsName:     runtime.GOOS,
            OsVersion:  "unknown",
            JvmVersion: "N/A",
        },
        Logs: []LogEntry{
            {
                LogID:     uuid.New().String(),
                Level:     level,
                Logger:    "app",
                Timestamp: time.Now().UnixMilli(),
                Event:     event,
                Message:   message,
                Metadata:  map[string]string{},
            },
        },
    }

    body, _ := json.Marshal(batch)
    req, _ := http.NewRequest("POST", baseURL+"/logs/ingest/server", bytes.NewReader(body))
    req.Header.Set("Content-Type", "application/json")
    req.Header.Set("X-API-KEY", apiKey)
    req.Header.Set("X-APP-ID", appID)

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return err
    }
    defer resp.Body.Close()
    if resp.StatusCode >= 400 {
        return fmt.Errorf("onemanage: ingest failed with status %d", resp.StatusCode)
    }
    return nil
}

2. Bug Reports

2.1 Submit a Bug Report

Submit a structured bug report to your OneManage Dashboard from any environment or language.

POST /bugs/ingest

Headers:

Header Required Description
Content-Type application/json
X-API-KEY Your account API key
X-APP-ID The app identifier

Request Body:

{
  "identifier": "com.example.myapp",
  "title": "Crash on checkout",
  "description": "App crashes when the user taps 'Pay Now' with an expired card.",
  "severity": "HIGH",
  "steps": "1. Add item to cart\n2. Go to checkout\n3. Enter expired card\n4. Tap 'Pay Now'\n5. Observe crash",
  "appVersion": "2.1.0",
  "submittedBy": "user_123456",
  "metadata": {
    "screen": "CheckoutScreen",
    "network_type": "WiFi",
    "os": "Android 14"
  }
}

Field Reference:

Field Type Required Default Description
identifier String? null App package/bundle name (helps resolve the app on the server)
title String Short, descriptive bug title
description String Detailed description of the issue
severity String "NORMAL" "LOW", "NORMAL", "HIGH", "CRITICAL"
steps String? null Steps to reproduce (plain text, newlines accepted)
appVersion String? null App version when the bug occurred
submittedBy String? null User ID or email of the person reporting the bug
metadata Object null Flat String → String map for extra device/context info

Severity Levels:

Value When to use
"LOW" Cosmetic issues, minor visual glitches
"NORMAL" Standard bugs affecting user experience
"HIGH" Feature broken for a segment of users
"CRITICAL" App crash, data loss, or security vulnerability

Success Response (200 OK):

{
  "statusCode": 200,
  "message": "Bug report submitted successfully",
  "data": null
}

Code Examples — Bug Reports

Node.js
async function submitBug({ title, description, severity = 'NORMAL', steps, metadata }) {
  await fetch('https://onemanage.quest/bugs/ingest', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-API-KEY': 'sk-live-xxxxxxxxxxxx',
      'X-APP-ID': 'my-node-service'
    },
    body: JSON.stringify({ title, description, severity, steps, metadata })
  });
}

// Usage
await submitBug({
  title: 'DB connection pool exhausted',
  description: 'All connections in the pool are busy under sustained load.',
  severity: 'CRITICAL',
  metadata: { pool_size: '10', active_connections: '10' }
});
Python
import requests

def submit_bug(title: str, description: str, severity: str = "NORMAL", **kwargs):
    requests.post(
        "https://onemanage.quest/bugs/ingest",
        json={"title": title, "description": description, "severity": severity, **kwargs},
        headers={
            "X-API-KEY": "sk-live-xxxxxxxxxxxx",
            "X-APP-ID": "my-python-service"
        },
        timeout=10
    ).raise_for_status()

# Usage
submit_bug(
    title="Redis cache miss storm",
    description="Cache miss rate spiked to 100% after deployment.",
    severity="HIGH",
    metadata={"cache_hit_rate": "0%", "deployment_id": "d-abc123"}
)

3. Remote Config

3.1 Fetch Remote Config

Retrieve all active configuration values for a specific app. The response returns a flat key→value map ready for immediate use in your application.

GET /config/fetch

Headers:

Header Required Description
X-API-KEY Your account API key
X-APP-ID The app identifier

No request body required.

Success Response (200 OK):

{
  "statusCode": 200,
  "message": "Config fetched successfully",
  "data": {
    "configs": {
      "dark_mode_enabled": true,
      "welcome_message": "Hello, world!",
      "max_retry_count": 3,
      "discount_rate": 0.15,
      "session_timeout_ms": 300000
    },
    "fetchedAt": 1750000000000
  }
}

Response data Fields:

Field Type Description
configs Object Flat map of key → typed value. Values are typed (boolean, number, string) based on the type configured in your dashboard.
fetchedAt Long Unix epoch (ms) when the config was fetched

Tip: You should cache this response locally and re-fetch on app startup or periodically (e.g. every 30 minutes). Avoid calling this on every request.

Code Examples — Remote Config

Node.js
let cachedConfig = {};

async function fetchRemoteConfig() {
  const res = await fetch('https://onemanage.quest/config/fetch', {
    headers: {
      'X-API-KEY': 'sk-live-xxxxxxxxxxxx',
      'X-APP-ID': 'my-node-service'
    }
  });
  const body = await res.json();
  if (body.statusCode === 200) {
    cachedConfig = body.data.configs;
    console.log('Config refreshed at', new Date(body.data.fetchedAt));
  }
}

function getConfig(key, defaultValue) {
  return cachedConfig[key] ?? defaultValue;
}

// On startup
await fetchRemoteConfig();

// Usage
const isFeatureEnabled = getConfig('new_checkout_flow', false);
const maxRetries       = getConfig('max_retry_count', 3);
Python
import requests, time

_config_cache = {}
_cache_ts = 0
CACHE_TTL_SECONDS = 1800  # 30 minutes

def fetch_remote_config():
    global _config_cache, _cache_ts
    if time.time() - _cache_ts < CACHE_TTL_SECONDS:
        return  # Still fresh

    resp = requests.get(
        "https://onemanage.quest/config/fetch",
        headers={
            "X-API-KEY": "sk-live-xxxxxxxxxxxx",
            "X-APP-ID": "my-python-service"
        },
        timeout=10
    )
    resp.raise_for_status()
    body = resp.json()
    if body["statusCode"] == 200:
        _config_cache = body["data"]["configs"]
        _cache_ts = time.time()

def get_config(key: str, default=None):
    fetch_remote_config()
    return _config_cache.get(key, default)

# Usage
is_enabled  = get_config("new_checkout_flow", False)
max_retries = get_config("max_retry_count", 3)

Log Levels Reference

Level String Value When to Use
Debug "DEBUG" Verbose diagnostic info — suppress in production
Info "INFO" Normal app lifecycle events
Warning "WARN" Recoverable issues, unexpected states
Error "ERROR" Errors that affect user experience

Batching Best Practices

The OneManage ingestion API is optimized for batched writes. Sending individual logs one at a time is wasteful and can trigger rate limits.

Recommended approach:

  1. Buffer logs in-memory up to a maximum of 50 entries or a time window of 15 seconds (whichever comes first).
  2. Send the batch to /logs/ingest/app or /logs/ingest/server as a single HTTP request.
  3. On failure, store the batch locally and retry with exponential backoff (1s, 2s, 4s, max 3 retries).
  4. On app shutdown, flush any remaining buffered logs immediately.
// Minimal Node.js batcher
class OneManageBatcher {
  #buffer = [];
  #timer = null;

  constructor(flushIntervalMs = 15000, maxBatchSize = 50) {
    this.flushIntervalMs = flushIntervalMs;
    this.maxBatchSize = maxBatchSize;
  }

  log(level, event, message, metadata = {}) {
    this.#buffer.push({
      logId: crypto.randomUUID(),
      level,
      event,
      message,
      metadata,
      timestamp: Date.now()
    });
    if (this.#buffer.length >= this.maxBatchSize) {
      this.flush();
    } else if (!this.#timer) {
      this.#timer = setTimeout(() => this.flush(), this.flushIntervalMs);
    }
  }

  async flush() {
    if (this.#timer) { clearTimeout(this.#timer); this.#timer = null; }
    if (this.#buffer.length === 0) return;

    const batch = this.#buffer.splice(0);
    // Build and POST the batch payload here (see Node.js example above)
    await sendLogs(batch);
  }

  info(event, msg, meta)  { this.log('INFO',  event, msg, meta); }
  warn(event, msg, meta)  { this.log('WARN',  event, msg, meta); }
  error(event, msg, meta) { this.log('ERROR', event, msg, meta); }
  debug(event, msg, meta) { this.log('DEBUG', event, msg, meta); }
}

// Usage
const logger = new OneManageBatcher();
process.on('exit', () => logger.flush());

logger.info('app_started', 'Application started');
logger.error('db_error', 'Connection refused', { host: 'db.internal' });

Rate Limits

Endpoint Limit
/logs/ingest/app 60 requests / minute per API key
/logs/ingest/server 60 requests / minute per API key
/bugs/ingest 30 requests / minute per API key
/config/fetch 20 requests / minute per API key

Exceeding the rate limit returns a 429 Too Many Requests response. Back off and retry after 60 seconds.


Error Codes Reference

HTTP Status Meaning Resolution
400 Bad Request Malformed JSON or missing required fields Check your request body against the schema
401 Unauthorized Missing or invalid X-API-KEY Verify your API key in the dashboard
400 Bad Request Missing X-APP-ID Include the X-APP-ID header
404 Not Found App ID not found in your account Verify the App ID exists in the dashboard
429 Too Many Requests Rate limit exceeded Implement exponential backoff
500 Internal Server Error Server-side failure Retry later; contact support if persistent

SDK vs REST API — Feature Comparison

Feature KMP SDK REST API
App Log Ingestion ✅ Automatic batching & retry ✅ Manual batching required
Server Log Ingestion ✅ Ktor SDK plugin ✅ Any HTTP client
Bug Report Submission ✅ Auto device metadata ✅ Manual metadata
Remote Config ✅ SQLDelight cache + StateFlow ✅ Manual caching
Offline Persistence ✅ Built-in ❌ Must implement yourself
Auto User Identity ✅ SDK-managed UUID ❌ Must generate/track yourself
Platform Support Android, iOS, JVM Any language or platform

Support & Resources