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:
| Plan | Requests/minute |
|---|---|
| Free | 10 |
| Starter | 30 |
| Pro | 60 |
| Max | 120 |
Rate Limit Headers
All responses include rate limit information:
| Header | Description |
|---|---|
X-RateLimit-Limit | Requests allowed per minute |
X-RateLimit-Remaining | Requests remaining in current window |
X-RateLimit-Reset | Unix 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
| Field | Type | Required | Description |
|---|---|---|---|
files | array | Yes | Files to scan |
files[].path | string | Yes | File path (for context) |
files[].content | string | Yes | File content to scan |
options.depth | string | No | cheap, validated, or deep (default: cheap) |
options.includeInfo | boolean | No | Include 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
| Field | Type | Description |
|---|---|---|
scanId | string | Unique scan identifier |
filesScanned | number | Number of files processed |
findings | array | Security findings |
findings[].id | string | Finding identifier |
findings[].category | string | Finding category (e.g., hardcoded_secret) |
findings[].severity | string | critical, high, medium, low, or info |
findings[].message | string | Human-readable description |
findings[].file | string | File path |
findings[].line | number | Line number |
findings[].column | number | Column number |
findings[].snippet | string | Code snippet |
findings[].remediation | string | Suggested fix |
summary | object | Counts by severity |
scanDuration | number | Scan 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
| Field | Type | Description |
|---|---|---|
plan | string | Current plan (free, starter, pro, max) |
creditsUsed | number | Credits used this period |
creditsTotal | number | Total credits available |
scansThisMonth | number | Number of scans run |
filesScanned | number | Total files scanned |
resetDate | string | ISO 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
| Field | Type | Description |
|---|---|---|
valid | boolean | Whether the key is valid |
plan | string | Associated plan |
email | string | Account email |
creditsRemaining | number | Credits 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
| Code | HTTP Status | Description |
|---|---|---|
UNAUTHORIZED | 401 | Invalid or missing API key |
FORBIDDEN | 403 | Insufficient permissions |
RATE_LIMIT_EXCEEDED | 429 | Too many requests |
QUOTA_EXCEEDED | 429 | Monthly credit limit reached |
INVALID_REQUEST | 400 | Malformed request body |
VALIDATION_ERROR | 400 | Request validation failed |
SCAN_FAILED | 500 | Internal scan error |
SERVICE_UNAVAILABLE | 503 | Temporary 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
- Quickstart — Get started with Oculum
- CLI Reference — Command-line scanning
- GitHub Action — CI/CD integration
- Credits & Usage — Quotas and plans