Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
7b89349
feat(api): add new public routes and update API specification
senamakel Aug 19, 2026
594b12f
chore(api): update route counts and add webhook routes
senamakel Aug 19, 2026
0a032dc
fix(test): pin admin route count to prevent silent regressions
senamakel Aug 19, 2026
c2084bb
fix(tests): update expected operation counts in OpenAPI sync test
senamakel Aug 19, 2026
9346e78
chore(api): remove spend-caps and admin routes from public API
senamakel Aug 20, 2026
15e9aae
Revert "chore(api): remove spend-caps and admin routes from public API"
senamakel Aug 20, 2026
52020bb
chore(api): update route counts and add new admin route
senamakel Aug 20, 2026
697457d
fix(tests): update expected route counts after spec regeneration
senamakel Aug 20, 2026
3b4f787
chore(api): sync spec for the admin spend-cap ceiling route
senamakel Aug 20, 2026
a31b946
chore(scripts): update sync-openapi script to handle new endpoint format
senamakel Aug 20, 2026
f4c319a
fix(scripts): correct OpenAPI sync to handle missing spec file
senamakel Aug 20, 2026
f005bf2
chore(api): update tinyhumans backend configuration
senamakel Aug 20, 2026
46792ba
chore(api): update tinyhumans backend configuration
senamakel Aug 20, 2026
2b87d6f
fix(public_routes): correct route generation for nested modules
senamakel Aug 20, 2026
21393c3
fix(public_routes): correct route generation for nested modules
senamakel Aug 20, 2026
9a67171
chore(tests): add missing test file for OpenAPI sync
senamakel Aug 20, 2026
9fb5492
fix(parser): handle empty input in tokenizer
senamakel Aug 20, 2026
66353d8
Merge origin/main into feat/spend-caps (SDK socket, SSE, webhooks, me…
senamakel Aug 20, 2026
77d3c98
feat(api): promote team and webhook routes to public
senamakel Aug 20, 2026
f67f700
fix(tests): correct test assertion for OpenAPI sync
senamakel Aug 20, 2026
f104b4b
fix: handle missing file gracefully in lib.rs
senamakel Aug 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,15 @@ including legacy paths outside `/admin` whose summary, description, or 403
response marks them as admin-only. `node scripts/sync-openapi.mjs` must retain that filter
and regenerate both the manifest and Rust public-route registry.

`sync-openapi.mjs` defaults to fetching the deployed spec. When syncing a backend
branch that adds or changes routes, dump that checkout's spec first and pass it
with `--input` (`cd ../backend && npm run swagger -- /tmp/spec.json`); a bare run
regenerates from production and reverts the branch's routes back out. Feed it the
RAW document, never the filtered one served at `/swagger.json` — this script does
its own admin/webhook exclusion and derives `UNEXPOSED_ROUTES` from what it sees,
so a pre-filtered input shrinks that denylist and unblocks the routes it exists to
block. `UNEXPOSED_ROUTES.len()` is pinned in `src/lib.rs` to catch exactly that.

## Git and PR Expectations

- Keep changes small and coherent.
Expand Down
40 changes: 36 additions & 4 deletions api/tinyhumans.backend.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
"url": "https://api.tinyhumans.ai/swagger.json",
"title": "TinyHumans API",
"version": "1.0.0",
"pathCount": 167,
"totalOperationCount": 188,
"operationCount": 202,
"pathCount": 177,
"totalOperationCount": 198,
"operationCount": 212,
"supplementalOperationCount": 14,
"excludedAdminOperationCount": 32,
"excludedWebhookOperationCount": 12,
Expand Down Expand Up @@ -201,7 +201,7 @@
"name": "feedback",
"basePath": "/feedback",
"auth": "bearer",
"operationCount": 6,
"operationCount": 7,
"tags": [
"Feedback"
],
Expand All @@ -210,6 +210,7 @@
"GET /feedback/{id}",
"POST /feedback",
"POST /feedback/ingest",
"POST /feedback/validate",
"POST /feedback/{id}/comments",
"POST /feedback/{id}/vote"
]
Expand Down Expand Up @@ -450,6 +451,37 @@
"PUT /teams/{teamId}/members/{userId}/role"
]
},
{
"name": "voiceAgent",
"basePath": "/voice-agent",
"auth": "bearer",
"operationCount": 1,
"tags": [
"VoiceAgent"
],
"routes": [
"GET /voice-agent/get-signed-url"
]
},
{
"name": "waitlist",
"basePath": "/waitlist",
"auth": "none",
"operationCount": 8,
"tags": [
"Waitlist"
],
"routes": [
"GET /waitlist/download/{token}",
"GET /waitlist/me",
"GET /waitlist/stats",
"POST /waitlist/join",
"POST /waitlist/tasks/download",
"POST /waitlist/tasks/download/confirm",
"POST /waitlist/tasks/shoutout",
"POST /waitlist/tasks/shoutout/click"
]
},
{
"name": "webhooks",
"basePath": "/webhooks",
Expand Down
10 changes: 10 additions & 0 deletions scripts/sync-openapi.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,11 @@ function isWebhookPath(path) {
return path.split("/").includes("webhooks");
}

function isCustomLlmSecretOperation(operation) {
const security = operation.security ?? [];
return security.some((entry) => Object.hasOwn(entry, "customLlmSecret"));
}

function namespaceFor(path) {
if (path === "/") return "health";
const segment = path.split("/")[1];
Expand Down Expand Up @@ -233,6 +238,11 @@ function buildManifest(spec) {
excludedOperations.push({ method: method.toUpperCase(), path });
continue;
}
if (isCustomLlmSecretOperation(operation)) {
excludedAdminOperationCount += 1;
excludedOperations.push({ method: method.toUpperCase(), path });
continue;
}
publicOperations.push({
method: method.toUpperCase(),
namespace: namespaceFor(path),
Expand Down
10 changes: 10 additions & 0 deletions src/generated_public_routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ pub const PUBLIC_ROUTES: &[(&str, &str)] = &[
("POST", "/feedback/{id}/comments"),
("POST", "/feedback/{id}/vote"),
("POST", "/feedback/ingest"),
("POST", "/feedback/validate"),
("GET", "/invite/my-codes"),
("POST", "/invite/redeem"),
("GET", "/invite/status"),
Expand Down Expand Up @@ -198,6 +199,15 @@ pub const PUBLIC_ROUTES: &[(&str, &str)] = &[
("POST", "/teams/{teamId}/switch"),
("POST", "/teams/join"),
("GET", "/teams/me/usage"),
("GET", "/voice-agent/get-signed-url"),
("GET", "/waitlist/download/{token}"),
("POST", "/waitlist/join"),
("GET", "/waitlist/me"),
("GET", "/waitlist/stats"),
("POST", "/waitlist/tasks/download"),
("POST", "/waitlist/tasks/download/confirm"),
("POST", "/waitlist/tasks/shoutout"),
("POST", "/waitlist/tasks/shoutout/click"),
("GET", "/webhooks/core"),
("POST", "/webhooks/core"),
("DELETE", "/webhooks/core/{id}"),
Expand Down
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,9 @@ mod exclusion_tests {

#[test]
fn every_admin_and_webhook_route_is_rejected_by_the_raw_transport_gate() {
// Pinned so the list can only ever be reviewed upward. A regenerated
// spec that stopped describing admin or webhook routes would otherwise
// shrink this list and silently unblock them at the raw transport.
assert_eq!(UNEXPOSED_ROUTES.len(), 44);
for (method, template) in UNEXPOSED_ROUTES {
let concrete_path = template
Expand Down
4 changes: 2 additions & 2 deletions tests/openapi_sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,11 +134,11 @@ fn generated_rust_routes_match_the_public_manifest() {
.collect::<BTreeSet<_>>();
let rust_routes = PUBLIC_ROUTES.iter().copied().collect::<BTreeSet<_>>();

assert_eq!(manifest["source"]["operationCount"], 202);
assert_eq!(manifest["source"]["operationCount"], 212);
assert_eq!(manifest["source"]["supplementalOperationCount"], 14);
assert_eq!(manifest["source"]["excludedAdminOperationCount"], 32);
assert_eq!(manifest["source"]["excludedWebhookOperationCount"], 12);
assert_eq!(rust_routes.len(), 202);
assert_eq!(rust_routes.len(), 212);
assert_eq!(rust_routes, manifest_routes);
assert!(rust_routes
.iter()
Expand Down
Loading