API Reference

The Oculum API provides programmatic access to security scanning. Use it to integrate Oculum into custom workflows, CI/CD pipelines, or applications.

Base URL

https://oculum.dev/api/v1

Authentication

All API requests require a Bearer token in the Authorization header:

Authorization: Bearer YOUR_API_KEY

Get your API key from Dashboard Settings.

Example Request

curl https://oculum.dev/api/v1/usage \
  -H "Authorization: Bearer ocu_abc123..."

Rate Limits

Rate limits vary by plan:

PlanRequests/minute
Free10
Starter30
Pro60
Max120

Rate Limit Headers

All responses include rate limit information:

HeaderDescription
X-RateLimit-LimitRequests allowed per minute
X-RateLimit-RemainingRequests remaining in current window
X-RateLimit-ResetUnix timestamp when limit resets

Handling Rate Limits

When you exceed the rate limit, you'll receive a 429 response:

{
  "success": false,
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Too many requests",
    "retryAfter": 45
  }
}

Wait retryAfter seconds before retrying.


Endpoints

POST /scan

Scan code files for security vulnerabilities.

Request

curl -X POST https://oculum.dev/api/v1/scan \
  -H "Authorization: Bearer ocu_..." \
  -H "Content-Type: application/json" \
  -d '{
    "files": [
      {
        "path": "src/api/chat.ts",
        "content": "import OpenAI from \"openai\";\n\nconst openai = new OpenAI({ apiKey: \"sk-abc123\" });\n\nexport async function chat(userMessage: string) {\n  return openai.chat.completions.create({\n    model: \"gpt-4\",\n    messages: [{ role: \"user\", content: userMessage }]\n  });\n}"
      }
    ],
    "options": {
      "depth": "validated",
      "includeInfo": false
    }
  }'

Request Body

FieldTypeRequiredDescription
filesarrayYesFiles to scan
files[].pathstringYesFile path (for context)
files[].contentstringYesFile content to scan
options.depthstringNocheap, validated, or deep (default: cheap)
options.includeInfobooleanNoInclude info-severity findings (default: false)

Response

{
  "success": true,
  "data": {
    "scanId": "scan_xyz789",
    "filesScanned": 1,
    "findings": [
      {
        "id": "finding_abc123",
        "category": "hardcoded_secret",
        "severity": "critical",
        "message": "Hardcoded OpenAI API key detected",
        "file": "src/api/chat.ts",
        "line": 3,
        "column": 39,
        "snippet": "const openai = new OpenAI({ apiKey: \"sk-abc123\" });",
        "remediation": "Use environment variables to store API keys"
      },
      {
        "id": "finding_def456",
        "category": "ai_prompt_hygiene",
        "severity": "medium",
        "message": "User input passed directly to LLM prompt",
        "file": "src/api/chat.ts",
        "line": 7,
        "column": 42,
        "snippet": "messages: [{ role: \"user\", content: userMessage }]",
        "remediation": "Consider input validation or content filtering"
      }
    ],
    "summary": {
      "total": 2,
      "critical": 1,
      "high": 0,
      "medium": 1,
      "low": 0,
      "info": 0
    },
    "scanDuration": 1234
  }
}

Response Fields

FieldTypeDescription
scanIdstringUnique scan identifier
filesScannednumberNumber of files processed
findingsarraySecurity findings
findings[].idstringFinding identifier
findings[].categorystringFinding category (e.g., hardcoded_secret)
findings[].severitystringcritical, high, medium, low, or info
findings[].messagestringHuman-readable description
findings[].filestringFile path
findings[].linenumberLine number
findings[].columnnumberColumn number
findings[].snippetstringCode snippet
findings[].remediationstringSuggested fix
summaryobjectCounts by severity
scanDurationnumberScan time in milliseconds

GET /usage

Get current usage statistics and quota.

Request

curl https://oculum.dev/api/v1/usage \
  -H "Authorization: Bearer ocu_..."

Response

{
  "success": true,
  "data": {
    "plan": "pro",
    "creditsUsed": 45,
    "creditsTotal": 250,
    "scansThisMonth": 45,
    "filesScanned": 12500,
    "resetDate": "2026-02-01T00:00:00Z"
  }
}

Response Fields

FieldTypeDescription
planstringCurrent plan (free, starter, pro, max)
creditsUsednumberCredits used this period
creditsTotalnumberTotal credits available
scansThisMonthnumberNumber of scans run
filesScannednumberTotal files scanned
resetDatestringISO 8601 date when quota resets

POST /verify-key

Verify that an API key is valid and check associated plan.

Request

curl -X POST https://oculum.dev/api/v1/verify-key \
  -H "Authorization: Bearer ocu_..."

Response

{
  "success": true,
  "data": {
    "valid": true,
    "plan": "pro",
    "email": "user@example.com",
    "creditsRemaining": 205
  }
}

Response Fields

FieldTypeDescription
validbooleanWhether the key is valid
planstringAssociated plan
emailstringAccount email
creditsRemainingnumberCredits remaining this period

Error Responses

All errors follow this format:

{
  "success": false,
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable error message",
    "retryAfter": 60
  }
}

Error Codes

CodeHTTP StatusDescription
UNAUTHORIZED401Invalid or missing API key
FORBIDDEN403Insufficient permissions
RATE_LIMIT_EXCEEDED429Too many requests
QUOTA_EXCEEDED429Monthly credit limit reached
INVALID_REQUEST400Malformed request body
VALIDATION_ERROR400Request validation failed
SCAN_FAILED500Internal scan error
SERVICE_UNAVAILABLE503Temporary service issue

Example Error Response

{
  "success": false,
  "error": {
    "code": "QUOTA_EXCEEDED",
    "message": "Monthly scan quota exceeded. Upgrade your plan for more credits.",
    "upgradeUrl": "https://oculum.dev/pricing"
  }
}

TypeScript SDK

Installation

npm install @oculum/sdk

Basic Usage

import { OculumClient } from '@oculum/sdk';

const client = new OculumClient({
  apiKey: process.env.OCULUM_API_KEY!
});

// Scan files
const result = await client.scan({
  files: [
    {
      path: 'src/index.ts',
      content: 'const apiKey = "sk-secret123";'
    }
  ],
  options: {
    depth: 'validated'
  }
});

console.log(`Found ${result.summary.total} issues`);

for (const finding of result.findings) {
  console.log(`[${finding.severity}] ${finding.message}`);
  console.log(`  ${finding.file}:${finding.line}`);
}

Get Usage

const usage = await client.getUsage();

console.log(`Plan: ${usage.plan}`);
console.log(`Credits: ${usage.creditsUsed}/${usage.creditsTotal}`);

Error Handling

import {
  OculumClient,
  OculumError,
  RateLimitError,
  QuotaExceededError
} from '@oculum/sdk';

const client = new OculumClient({
  apiKey: process.env.OCULUM_API_KEY!
});

try {
  const result = await client.scan({ files });
} catch (error) {
  if (error instanceof RateLimitError) {
    console.log(`Rate limited. Retry after ${error.retryAfter}s`);
  } else if (error instanceof QuotaExceededError) {
    console.log('Quota exceeded. Upgrade at:', error.upgradeUrl);
  } else if (error instanceof OculumError) {
    console.log('API error:', error.code, error.message);
  } else {
    throw error;
  }
}

TypeScript Types

interface ScanOptions {
  files: Array<{
    path: string;
    content: string;
  }>;
  options?: {
    depth?: 'cheap' | 'validated' | 'deep';
    includeInfo?: boolean;
  };
}

interface ScanResult {
  scanId: string;
  filesScanned: number;
  findings: Finding[];
  summary: {
    total: number;
    critical: number;
    high: number;
    medium: number;
    low: number;
    info: number;
  };
  scanDuration: number;
}

interface Finding {
  id: string;
  category: string;
  severity: 'critical' | 'high' | 'medium' | 'low' | 'info';
  message: string;
  file: string;
  line: number;
  column: number;
  snippet: string;
  remediation: string;
}

Python SDK

Coming soon. In the meantime, use the REST API directly:

import requests
import os

API_KEY = os.environ["OCULUM_API_KEY"]
BASE_URL = "https://oculum.dev/api/v1"

def scan_files(files, depth="cheap"):
    response = requests.post(
        f"{BASE_URL}/scan",
        headers={
            "Authorization": f"Bearer {API_KEY}",
            "Content-Type": "application/json"
        },
        json={
            "files": files,
            "options": {"depth": depth}
        }
    )
    response.raise_for_status()
    return response.json()

# Example usage
result = scan_files([
    {"path": "main.py", "content": "api_key = 'secret123'"}
])

for finding in result["data"]["findings"]:
    print(f"[{finding['severity']}] {finding['message']}")

Webhooks (Coming Soon)

Configure webhooks to receive scan results asynchronously:

{
  "event": "scan.completed",
  "timestamp": "2026-01-20T12:00:00Z",
  "data": {
    "scanId": "scan_abc123",
    "status": "completed",
    "summary": {
      "total": 3,
      "critical": 1,
      "high": 2
    }
  }
}

Related