[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6F66N7X4AASCZVQ95VVM246":3,"skill-same:01M6F66N7X4AASCZVQ95VVM246":84},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":27,"publishedAt":49,"linkOnly":50,"markdownContent":51,"markdownBody":52,"frontMatter":53,"refName":41,"commitSha":58,"skillDirectory":59,"forksCount":60,"repositoryHtmlUrl":61,"repositoryHomepageUrl":6,"ownerHtmlUrl":62,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":63,"install":64,"resources":77},"01M6F66N7X4AASCZVQ95VVM246","expo-api-routes",null,"Guidelines for creating API routes in Expo Router with EAS Hosting","boisenoise\u002Fskills-collections","boisenoise","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F193180?v=4","01M6CDBDQK6BDN55ZMA6B6PKBM","01M6CDBDVSJ3JPTBYN26FZ7TZK",12,"MIT","https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-expo-api-routes\u002FSKILL.md",[17,22],{"categoryNo":18,"categoryName":19,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000010","后端与接口","SCENARIO","使用场景",{"categoryNo":23,"categoryName":24,"taxonomyType":25,"taxonomyTypeDesc":26},"01JBSEEDCAT000000000000030","开发工程师","PROFESSION","适用职业",[28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48],"expo api routes","api","expo","2026","github","blob","for","tree","eas hosting","with","expo router","critical","wed","main","hosting","router","plugins","https","license","guidelines","official","2026-11-10 10:12:30",false,"---\nname: expo-api-routes\ndescription: Guidelines for creating API routes in Expo Router with EAS Hosting\nrisk: critical\nsource: https:\u002F\u002Fgithub.com\u002Fexpo\u002Fskills\u002Ftree\u002Fmain\u002Fplugins\u002Fexpo\u002Fskills\u002Fexpo-api-routes\nsource_repo: expo\u002Fskills\nsource_type: official\ndate_added: 2026-07-01\nlicense: MIT\nlicense_source: https:\u002F\u002Fgithub.com\u002Fexpo\u002Fskills\u002Fblob\u002Fmain\u002FLICENSE\n---\n\n## When to Use API Routes\n\nUse API routes when you need:\n\n- **Server-side secrets** — API keys, database credentials, or tokens that must never reach the client\n- **Database operations** — Direct database queries that shouldn't be exposed\n- **Third-party API proxies** — Hide API keys when calling external services (OpenAI, Stripe, etc.)\n- **Server-side validation** — Validate data before database writes\n- **Webhook endpoints** — Receive callbacks from services like Stripe or GitHub\n- **Rate limiting** — Control access at the server level\n- **Heavy computation** — Offload processing that would be slow on mobile\n\n## When NOT to Use API Routes\n\nAvoid API routes when:\n\n- **Data is already public** — Use direct fetch to public APIs instead\n- **No secrets required** — Static data or client-safe operations\n- **Real-time updates needed** — Use WebSockets or services like Supabase Realtime\n- **Simple CRUD** — Consider Firebase, Supabase, or Convex for managed backends\n- **File uploads** — Use direct-to-storage uploads (S3 presigned URLs, Cloudflare R2)\n- **Authentication only** — Use Clerk, Auth0, or Firebase Auth instead\n\n## File Structure\n\nConfirm the installed Expo\u002FEAS version and configure web output for server routes. Fragments are incomplete demos: header presence and nonempty password fields do not authenticate anyone. Implement token verification, per-user authorization, body\u002Fschema\u002Fsize limits and rate\u002Fcost limits before exposing database or paid AI proxy routes. Keep secrets out of EXPO_PUBLIC variables\u002Fclient imports. Native callers need the configured server origin; do not assume a relative URL works identically on every platform. Validate webhook signatures over the required raw body and deduplicate events. Bound upstream requests, check response.ok and return only approved fields; the weather endpoint is illustrative, not a verified API contract.\n\nAPI routes live in the `app` directory with `+api.ts` suffix:\n\n```\napp\u002F\n  api\u002F\n    hello+api.ts          → GET \u002Fapi\u002Fhello\n    users+api.ts          → \u002Fapi\u002Fusers\n    users\u002F[id]+api.ts     → \u002Fapi\u002Fusers\u002F:id\n  (tabs)\u002F\n    index.tsx\n```\n\n## Basic API Route\n\n```ts\n\u002F\u002F app\u002Fapi\u002Fhello+api.ts\nexport function GET(request: Request) {\n  return Response.json({ message: \"Hello from Expo!\" });\n}\n```\n\n## HTTP Methods\n\nExport named functions for each HTTP method:\n\n```ts\n\u002F\u002F app\u002Fapi\u002Fitems+api.ts\nexport function GET(request: Request) {\n  return Response.json({ items: [] });\n}\n\nexport async function POST(request: Request) {\n  const body = await request.json();\n  return Response.json({ created: body }, { status: 201 });\n}\n\nexport async function PUT(request: Request) {\n  const body = await request.json();\n  return Response.json({ updated: body });\n}\n\nexport async function DELETE(request: Request) {\n  return new Response(null, { status: 204 });\n}\n```\n\n## Dynamic Routes\n\n```ts\n\u002F\u002F app\u002Fapi\u002Fusers\u002F[id]+api.ts\nexport function GET(request: Request, { id }: { id: string }) {\n  return Response.json({ userId: id });\n}\n```\n\n## Request Handling\n\n### Query Parameters\n\n```ts\nexport function GET(request: Request) {\n  const url = new URL(request.url);\n  const page = url.searchParams.get(\"page\") ?? \"1\";\n  const limit = url.searchParams.get(\"limit\") ?? \"10\";\n\n  return Response.json({ page, limit });\n}\n```\n\n### Headers\n\n```ts\nexport async function GET(request: Request) {\n  const auth = request.headers.get(\"Authorization\");\n\n  if (!auth || !(await verifyAccessToken(auth))) { \u002F\u002F implement signature\u002Fissuer\u002Faudience\u002Fexpiry checks\n    return Response.json({ error: \"Unauthorized\" }, { status: 401 });\n  }\n\n  return Response.json({ authenticated: true });\n}\n```\n\n### JSON Body\n\n```ts\nexport async function POST(request: Request) {\n  const { email, password } = await request.json();\n\n  if (!email || !password) {\n    return Response.json({ error: \"Missing fields\" }, { status: 400 });\n  }\n\n  return Response.json({ success: true });\n}\n```\n\n## Environment Variables\n\nUse `process.env` for server-side secrets:\n\n```ts\n\u002F\u002F app\u002Fapi\u002Fai+api.ts\nexport async function POST(request: Request) {\n  const { prompt } = await request.json();\n\n  const response = await fetch(\"https:\u002F\u002Fapi.openai.com\u002Fv1\u002Fchat\u002Fcompletions\", {\n    method: \"POST\",\n    headers: {\n      \"Content-Type\": \"application\u002Fjson\",\n      Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,\n    },\n    body: JSON.stringify({\n      model: \"gpt-4\",\n      messages: [{ role: \"user\", content: prompt }],\n    }),\n  });\n\n  const data = await response.json();\n  return Response.json(data);\n}\n```\n\nSet environment variables:\n\n- **Local**: Create `.env` file (never commit)\n- **EAS Hosting**: Use `eas env:create` or Expo dashboard\n\n## CORS Headers\n\nAdd CORS for web clients:\n\n```ts\nconst corsHeaders = {\n  \"Access-Control-Allow-Origin\": \"*\",\n  \"Access-Control-Allow-Methods\": \"GET, POST, PUT, DELETE, OPTIONS\",\n  \"Access-Control-Allow-Headers\": \"Content-Type, Authorization\",\n};\n\nexport function OPTIONS() {\n  return new Response(null, { headers: corsHeaders });\n}\n\nexport function GET() {\n  return Response.json({ data: \"value\" }, { headers: corsHeaders });\n}\n```\n\n## Error Handling\n\n```ts\nexport async function POST(request: Request) {\n  try {\n    const body = await request.json();\n    \u002F\u002F Process...\n    return Response.json({ success: true });\n  } catch (error) {\n    console.error(\"API error:\", error);\n    return Response.json({ error: \"Internal server error\" }, { status: 500 });\n  }\n}\n```\n\n## Testing Locally\n\nStart the development server with API routes:\n\n```bash\nnpx expo start\n# To test an exported production bundle instead:\n# npx expo export --platform web\n# npx expo serve\n```\n\nThis starts a local server at `http:\u002F\u002Flocalhost:8081` with full API route support.\n\nTest with curl:\n\n```bash\ncurl http:\u002F\u002Flocalhost:8081\u002Fapi\u002Fhello\ncurl -X POST http:\u002F\u002Flocalhost:8081\u002Fapi\u002Fusers -H \"Content-Type: application\u002Fjson\" -d '{\"name\":\"Test\"}'\n```\n\n## Deployment to EAS Hosting\n\n### Prerequisites\n\n```bash\nnpm install -g eas-cli\neas login\n```\n\n### Deploy\n\n```bash\neas deploy\n```\n\nExport the configured server web output first, then deploy that artifact with the supported EAS CLI; deployment is a separate authorized public action.\n\n### Environment Variables for Production\n\n```bash\n# Create a secret\neas env:create --name OPENAI_API_KEY --environment production\n# Use the supported interactive secret input; never pass real keys in command arguments.\n\n# Or use the Expo dashboard\n```\n\n### Custom Domain\n\nConfigure in `eas.json` or Expo dashboard.\n\n## EAS Hosting Runtime (Cloudflare Workers)\n\nAPI routes run on Cloudflare Workers. Key limitations:\n\n### Missing\u002FLimited APIs\n\n- **No Node.js filesystem** — `fs` module unavailable\n- **No native Node modules** — Use Web APIs or polyfills\n- **Limited execution time** — 30 second timeout for CPU-intensive tasks\n- **No persistent connections** — WebSockets require Durable Objects\n- **fetch is available** — Use standard fetch for HTTP requests\n\n### Use Web APIs Instead\n\n```ts\n\u002F\u002F Use Web Crypto instead of Node crypto\nconst hash = await crypto.subtle.digest(\n  \"SHA-256\",\n  new TextEncoder().encode(\"data\")\n);\n\n\u002F\u002F Use fetch instead of node-fetch\nconst response = await fetch(\"https:\u002F\u002Fapi.example.com\");\n\n\u002F\u002F Use Response\u002FRequest (already available)\nreturn new Response(JSON.stringify(data), {\n  headers: { \"Content-Type\": \"application\u002Fjson\" },\n});\n```\n\n### Database Options\n\nSince filesystem is unavailable, use cloud databases:\n\n- **Cloudflare D1** — SQLite at the edge\n- **Turso** — Distributed SQLite\n- **PlanetScale** — Serverless MySQL\n- **Supabase** — Postgres with REST API\n- **Neon** — Serverless Postgres\n\nExample with Turso:\n\n```ts\n\u002F\u002F app\u002Fapi\u002Fusers+api.ts\nimport { createClient } from \"@libsql\u002Fclient\u002Fweb\";\n\nconst db = createClient({\n  url: process.env.TURSO_URL!,\n  authToken: process.env.TURSO_AUTH_TOKEN!,\n});\n\nexport async function GET() {\n  const result = await db.execute(\"SELECT * FROM users\");\n  return Response.json(result.rows);\n}\n```\n\n## Calling API Routes from Client\n\n```ts\n\u002F\u002F From React Native components\nconst response = await fetch(\"\u002Fapi\u002Fhello\");\nconst data = await response.json();\n\n\u002F\u002F With body\nconst response = await fetch(\"\u002Fapi\u002Fusers\", {\n  method: \"POST\",\n  headers: { \"Content-Type\": \"application\u002Fjson\" },\n  body: JSON.stringify({ name: \"John\" }),\n});\n```\n\n## Common Patterns\n\n### Authentication Middleware\n\n```ts\n\u002F\u002F utils\u002Fauth.ts\nexport async function requireAuth(request: Request) {\n  const token = request.headers.get(\"Authorization\")?.replace(\"Bearer \", \"\");\n\n  if (!token) {\n    throw new Response(JSON.stringify({ error: \"Unauthorized\" }), {\n      status: 401,\n      headers: { \"Content-Type\": \"application\u002Fjson\" },\n    });\n  }\n\n  \u002F\u002F Implement with the application identity provider; fail closed until implemented.\n  throw new Error(\"Token verification must be implemented; never return a fabricated user\");\n}\n\n\u002F\u002F app\u002Fapi\u002Fprotected+api.ts\nimport { requireAuth } from \"..\u002F..\u002Futils\u002Fauth\";\n\nexport async function GET(request: Request) {\n  const { userId } = await requireAuth(request);\n  return Response.json({ userId });\n}\n```\n\n### Proxy External API\n\n```ts\n\u002F\u002F app\u002Fapi\u002Fweather+api.ts\nexport async function GET(request: Request) {\n  const url = new URL(request.url);\n  const city = url.searchParams.get(\"city\");\n\n  const response = await fetch(\n    `https:\u002F\u002Fapi.weather.com\u002Fv1\u002Fcurrent?city=${city}&key=${process.env.WEATHER_API_KEY}`\n  );\n\n  return Response.json(await response.json());\n}\n```\n\n## Rules\n\n- NEVER expose API keys or secrets in client code\n- ALWAYS validate and sanitize user input\n- Use proper HTTP status codes (200, 201, 400, 401, 404, 500)\n- Handle errors gracefully with try\u002Fcatch\n- Keep API routes focused — one responsibility per endpoint\n- Use TypeScript for type safety\n- Log errors server-side for debugging\n\n## Limitations\n\n- Use this skill only when the task clearly matches its upstream product or API scope.\n- Verify commands, API behavior, pricing, quotas, credentials, and deployment effects against current official documentation before making changes.\n- Do not treat generated examples as a substitute for environment-specific tests, security review, or user approval for destructive or costly actions.\n","## When to Use API Routes\n\nUse API routes when you need:\n\n- **Server-side secrets** — API keys, database credentials, or tokens that must never reach the client\n- **Database operations** — Direct database queries that shouldn't be exposed\n- **Third-party API proxies** — Hide API keys when calling external services (OpenAI, Stripe, etc.)\n- **Server-side validation** — Validate data before database writes\n- **Webhook endpoints** — Receive callbacks from services like Stripe or GitHub\n- **Rate limiting** — Control access at the server level\n- **Heavy computation** — Offload processing that would be slow on mobile\n\n## When NOT to Use API Routes\n\nAvoid API routes when:\n\n- **Data is already public** — Use direct fetch to public APIs instead\n- **No secrets required** — Static data or client-safe operations\n- **Real-time updates needed** — Use WebSockets or services like Supabase Realtime\n- **Simple CRUD** — Consider Firebase, Supabase, or Convex for managed backends\n- **File uploads** — Use direct-to-storage uploads (S3 presigned URLs, Cloudflare R2)\n- **Authentication only** — Use Clerk, Auth0, or Firebase Auth instead\n\n## File Structure\n\nConfirm the installed Expo\u002FEAS version and configure web output for server routes. Fragments are incomplete demos: header presence and nonempty password fields do not authenticate anyone. Implement token verification, per-user authorization, body\u002Fschema\u002Fsize limits and rate\u002Fcost limits before exposing database or paid AI proxy routes. Keep secrets out of EXPO_PUBLIC variables\u002Fclient imports. Native callers need the configured server origin; do not assume a relative URL works identically on every platform. Validate webhook signatures over the required raw body and deduplicate events. Bound upstream requests, check response.ok and return only approved fields; the weather endpoint is illustrative, not a verified API contract.\n\nAPI routes live in the `app` directory with `+api.ts` suffix:\n\n```\napp\u002F\n  api\u002F\n    hello+api.ts          → GET \u002Fapi\u002Fhello\n    users+api.ts          → \u002Fapi\u002Fusers\n    users\u002F[id]+api.ts     → \u002Fapi\u002Fusers\u002F:id\n  (tabs)\u002F\n    index.tsx\n```\n\n## Basic API Route\n\n```ts\n\u002F\u002F app\u002Fapi\u002Fhello+api.ts\nexport function GET(request: Request) {\n  return Response.json({ message: \"Hello from Expo!\" });\n}\n```\n\n## HTTP Methods\n\nExport named functions for each HTTP method:\n\n```ts\n\u002F\u002F app\u002Fapi\u002Fitems+api.ts\nexport function GET(request: Request) {\n  return Response.json({ items: [] });\n}\n\nexport async function POST(request: Request) {\n  const body = await request.json();\n  return Response.json({ created: body }, { status: 201 });\n}\n\nexport async function PUT(request: Request) {\n  const body = await request.json();\n  return Response.json({ updated: body });\n}\n\nexport async function DELETE(request: Request) {\n  return new Response(null, { status: 204 });\n}\n```\n\n## Dynamic Routes\n\n```ts\n\u002F\u002F app\u002Fapi\u002Fusers\u002F[id]+api.ts\nexport function GET(request: Request, { id }: { id: string }) {\n  return Response.json({ userId: id });\n}\n```\n\n## Request Handling\n\n### Query Parameters\n\n```ts\nexport function GET(request: Request) {\n  const url = new URL(request.url);\n  const page = url.searchParams.get(\"page\") ?? \"1\";\n  const limit = url.searchParams.get(\"limit\") ?? \"10\";\n\n  return Response.json({ page, limit });\n}\n```\n\n### Headers\n\n```ts\nexport async function GET(request: Request) {\n  const auth = request.headers.get(\"Authorization\");\n\n  if (!auth || !(await verifyAccessToken(auth))) { \u002F\u002F implement signature\u002Fissuer\u002Faudience\u002Fexpiry checks\n    return Response.json({ error: \"Unauthorized\" }, { status: 401 });\n  }\n\n  return Response.json({ authenticated: true });\n}\n```\n\n### JSON Body\n\n```ts\nexport async function POST(request: Request) {\n  const { email, password } = await request.json();\n\n  if (!email || !password) {\n    return Response.json({ error: \"Missing fields\" }, { status: 400 });\n  }\n\n  return Response.json({ success: true });\n}\n```\n\n## Environment Variables\n\nUse `process.env` for server-side secrets:\n\n```ts\n\u002F\u002F app\u002Fapi\u002Fai+api.ts\nexport async function POST(request: Request) {\n  const { prompt } = await request.json();\n\n  const response = await fetch(\"https:\u002F\u002Fapi.openai.com\u002Fv1\u002Fchat\u002Fcompletions\", {\n    method: \"POST\",\n    headers: {\n      \"Content-Type\": \"application\u002Fjson\",\n      Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,\n    },\n    body: JSON.stringify({\n      model: \"gpt-4\",\n      messages: [{ role: \"user\", content: prompt }],\n    }),\n  });\n\n  const data = await response.json();\n  return Response.json(data);\n}\n```\n\nSet environment variables:\n\n- **Local**: Create `.env` file (never commit)\n- **EAS Hosting**: Use `eas env:create` or Expo dashboard\n\n## CORS Headers\n\nAdd CORS for web clients:\n\n```ts\nconst corsHeaders = {\n  \"Access-Control-Allow-Origin\": \"*\",\n  \"Access-Control-Allow-Methods\": \"GET, POST, PUT, DELETE, OPTIONS\",\n  \"Access-Control-Allow-Headers\": \"Content-Type, Authorization\",\n};\n\nexport function OPTIONS() {\n  return new Response(null, { headers: corsHeaders });\n}\n\nexport function GET() {\n  return Response.json({ data: \"value\" }, { headers: corsHeaders });\n}\n```\n\n## Error Handling\n\n```ts\nexport async function POST(request: Request) {\n  try {\n    const body = await request.json();\n    \u002F\u002F Process...\n    return Response.json({ success: true });\n  } catch (error) {\n    console.error(\"API error:\", error);\n    return Response.json({ error: \"Internal server error\" }, { status: 500 });\n  }\n}\n```\n\n## Testing Locally\n\nStart the development server with API routes:\n\n```bash\nnpx expo start\n# To test an exported production bundle instead:\n# npx expo export --platform web\n# npx expo serve\n```\n\nThis starts a local server at `http:\u002F\u002Flocalhost:8081` with full API route support.\n\nTest with curl:\n\n```bash\ncurl http:\u002F\u002Flocalhost:8081\u002Fapi\u002Fhello\ncurl -X POST http:\u002F\u002Flocalhost:8081\u002Fapi\u002Fusers -H \"Content-Type: application\u002Fjson\" -d '{\"name\":\"Test\"}'\n```\n\n## Deployment to EAS Hosting\n\n### Prerequisites\n\n```bash\nnpm install -g eas-cli\neas login\n```\n\n### Deploy\n\n```bash\neas deploy\n```\n\nExport the configured server web output first, then deploy that artifact with the supported EAS CLI; deployment is a separate authorized public action.\n\n### Environment Variables for Production\n\n```bash\n# Create a secret\neas env:create --name OPENAI_API_KEY --environment production\n# Use the supported interactive secret input; never pass real keys in command arguments.\n\n# Or use the Expo dashboard\n```\n\n### Custom Domain\n\nConfigure in `eas.json` or Expo dashboard.\n\n## EAS Hosting Runtime (Cloudflare Workers)\n\nAPI routes run on Cloudflare Workers. Key limitations:\n\n### Missing\u002FLimited APIs\n\n- **No Node.js filesystem** — `fs` module unavailable\n- **No native Node modules** — Use Web APIs or polyfills\n- **Limited execution time** — 30 second timeout for CPU-intensive tasks\n- **No persistent connections** — WebSockets require Durable Objects\n- **fetch is available** — Use standard fetch for HTTP requests\n\n### Use Web APIs Instead\n\n```ts\n\u002F\u002F Use Web Crypto instead of Node crypto\nconst hash = await crypto.subtle.digest(\n  \"SHA-256\",\n  new TextEncoder().encode(\"data\")\n);\n\n\u002F\u002F Use fetch instead of node-fetch\nconst response = await fetch(\"https:\u002F\u002Fapi.example.com\");\n\n\u002F\u002F Use Response\u002FRequest (already available)\nreturn new Response(JSON.stringify(data), {\n  headers: { \"Content-Type\": \"application\u002Fjson\" },\n});\n```\n\n### Database Options\n\nSince filesystem is unavailable, use cloud databases:\n\n- **Cloudflare D1** — SQLite at the edge\n- **Turso** — Distributed SQLite\n- **PlanetScale** — Serverless MySQL\n- **Supabase** — Postgres with REST API\n- **Neon** — Serverless Postgres\n\nExample with Turso:\n\n```ts\n\u002F\u002F app\u002Fapi\u002Fusers+api.ts\nimport { createClient } from \"@libsql\u002Fclient\u002Fweb\";\n\nconst db = createClient({\n  url: process.env.TURSO_URL!,\n  authToken: process.env.TURSO_AUTH_TOKEN!,\n});\n\nexport async function GET() {\n  const result = await db.execute(\"SELECT * FROM users\");\n  return Response.json(result.rows);\n}\n```\n\n## Calling API Routes from Client\n\n```ts\n\u002F\u002F From React Native components\nconst response = await fetch(\"\u002Fapi\u002Fhello\");\nconst data = await response.json();\n\n\u002F\u002F With body\nconst response = await fetch(\"\u002Fapi\u002Fusers\", {\n  method: \"POST\",\n  headers: { \"Content-Type\": \"application\u002Fjson\" },\n  body: JSON.stringify({ name: \"John\" }),\n});\n```\n\n## Common Patterns\n\n### Authentication Middleware\n\n```ts\n\u002F\u002F utils\u002Fauth.ts\nexport async function requireAuth(request: Request) {\n  const token = request.headers.get(\"Authorization\")?.replace(\"Bearer \", \"\");\n\n  if (!token) {\n    throw new Response(JSON.stringify({ error: \"Unauthorized\" }), {\n      status: 401,\n      headers: { \"Content-Type\": \"application\u002Fjson\" },\n    });\n  }\n\n  \u002F\u002F Implement with the application identity provider; fail closed until implemented.\n  throw new Error(\"Token verification must be implemented; never return a fabricated user\");\n}\n\n\u002F\u002F app\u002Fapi\u002Fprotected+api.ts\nimport { requireAuth } from \"..\u002F..\u002Futils\u002Fauth\";\n\nexport async function GET(request: Request) {\n  const { userId } = await requireAuth(request);\n  return Response.json({ userId });\n}\n```\n\n### Proxy External API\n\n```ts\n\u002F\u002F app\u002Fapi\u002Fweather+api.ts\nexport async function GET(request: Request) {\n  const url = new URL(request.url);\n  const city = url.searchParams.get(\"city\");\n\n  const response = await fetch(\n    `https:\u002F\u002Fapi.weather.com\u002Fv1\u002Fcurrent?city=${city}&key=${process.env.WEATHER_API_KEY}`\n  );\n\n  return Response.json(await response.json());\n}\n```\n\n## Rules\n\n- NEVER expose API keys or secrets in client code\n- ALWAYS validate and sanitize user input\n- Use proper HTTP status codes (200, 201, 400, 401, 404, 500)\n- Handle errors gracefully with try\u002Fcatch\n- Keep API routes focused — one responsibility per endpoint\n- Use TypeScript for type safety\n- Log errors server-side for debugging\n\n## Limitations\n\n- Use this skill only when the task clearly matches its upstream product or API scope.\n- Verify commands, API behavior, pricing, quotas, credentials, and deployment effects against current official documentation before making changes.\n- Do not treat generated examples as a substitute for environment-specific tests, security review, or user approval for destructive or costly actions.\n",{"name":5,"risk":39,"source":54,"license":14,"date_added":55,"description":7,"source_repo":56,"source_type":48,"license_source":57},"https:\u002F\u002Fgithub.com\u002Fexpo\u002Fskills\u002Ftree\u002Fmain\u002Fplugins\u002Fexpo\u002Fskills\u002Fexpo-api-routes",1782864000000,"expo\u002Fskills","https:\u002F\u002Fgithub.com\u002Fexpo\u002Fskills\u002Fblob\u002Fmain\u002FLICENSE","7bf2279c8a65758319526a721d5c4b75683aec9c","skills\u002Fantigravity-expo-api-routes",3,"https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections","https:\u002F\u002Fgithub.com\u002Fboisenoise","2026-11-13 14:20:47",{"source":8,"skillName":5,"commands":65,"prompt":75,"zipAvailable":76},[66,69,72],{"tool":67,"command":68},"npx","npx skills add boisenoise\u002Fskills-collections --skill expo-api-routes",{"tool":70,"command":71},"bunx","bunx skills add boisenoise\u002Fskills-collections --skill expo-api-routes",{"tool":73,"command":74},"pnpm","pnpm dlx skills add boisenoise\u002Fskills-collections --skill expo-api-routes","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-expo-api-routes\u002FSKILL.md",true,[78],{"path":79,"resourceType":80,"resourceTypeDesc":81,"sizeBytes":82,"sourceHtmlUrl":83},"agents\u002Fopenai.yaml","REFERENCE","参考文档",353,"https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-expo-api-routes\u002Fagents\u002Fopenai.yaml",[85,112,134,156,175,198],{"skillNo":86,"name":87,"nameZh":6,"description":88,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":89,"licenseFileUrl":90,"sourceHtmlUrl":91,"categories":92,"keywords":93,"publishedAt":49},"01M6WNH92BHVFXHGPDZCPJ0AG0","web-access","Use Treg for public web or platform data only when existing search, browser and connected tools cannot cover the task, with explicit cost and data boundaries.","Apache-2.0","https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-web-access\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-web-access\u002FSKILL.md",[],[94,95,96,48,97,98,99,100,101,102,103,104,105,106,107,39,108,109,110,111,31],"web","access","web access","development","browser","search","data","Tools","read","fetch","only","youtube","free tools","platform","free","cover","normal","when",{"skillNo":113,"name":114,"nameZh":6,"description":115,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":89,"licenseFileUrl":116,"sourceHtmlUrl":117,"categories":118,"keywords":122,"publishedAt":49},"01M6WNF9XAYAB2CZDDJBAKNM93","treg","Use the Treg CLI or MCP catalog to discover, price and call authorized external tools with team-scoped credentials and a user-approved budget.","https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-treg\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-treg\u002FSKILL.md",[119],{"categoryNo":120,"categoryName":121,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000023","Skill与智能体开发",[114,123,124,32,48,97,46,101,125,126,127,128,129,130,131,39,132,37,133,33,31],"MCP","cli","Budget","credentials","team","catalog","use","call","discover","external tools","scoped",{"skillNo":135,"name":136,"nameZh":6,"description":137,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":89,"licenseFileUrl":138,"sourceHtmlUrl":139,"categories":140,"keywords":144,"publishedAt":49},"01M6WN93MEAZ4E41DYAHJRPHQT","recruit","Turn a job description into an evidence-linked candidate shortlist through Treg, with job-related ranking and optional authorized business contact enrichment.","https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-recruit\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-recruit\u002FSKILL.md",[141],{"categoryNo":142,"categoryName":143,"taxonomyType":25,"taxonomyTypeDesc":26},"01JBSEEDCAT000000000000038","人力资源",[136,32,48,46,45,145,146,147,148,149,39,150,151,152,37,34,153,154,155,33,114,31],"email","enrichment","business","evidence","phone","ranking","post","job description","person","candidate","reason",{"skillNo":157,"name":158,"nameZh":6,"description":159,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":89,"licenseFileUrl":160,"sourceHtmlUrl":161,"categories":162,"keywords":163,"publishedAt":49},"01M6WN8R5RXSMHM9B4D9FAFX42","rea-growth","Analyze a company’s public growth and content through Treg, distinguish observations from hypotheses, and produce an evidence-linked playbook within an approved budget.","https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-rea-growth\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-rea-growth\u002FSKILL.md",[],[164,165,166,32,48,46,167,168,45,169,125,170,171,172,148,39,173,174,33,114,31],"growth","rea","rea growth","produce","content","playbook","marketing","analyze","public","observations","company",{"skillNo":176,"name":177,"nameZh":6,"description":178,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":89,"licenseFileUrl":179,"sourceHtmlUrl":180,"categories":181,"keywords":182,"publishedAt":49},"01M6WN2EVE3RQRGWAKBT4S7YT2","make-ugc","Create original AI UGC-style product videos through Treg: research hooks, approve a licensed presenter and voice, generate clips, add captions and report costs.","https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-make-ugc\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-make-ugc\u002FSKILL.md",[],[183,184,32,185,48,186,46,187,188,189,190,191,39,192,193,194,195,196,197,33],"make","make ugc","research","create","Video","Captions","ads","short","hooks","head","style","niche","trending","talking-head","approve",{"skillNo":199,"name":200,"nameZh":6,"description":201,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":89,"licenseFileUrl":202,"sourceHtmlUrl":203,"categories":204,"keywords":205,"publishedAt":49},"01M6WN10ARAA63Y8K1YNGXJ2RM","lead-signals","Find and qualify public buying signals through Treg, rank fitting business accounts and optionally enrich authorized business contact information.","https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-lead-signals\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-lead-signals\u002FSKILL.md",[],[206,207,208,32,48,46,209,210,147,211,212,39,213,214,215,216,217,218,219,33],"signals","lead","lead signals","how","find","ready","as","how to","qualify","people","information","enrich","changes","funding"]