빠른 시작
앱의 Wire → API 키에서 키를 만든 뒤 어디서든 API를 호출하세요. 응답은 JSON이고 시각은 ISO-8601 UTC입니다.
curl -H "Authorization: Bearer rr_live_…" \
https://realityradar-7h6.pages.dev/api/v1/public/alerts?limit=5const res = await fetch('https://realityradar-7h6.pages.dev/api/v1/public/entities', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.RADAR_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ entityType: 'software_version', displayName: 'Node.js', modelOrVersion: '18.20.0',
identifiers: { eolProduct: 'nodejs' }, relationship: 'used' }),
});
console.log(res.status, await res.json());import os, requests
r = requests.get("https://realityradar-7h6.pages.dev/api/v1/public/events", params={"type": "product_recall", "limit": 20},
headers={"Authorization": f"Bearer {os.environ['RADAR_KEY']}"}, timeout=10)
r.raise_for_status()
for e in r.json()["items"]:
print(e["firstSeenAt"], e["title"], e["sourceUrl"])인증
키를 Bearer 토큰으로 보냅니다. 키는 한 번만 보여 주고 해시로만 저장하며, 30·90·365일 뒤 만료되게 할 수 있고 24시간 겹치게 교체할 수 있습니다.
Authorization: Bearer rr_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX권한 범위
키에는 고른 권한 범위만 붙습니다. 범위 밖 요청은 403 insufficient_scope를 돌려주므로 읽기 전용 대시보드 키로는 항목을 추가하거나 웹훅을 관리할 수 없습니다.
| 권한 범위 | 엔드포인트 |
|---|---|
read:signals | GET /public/eventsGET /public/events/{id} |
read:entities | GET /public/entities |
write:entities | POST /public/entities |
read:alerts | GET /public/alerts |
manage:webhooks | GET /public/webhooksPOST /public/webhooksDELETE /public/webhooks/{id}GET /public/webhooks/{id}/deliveriesPOST /public/webhooks/{id}/deliveries/{deliveryId}/replay |
요청 한도
표준 키는 분당 60회, 높은 한도 키는 분당 300회입니다. 429 응답이면 기다렸다가 다시 시도하세요. 키·엔드포인트별 사용량은 Wire에서 볼 수 있습니다.
페이지 나누기
목록 엔드포인트는 items와 nextCursor를 돌려줍니다. nextCursor가 null이 될 때까지 다시 넘기세요. 커서는 불투명하며 넘기는 동안 안정적입니다.
cursor = None
while True:
page = requests.get(f"https://realityradar-7h6.pages.dev/api/v1/public/events", headers=H, params={"limit": 100, "cursor": cursor}).json()
handle(page["items"])
cursor = page["nextCursor"]
if not cursor:
break오류
오류는 하나의 형식과 고정된 코드로 옵니다. 5xx와 429는 간격을 두고 재시도하고, 4xx는 요청을 고쳐야 합니다.
{ "error": { "code": "insufficient_scope", "message": "This key lacks the \"write:entities\" scope" } }invalid_api_key | 401 | 키가 없거나 형식이 틀렸거나 폐기됨 |
api_key_expired | 401 | 만료일 또는 교체 유예가 지남 |
insufficient_scope | 403 | 이 엔드포인트에 필요한 권한 범위가 없음 |
not_found | 404 | 없거나 이 워크스페이스에서 볼 수 없음 |
plan_limit | 409 | 무료 플랜 항목 수 한도에 도달 |
ssrf_blocked | 422 | URL이 사설·내부 주소를 가리킴 |
rate_limited | 429 | 요청이 너무 많음 — 잠시 후 다시 시도 |
웹훅
설정한 수준 이상의 알림이 생기면 JSON 이벤트를 POST로 보냅니다. 전송 기록은 30일간 상태·지연 시간과 함께 남고 한 번에 재전송할 수 있습니다.
POST /your/endpoint
Content-Type: application/json
User-Agent: RealityRadar-Webhook/1.0
X-Radar-Signature: t=1761112800,v1=5f2b…a91c,v1=03de…77b0
{"type":"alert.created","alert":{"impactLevel":"critical","matchType":"exact",…},"event":{…}}서명 검증
t, 점, 원본 본문을 비밀값으로 HMAC-SHA256 하고, v1 값 중 하나와 상수 시간 비교하세요. 5분보다 오래된 시각은 거부합니다. 비밀값을 교체하면 24시간 동안 v1이 두 개 옵니다.
import { createHmac, timingSafeEqual } from 'node:crypto';
// rawBody: the exact bytes you received (do not re-serialize JSON)
export function verifyRadar(header, rawBody, secret) {
const parts = header.split(',').map((p) => p.split('='));
const t = Number(parts.find(([k]) => k === 't')?.[1]);
const sigs = parts.filter(([k]) => k === 'v1').map(([, v]) => v);
if (!Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > 300) return false;
const mac = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
return sigs.some((v) => v.length === mac.length && timingSafeEqual(Buffer.from(v), Buffer.from(mac)));
}import hmac, hashlib, time
def verify_radar(header: str, raw_body: bytes, secret: str) -> bool:
parts = [p.split("=", 1) for p in header.split(",")]
t = next((int(v) for k, v in parts if k == "t"), None)
sigs = [v for k, v in parts if k == "v1"]
if t is None or abs(time.time() - t) > 300:
return False
mac = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
return any(hmac.compare_digest(mac, s) for s in sigs)func VerifyRadar(header string, rawBody []byte, secret string) bool {
var t int64 = -1
var sigs []string
for _, p := range strings.Split(header, ",") {
k, v, ok := strings.Cut(p, "=")
if !ok { continue }
if k == "t" { t, _ = strconv.ParseInt(v, 10, 64) }
if k == "v1" { sigs = append(sigs, v) }
}
if t < 0 || math.Abs(float64(time.Now().Unix()-t)) > 300 { return false }
m := hmac.New(sha256.New, []byte(secret))
fmt.Fprintf(m, "%d.", t)
m.Write(rawBody)
want := hex.EncodeToString(m.Sum(nil))
for _, s := range sigs {
if hmac.Equal([]byte(want), []byte(s)) { return true }
}
return false
}# Replay a delivery (fresh timestamp + signature)
curl -X POST -H "Authorization: Bearer rr_live_…" \
https://realityradar-7h6.pages.dev/api/v1/public/webhooks/WEBHOOK_ID/deliveries/DELIVERY_ID/replay의존성과 OSV
pkg:npm/lodash 같은 purl과 정확한 버전으로 패키지를 추적하세요. OSV.dev를 확인해 내 버전이 영향 목록에 있으면 정확히 일치로 표시합니다. lock 파일이나 SBOM은 앱의 가져오기에서 올릴 수 있습니다.
curl -X POST -H "Authorization: Bearer rr_live_…" -H "Content-Type: application/json" \
-d '{"entityType":"software_version","displayName":"lodash","modelOrVersion":"4.17.20","identifiers":{"purl":"pkg:npm/lodash"},"relationship":"used"}' \
https://realityradar-7h6.pages.dev/api/v1/public/entities용어
응답에서 보게 될 용어입니다.
- Signal (시그널)
- 공식 출처에서 찾은 변화 사건. 근거와 시각이 함께 있습니다.
- Beacon (비콘)
- 데이터 출처. A등급은 공식 규제기관·제조사, B등급은 검증된 데이터베이스입니다.
- 일치 유형
- exact·strong·probable·weak — 사건이 내 항목에 관한 것인지 얼마나 확실한지. exact와 strong만 확정으로 표현합니다.
- 관련성·신뢰도·심각도
- 따로 계산하는 0~100 점수 세 개: 나와 관련 있는가, 근거를 얼마나 믿을 수 있는가, 변화가 얼마나 심각한가.
- purl
- pkg:npm/lodash 같은 패키지 URL. OSV 매칭에 쓰는 표준 식별자입니다.
- 증거 원장
- 저장한 모든 근거의 해시 체인. 누구나 조작 여부를 검증할 수 있습니다.