{"openapi":"3.1.0","info":{"title":"RootNode API","version":"0.1.0","description":"Implemented MVP endpoints. Administrative access requires a verified Firebase user and server-side workspace membership. Full policy input contracts are in packages/types."},"servers":[{"url":"https://api.admin.rootnode.kr"}],"components":{"securitySchemes":{"firebaseSession":{"type":"apiKey","in":"header","name":"Authorization","description":"Session <Firebase session cookie> or Bearer <Firebase ID token>"},"sdkKey":{"type":"http","scheme":"bearer","description":"Server-only API key; also send X-RootNode-Site"}}},"paths":{"/health":{"get":{"summary":"Liveness only","security":[],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"data":{}}}}}},"400":{"description":"Invalid input"},"401":{"description":"Authentication required"},"403":{"description":"Insufficient role or unverified site"},"503":{"description":"Dependency unavailable"}}}},"/api/v1/auth/google/session":{"post":{"summary":"Exchange a verified Google ID token for an existing administrator session","description":"Google OAuth code, state, PKCE and nonce are handled by the Next.js dashboard. Firebase preserves account UID and session revocation. Membership is required.","responses":{"200":{"description":"Administrator session"},"401":{"description":"Invalid Google identity"},"403":{"description":"Not an approved administrator"}}}},"/api/v1/auth/session":{"post":{"summary":"Create a 12-hour administrator session","security":[],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"data":{}}}}}},"400":{"description":"Invalid input"},"401":{"description":"Authentication required"},"403":{"description":"Insufficient role or unverified site"},"503":{"description":"Dependency unavailable"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["idToken"],"properties":{"idToken":{"type":"string"}}}}}}}},"/api/v1/sites":{"get":{"summary":"List current workspace sites","security":[{"firebaseSession":[]}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"data":{}}}}}},"400":{"description":"Invalid input"},"401":{"description":"Authentication required"},"403":{"description":"Insufficient role or unverified site"},"503":{"description":"Dependency unavailable"}}},"post":{"summary":"Register an unverified site","security":[{"firebaseSession":[]}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"data":{}}}}}},"400":{"description":"Invalid input"},"401":{"description":"Authentication required"},"403":{"description":"Insufficient role or unverified site"},"503":{"description":"Dependency unavailable"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","url"],"properties":{"name":{"type":"string","maxLength":80},"url":{"type":"string","format":"uri"},"platform":{"type":"string"},"framework":{"type":"string"}}}}}}}},"/api/v1/sites/{siteId}/verify":{"parameters":[{"name":"siteId","in":"path","required":true,"schema":{"type":"string"}}],"post":{"summary":"Verify ownership using DNS TXT or pinned public HTTPS verification file","security":[{"firebaseSession":[]}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"data":{}}}}}},"400":{"description":"Invalid input"},"401":{"description":"Authentication required"},"403":{"description":"Insufficient role or unverified site"},"503":{"description":"Dependency unavailable"}}}},"/api/v1/sites/{siteId}/policy":{"parameters":[{"name":"siteId","in":"path","required":true,"schema":{"type":"string"}}],"patch":{"summary":"Update a verified site policy and increment its version","security":[{"firebaseSession":[]}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"data":{}}}}}},"400":{"description":"Invalid input"},"401":{"description":"Authentication required"},"403":{"description":"Insufficient role or unverified site"},"503":{"description":"Dependency unavailable"}}}},"/api/v1/events":{"get":{"summary":"Read latest 100 current-workspace events","security":[{"firebaseSession":[]}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"data":{}}}}}},"400":{"description":"Invalid input"},"401":{"description":"Authentication required"},"403":{"description":"Insufficient role or unverified site"},"503":{"description":"Dependency unavailable"}}}},"/api/v1/keys":{"get":{"summary":"List key metadata without secrets","security":[{"firebaseSession":[]}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"data":{}}}}}},"400":{"description":"Invalid input"},"401":{"description":"Authentication required"},"403":{"description":"Insufficient role or unverified site"},"503":{"description":"Dependency unavailable"}}},"post":{"summary":"Owner-only key issuance","security":[{"firebaseSession":[]}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"data":{}}}}}},"400":{"description":"Invalid input"},"401":{"description":"Authentication required"},"403":{"description":"Insufficient role or unverified site"},"503":{"description":"Dependency unavailable"}}}},"/api/v1/keys/{keyId}/revoke":{"parameters":[{"name":"keyId","in":"path","required":true,"schema":{"type":"string"}}],"post":{"summary":"Owner-only credential revocation","security":[{"firebaseSession":[]}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"data":{}}}}}},"400":{"description":"Invalid input"},"401":{"description":"Authentication required"},"403":{"description":"Insufficient role or unverified site"},"503":{"description":"Dependency unavailable"}}}},"/api/v1/sdk/policy":{"get":{"summary":"Fetch a site policy with ETag support","security":[{"sdkKey":[]}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"data":{}}}}}},"400":{"description":"Invalid input"},"401":{"description":"Authentication required"},"403":{"description":"Insufficient role or unverified site"},"503":{"description":"Dependency unavailable"}}}},"/api/v1/sdk/enforce":{"post":{"summary":"Evaluate shared Firestore rate limit and IP rules","security":[{"sdkKey":[]}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"data":{}}}}}},"400":{"description":"Invalid input"},"401":{"description":"Authentication required"},"403":{"description":"Insufficient role or unverified site"},"503":{"description":"Dependency unavailable"}}}},"/api/v1/sdk/challenge/start":{"post":{"summary":"Create a signed short-lived challenge request","security":[{"sdkKey":[]}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"data":{}}}}}},"400":{"description":"Invalid input"},"401":{"description":"Authentication required"},"403":{"description":"Insufficient role or unverified site"},"503":{"description":"Dependency unavailable"}}}},"/api/v1/challenges/verify":{"post":{"summary":"Probe own CAPTCHA, refresh an image, or submit six characters","security":[],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"data":{}}}}}},"400":{"description":"Invalid input"},"401":{"description":"Authentication required"},"403":{"description":"Insufficient role or unverified site"},"503":{"description":"Dependency unavailable"}},"description":"Signed request required. Empty normally timed honeypots pass a basic check; suspicious requests require a PNG image. Expiry 180s, five total guesses, five generated images. Refresh does not reset attempts. This is not identity authentication.","requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"type":"object","additionalProperties":false,"required":["phase","request","honeypot"],"properties":{"phase":{"const":"probe"},"request":{"type":"string","maxLength":8192},"honeypot":{"type":"string","maxLength":256}}},{"type":"object","additionalProperties":false,"required":["phase","request"],"properties":{"phase":{"const":"refresh"},"request":{"type":"string","maxLength":8192}}},{"type":"object","additionalProperties":false,"required":["phase","request","imageToken","answer"],"properties":{"phase":{"const":"answer"},"request":{"type":"string","maxLength":8192},"imageToken":{"type":"string","maxLength":8192},"answer":{"type":"string","pattern":"^[A-Za-z0-9]{6}$"}}}]}}}}}},"/api/v1/challenges/image":{"get":{"summary":"Fetch an active, signed CAPTCHA image","security":[],"parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","maxLength":8192}}],"responses":{"200":{"description":"Private, no-store raster PNG","content":{"image/png":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"Invalid, expired or replaced image"},"429":{"description":"Image request limit"}}}},"/api/v1/sdk/challenge/exchange":{"post":{"summary":"Consume a site-bound one-time verification code","security":[{"sdkKey":[]}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"data":{}}}}}},"400":{"description":"Invalid input"},"401":{"description":"Authentication required"},"403":{"description":"Insufficient role or unverified site"},"503":{"description":"Dependency unavailable"}}}}}}