OAuthSentry exposes a REST API for programmatic scanning, live threat intelligence, Linear ticket filing, Slack alerting, and scheduled scan management. All endpoints are available at https://oauthsentry-phi.vercel.app.
Streaming scan
NDJSON
Live threat feed
NVD · OSV · GitHub
Slack + Linear
Real alerts
Scheduled scans
Upstash Redis
Endpoints marked api-key require your API key set in the Settings dialog in the app (for Linear and Slack integrations), or as environment variables on the server. For programmatic access:
curl -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ https://oauthsentry-phi.vercel.app/api/scan
LINEAR_API_KEY — Linear API key (from linear.app/settings/account/security)
SLACK_WEBHOOK_URL — Slack Incoming Webhook URL
KV_REST_API_URL / KV_REST_API_TOKEN — Upstash Redis (for scheduled scans)
/api/scanStreaming NDJSONRun an AI-powered security scan on a list of assets (OAuth apps, npm packages, SaaS tools). Returns a streaming NDJSON response — each line is a JSON event as findings are produced.
Request Body
| Field | Type | Description |
|---|---|---|
| assets | Asset[] | Array of assets to scan (max 40) |
| assets[].id | string | Unique identifier for the asset |
| assets[].kind | 'oauth_app' | 'npm_package' | 'saas_tool' | Asset category |
| assets[].name | string | Human-readable name |
| assets[].identifier | string | OAuth client ID, npm package name, or domain |
| assets[].owner | string | Who installed or owns this asset |
| assets[].scopes | string[] | OAuth/permission scopes granted |
Response
| Field | Type | Description |
|---|---|---|
| type | 'start' | 'finding' | 'error' | 'done' | Event type for each NDJSON line |
| finding.score | number (0–100) | Risk score — higher means riskier |
| finding.level | 'critical' | 'high' | 'medium' | 'low' | 'info' | Risk severity level |
| finding.headline | string | Short summary of the identified risk |
| finding.reasoning | string | Detailed AI-generated reasoning |
| finding.factors | { label: string; detail: string }[] | Individual risk factor breakdown |
| finding.iocMatches | string[] | Matched IOC IDs from threat feeds |
| finding.recommendation | string | Actionable remediation steps |
Example
Request
POST /api/scan
Content-Type: application/json
Authorization: Bearer YOUR_API_KEY
{
"assets": [
{
"id": "oauth-context-ai",
"kind": "oauth_app",
"name": "Context.ai",
"identifier": "110671459871-30f1spbu0hptbs60cb4vsmv791tbbvqj.apps.googleusercontent.com",
"scopes": ["gmail.readonly", "calendar.modify", "contacts.readonly"]
}
]
}Response
{"type":"start","count":1}
{"type":"finding","finding":{"assetId":"oauth-context-ai","score":95,"level":"critical","headline":"OAuth app involved in major security breach","reasoning":"Context.ai was the vector for the April 2026 Vercel/Context.ai incident...","factors":[{"label":"Breach Confirmed","detail":"Listed in NVD and GitHub Security Advisories"}],"iocMatches":["ioc-context-ai-2026"],"recommendation":"Immediately revoke all permissions..."},"analyzed":1,"total":1}
{"type":"done","analyzed":1}/api/threat-feedLive — NVD · OSV · GitHubNo auth requiredFetch live threat intelligence from NVD, OSV (Google), and GitHub Security Advisories. Results are aggregated, sorted by date, and cached for 1 hour to respect upstream rate limits.
Response
| Field | Type | Description |
|---|---|---|
| items | ThreatItem[] | Array of up to 15 live threat items sorted newest-first |
| items[].id | string | CVE ID, GHSA ID, or OSV ID |
| items[].title | string | Short title of the threat |
| items[].summary | string | Detailed description |
| items[].severity | 'critical' | 'high' | 'medium' | 'low' | 'info' | Severity mapped from CVSS score |
| items[].indicatorKind | 'oauth_client' | 'npm' | 'domain' | 'cve' | Type of threat indicator |
| items[].indicator | string | The affected identifier (package name, CVE ID, domain) |
| items[].source | 'NVD' | 'OSV' | 'GITHUB SECURITY' | Source of the threat intelligence |
| items[].reference | string (URL) | Direct link to advisory |
| items[].publishedAt | ISO 8601 string | When this advisory was published |
| fetchedAt | ISO 8601 string | Timestamp of this response |
Example
Response
{
"items": [
{
"id": "CVE-2026-12345",
"title": "CVE-2026-12345: Critical RCE in oauth2-proxy...",
"summary": "A heap buffer overflow in oauth2-proxy allows remote code execution via malformed token.",
"severity": "critical",
"indicatorKind": "cve",
"indicator": "CVE-2026-12345",
"source": "NVD",
"reference": "https://nvd.nist.gov/vuln/detail/CVE-2026-12345",
"publishedAt": "2026-04-29T00:00:00Z"
}
],
"sources": ["NVD", "OSV", "GitHub Security"],
"fetchedAt": "2026-05-03T12:00:00Z"
}/api/actions/file-ticketCreate a Linear issue for a specific risk finding. Uses the API key from the request body (set in Settings) or falls back to the LINEAR_API_KEY environment variable.
Request Body
| Field | Type | Description |
|---|---|---|
| finding | RiskFinding | The full finding object from a scan result |
| finding.asset.name | string | Asset name for the ticket title |
| finding.score | number | Risk score (0–100) |
| finding.level | string | Severity level |
| finding.headline | string | Issue title |
| finding.reasoning | string | Issue description body |
| finding.recommendation | string | Remediation steps in the ticket |
| linearApiKey | string | Linear API key (overrides env var) |
Response
| Field | Type | Description |
|---|---|---|
| success | boolean | Whether the ticket was created |
| ticketUrl | string (URL) | Direct URL to the Linear issue |
| ticketId | string | Linear issue ID (e.g. OAU-42) |
| error | string | Error message if success is false |
Example
Response
{
"success": true,
"ticketUrl": "https://linear.app/oauthsentry/issue/OAU-42",
"ticketId": "OAU-42"
}/api/actions/send-alertSend a rich Slack message to your webhook for a risk finding. Uses the webhook URL from the request body (set in Settings) or falls back to the SLACK_WEBHOOK_URL environment variable.
Request Body
| Field | Type | Description |
|---|---|---|
| finding | RiskFinding | The full finding object from a scan result |
| finding.asset.name | string | Asset name displayed in the alert |
| finding.score | number | Risk score shown in the alert |
| finding.headline | string | Alert summary headline |
| finding.cveReferences | { id: string; score: number }[] | CVE data shown in threat intelligence section |
| slackWebhookUrl | string (URL) | Slack webhook URL (overrides env var) |
Response
| Field | Type | Description |
|---|---|---|
| success | boolean | Whether the alert was delivered |
| error | string | Error message if success is false |
Example
Response
{
"success": true
}/api/scheduled-scansRetrieve all saved scheduled scan configurations from Upstash Redis.
Response
| Field | Type | Description |
|---|---|---|
| success | boolean | Whether the request succeeded |
| schedules | ScheduleConfig[] | Array of all saved schedules |
| schedules[].id | string | Unique schedule ID |
| schedules[].frequency | 'daily' | 'weekly' | 'monthly' | How often the scan runs |
| schedules[].time | string (HH:MM) | Time of day to run |
| schedules[].recipients | string[] | Email addresses for reports |
| schedules[].enabled | boolean | Whether this schedule is active |
| schedules[].nextRun | ISO 8601 string | When this scan will next execute |
| schedules[].lastRun | ISO 8601 string | When this scan last ran (if ever) |
Example
Response
{
"success": true,
"schedules": [
{
"id": "schedule-1746123456-abc123",
"frequency": "daily",
"time": "09:00",
"recipients": ["security@company.com"],
"includeCharts": true,
"includeRecommendations": true,
"enabled": true,
"createdAt": "2026-05-01T00:00:00Z",
"nextRun": "2026-05-04T09:00:00Z",
"lastRun": "2026-05-03T09:00:00Z"
}
]
}/api/scheduled-scansCreate a new scheduled scan configuration. The schedule is persisted to Upstash Redis and the next run time is calculated automatically.
Request Body
| Field | Type | Description |
|---|---|---|
| frequency | 'daily' | 'weekly' | 'monthly' | How often to run the scan |
| time | string (HH:MM) | Time of day to run (24h format) |
| dayOfWeek | number (0–6) | Day of week for weekly schedules (0=Sunday) |
| dayOfMonth | number (1–31) | Day of month for monthly schedules |
| recipients | string[] | Email addresses to notify |
| includeCharts | boolean | Include visual charts in report (default: true) |
| includeRecommendations | boolean | Include remediation steps (default: true) |
Response
| Field | Type | Description |
|---|---|---|
| success | boolean | Whether the schedule was saved |
| schedule | ScheduleConfig | The created schedule with computed nextRun |
Example
Response
{
"success": true,
"schedule": {
"id": "schedule-1746123456-abc123",
"frequency": "daily",
"time": "09:00",
"enabled": true,
"nextRun": "2026-05-04T09:00:00Z",
"createdAt": "2026-05-03T12:00:00Z"
}
}/api/scheduled-scans/executeVercel Cron every 15 minImmediately execute a specific scheduled scan by ID, or run all due scans when called by Vercel Cron (every 15 minutes). Returns scan results including findings summary.
Request Body
| Field | Type | Description |
|---|---|---|
| scheduleId | string | Specific schedule ID to run. Omit to run all due schedules (Cron mode) |
Response
| Field | Type | Description |
|---|---|---|
| success | boolean | Whether the scan executed |
| result.findingsCount | number | Total findings from this scan |
| result.criticalCount | number | Number of critical severity findings |
| result.highCount | number | Number of high severity findings |
| result.ranAt | ISO 8601 string | When this execution ran |
Example
Request
POST /api/scheduled-scans/execute
Content-Type: application/json
{ "scheduleId": "schedule-1746123456-abc123" }Response
{
"success": true,
"result": {
"findingsCount": 5,
"criticalCount": 2,
"highCount": 2,
"ranAt": "2026-05-03T12:00:00Z"
}
}/api/workflowTrigger a durable WDK (Workflow Development Kit) scan workflow that enumerates assets from Google Workspace, GitHub, npm, and SaaS tools, then runs the AI scan agent, and posts Slack alerts for critical findings.
Request Body
| Field | Type | Description |
|---|---|---|
| trigger | 'manual-scan' | Trigger type — only 'manual-scan' is supported |
Response
| Field | Type | Description |
|---|---|---|
| runId | string | The durable workflow run ID for tracking |
| status | 'started' | Confirmation the workflow was started |
Example
Request
POST /api/workflow
Content-Type: application/json
{ "trigger": "manual-scan" }Response
{
"runId": "wf_9x3kZ1mNpQ2rT8",
"status": "started"
}| Status | Meaning | Common cause |
|---|---|---|
| 200 OK | Success | Request processed correctly |
| 400 Bad Request | Invalid request | Missing required fields or wrong types — check Zod schema errors in response |
| 401 Unauthorized | Authentication required | LINEAR_API_KEY or SLACK_WEBHOOK_URL not configured |
| 404 Not Found | Resource missing | Schedule ID not found in Upstash Redis |
| 429 Too Many Requests | Rate limited | NVD or GitHub APIs are rate-limited — the threat feed caches for 1 hour to mitigate this |
| 500 Internal Server Error | Server error | AI Gateway failure, upstream API error, or Upstash Redis connection issue |
/api/scan — Max 40 assets per request. AI Gateway token limits apply per model./api/threat-feed — Cached for 1 hour. NVD free tier: 5 requests/30s without API key./api/actions/* — Slack webhook: no hard limit. Linear: 1,500 req/hour./api/scheduled-scans/execute — Triggered by Vercel Cron every 15 minutes.View source, open issues, or contribute at github.com/HayreKhan750/oauthsentry