16
171. Click **Fork** on this val to create your own copy
182. Get your API key from the [Braintrust settings page](https://www.braintrust.dev/app/settings?subroute=api-keys)
193. Add your API key to your project's [Environment Variables](https://www.val.town/settings/environment-variables) as `BRAINTRUST_API_KEY`
204. Click **Run** on the `tutorial` val to start experimenting
21
16 );
17
18 const client = new OpenAI({ apiKey: process.env.PERPLEXITY_API_KEY, baseURL: "https://api.perplexity.ai" });
19 const response = await client.chat.completions.create({
20 model: "sonar",
187 },
188 "redirects": {
189 "https://esm.town/v/std/API_URL": "https://esm.town/v/std/API_URL?v=5",
190 "https://esm.town/v/stevekrouse/sqlite": "https://esm.town/v/stevekrouse/sqlite?v=13"
191 },
192 "remote": {
193 "https://esm.town/v/std/API_URL?v=5": "46109f905a50e32281d3ffbe7b9c8209a778290c5274d83d131fba2d26c4974d",
194 "https://esm.town/v/stevekrouse/sqlite?v=13": "3b613197e9da35db335dc2726c062c61f9247439c10a0c64c118994e200ffa46"
195 }
4export function App() {
5 const [projectUrl, setProjectUrl] = useState("");
6 const [apiToken, setApiToken] = useState("");
7 const [loading, setLoading] = useState(false);
8 const [message, setMessage] = useState("");
21 }
22
23 if (!apiToken.trim()) {
24 throw new Error("API Token is required");
25 }
26
28 method: "POST",
29 headers: {
30 "Authorization": `Bearer ${apiToken}`,
31 "Content-Type": "application/json",
32 },
63 value={projectUrl}
64 onChange={(e) => setProjectUrl(e.target.value)}
65 placeholder="https://api.val.town/v1/projects/..."
66 className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-purple-500"
67 />
69
70 <div>
71 <label htmlFor="apiToken" className="block text-sm font-medium text-gray-700 mb-1">
72 Val Town API Token (project read + write permissions)
73 </label>
74 <input
75 id="apiToken"
76 type="password"
77 value={apiToken}
78 onChange={(e) => setApiToken(e.target.value)}
79 placeholder="Enter your API token"
80 className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-purple-500"
81 />
193app.get("/frontend/**/*", c => serveFile(c.req.path, import.meta.url));
194
195// Add your API routes here
196// app.get("/api/data", c => c.json({ hello: "world" }));
197
198// Unwrap and rethrow Hono errors as the original error
1# Research Agent over email
2
3This is a lightweight wrapper around Perplexity's web search API that you can interact with via email.
4export function App() {
5 const [projectUrl, setProjectUrl] = useState("");
6 const [apiToken, setApiToken] = useState("");
7 const [loading, setLoading] = useState(false);
8 const [message, setMessage] = useState("");
21 }
22
23 if (!apiToken.trim()) {
24 throw new Error("API Token is required");
25 }
26
28 method: "POST",
29 headers: {
30 "Authorization": `Bearer ${apiToken}`,
31 "Content-Type": "application/json",
32 },
69
70 <div>
71 <label htmlFor="apiToken" className="block text-sm font-medium text-gray-700 mb-1">
72 Val Town API Token (project read + write permissions)
73 </label>
74 <input
75 id="apiToken"
76 type="password"
77 value={apiToken}
78 onChange={(e) => setApiToken(e.target.value)}
79 placeholder="Enter your API token"
80 className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-purple-500"
81 />
1import { createRoute } from "https://esm.sh/@hono/zod-openapi@0.18.4";
2import { ErrorSchema, UpdateUserSchema, UserIdPathParamSchema, UserSchema } from "../schema.ts";
3
1import { z } from "https://esm.sh/@hono/zod-openapi@0.18.4";
2
3// Schema that defines presence of an ID in the path
6 .coerce
7 .number()
8 .openapi({ example: 1 }),
9});
10
12export const NewUserSchema = z
13 .object({
14 name: z.string().min(2).max(50).openapi({ example: "Mark Scout" }),
15 email: z.string().email().openapi({ example: "mark@lumen.co" }),
16 age: z.number().int().min(18).max(120).openapi({ example: 35 }),
17 }).openapi("NewUser");
18
19// Schema that defines the response of a request to get a user
21 .object({
22 id: z.number().int(),
23 name: z.string().openapi({ example: "Mark Scout" }),
24 email: z.string().email().openapi({ example: "mark@lumen.co" }),
25 age: z.number().int().openapi({ example: 35 }),
26 }).openapi("User");
27
28export const UpdateUserSchema = z
29 .object({
30 name: z.string().optional().openapi({
31 example: "Marcus Scoutius",
32 }),
33 age: z.number().int().optional().openapi({
34 example: 53,
35 }),
36 })
37 .openapi("UpdateUser");
38
39// Error schema
1**Example Hono-Zod-OpenAPI app with a Fiberplane API explorer.**
2
3> For an example with regular-old Hono, see: https://www.val.town/v/fiberplane/fiberplaneHonoStarter
8 ```
9
102. Expose your OpenAPI spec
11 ```ts
12 app.doc("/doc", {
13 openapi: "3.0.0",
14 info: {
15 title: "User Management API",
16 version: "v1.0.0",
17 },
19 ```
20
213. Mount the api explorer
22
23 This will mount it at the root `/*`, but you can mount it to another route, like `/fp/*` if you
24 are using `/` for your main app. We recommend `/` if your Hono app is an API without a frontend.
25
26 ```ts
28 "/*",
29 createFiberplane({
30 openapi: { url: "/doc" },
31 }),
32 );
33 ```
34
354. Visit your Val's root route to play with the API explorer!
36
37## How it Works
38
39`createFiberplane` mounts Fiberpalne at the root route (`/`), which can be used to explore the api's routes and make requests.
40Think of it like an embedded, lightweight postman.