The API is defined in a single file using Effect’s HttpApi:
import { HttpApi, HttpApiEndpoint, HttpApiGroup } from "@effect/platform";
import { Schema } from "effect";
const ScoutInput = Schema.Struct({
url: Schema.String,
task: Schema.String,
});
const ScoutResult = Schema.Struct({
siteId: Schema.String,
endpointCount: Schema.Number,
pathId: Schema.String,
openApiSpec: Schema.Unknown,
});
const WorkerInput = Schema.Struct({
pathId: Schema.String,
data: Schema.optional(Schema.Record({ key: Schema.String, value: Schema.Unknown })),
});
const WorkerResult = Schema.Struct({
success: Schema.Boolean,
response: Schema.optional(Schema.Unknown),
});
const HealInput = Schema.Struct({
pathId: Schema.String,
error: Schema.optional(Schema.String),
});
const HealResult = Schema.Struct({
healed: Schema.Boolean,
newPathId: Schema.optional(Schema.String),
});
const ToolsGroup = HttpApiGroup.make("Tools")
.add(HttpApiEndpoint.post("scout", "/tools/scout").addSuccess(ScoutResult).setPayload(ScoutInput))
.add(
HttpApiEndpoint.post("worker", "/tools/worker")
.addSuccess(WorkerResult)
.setPayload(WorkerInput),
)
.add(HttpApiEndpoint.post("heal", "/tools/heal").addSuccess(HealResult).setPayload(HealInput));
export const UnsurfApi = HttpApi.make("unsurf").add(ToolsGroup);
Explore a website and capture its API endpoints.
| Field | Type | Required | Description |
|---|
url | string | ✅ | Target URL to scout |
task | string | ✅ | Natural language description of what to find |
auth | object | | Authentication config |
auth.cookies | string | | Cookie header value |
auth.headers | Record<string, string> | | Additional request headers |
| Field | Type | Description |
|---|
siteId | string | Unique identifier for the scouted site |
endpointCount | number | Number of unique endpoints captured |
pathId | string | Replayable navigation path ID |
openApiSpec | object | OpenAPI 3.1 specification |
| Error | When |
|---|
BrowserError | Browser container failed to launch or crashed |
NetworkError | Target site unreachable |
StoreError | Failed to save results to D1/R2 |
"description": "Explore a website and capture its API endpoints",
"required": ["url", "task"],
"url": { "type": "string", "description": "Target URL to scout" },
"task": { "type": "string", "description": "What to find on the site" }
Execute a scouted path or replay a captured API endpoint.
| Field | Type | Required | Description |
|---|
pathId | string | ✅ | ID of the path to execute |
data | Record<string, unknown> | | Data to pass to the endpoint |
| Field | Type | Description |
|---|
success | boolean | Whether execution succeeded |
response | unknown | Response body from the target API |
| Error | When |
|---|
NotFoundError | Path ID does not exist |
PathBrokenError | Path is broken — target site changed |
NetworkError | Target API unreachable |
BrowserError | Browser fallback failed |
"description": "Execute a scouted path or replay a captured API endpoint",
"pathId": { "type": "string", "description": "Path ID from a previous scout" },
"data": { "type": "object", "description": "Data to send to the endpoint" }
Re-scout and repair a broken path.
| Field | Type | Required | Description |
|---|
pathId | string | ✅ | ID of the broken path |
error | string | | Description of what went wrong |
| Field | Type | Description |
|---|
healed | boolean | Whether the path was successfully repaired |
newPathId | string | ID of the new path (if changed) |
| Error | When |
|---|
NotFoundError | Path ID does not exist |
BrowserError | Re-scout browser failed |
NetworkError | Target site unreachable |
"description": "Re-scout and repair a broken path",
"pathId": { "type": "string", "description": "ID of the broken path" },
"error": { "type": "string", "description": "What went wrong" }